diff --git a/.gitignore b/.gitignore
index dcb2175..c4c063d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,7 +34,7 @@ local.properties
target/
.idea/
-config/
+logs/
*.iml
# project specific
diff --git a/README.md b/README.md
index 121bfbf..b865279 100644
--- a/README.md
+++ b/README.md
@@ -13,6 +13,28 @@ To get started, begin here:
3. If you have any problems read the [FAQ](https://github.com/NiceSystems/hrider/wiki/FAQ) first.
## News
+### 23 June, 2013: Release 1.0.7.2 available
+Issues fixed: [#45](https://github.com/NiceSystems/hrider/issues/45), [#46](https://github.com/NiceSystems/hrider/issues/46)
+
+[Download for hbase 0.94.1 - with dependencies](http://bit.ly/14R3Pdv).
+
+[Download for hbase 0.94.1 - without dependencies](http://bit.ly/1a3YF1A).
+### 18 June, 2013: Release 1.0.7.1 available
+Issues fixed: [#41](https://github.com/NiceSystems/hrider/issues/41), [#43](https://github.com/NiceSystems/hrider/issues/43), [#44](https://github.com/NiceSystems/hrider/issues/44)
+
+[Download for hbase 0.94.1](http://bit.ly/11Iesdr).
+### 19 May, 2013: Release 1.0.7.0 available
+New features: [#38](https://github.com/NiceSystems/hrider/issues/38)
+
+Issues fixed: [#36](https://github.com/NiceSystems/hrider/issues/36), [#39](https://github.com/NiceSystems/hrider/issues/39)
+
+[Download for hbase 0.94.1](http://bit.ly/19PgWvd).
+### 21 April, 2013: Release 1.0.6.0 available
+New features: [#34](https://github.com/NiceSystems/hrider/issues/34), [#35](https://github.com/NiceSystems/hrider/issues/35)
+
+Issues fixed: [#31](https://github.com/NiceSystems/hrider/issues/31)
+
+[Download for hbase 0.94.1](http://bit.ly/15uOEbu).
### 02 April, 2013: Release 1.0.5.0 available
New features: [#28](https://github.com/NiceSystems/hrider/issues/28)
diff --git a/documentation/images/h-rider.png b/documentation/images/h-rider.png
index 7d479f4..cf10e9e 100644
Binary files a/documentation/images/h-rider.png and b/documentation/images/h-rider.png differ
diff --git a/documentation/images/typeConverterDialog.png b/documentation/images/typeConverterDialog.png
new file mode 100644
index 0000000..f3b9da3
Binary files /dev/null and b/documentation/images/typeConverterDialog.png differ
diff --git a/documentation/images/welcome.png b/documentation/images/welcome.png
index f484f47..a1ed61c 100644
Binary files a/documentation/images/welcome.png and b/documentation/images/welcome.png differ
diff --git a/pom.xml b/pom.xml
index 42060e1..3c0c510 100644
--- a/pom.xml
+++ b/pom.xml
@@ -10,11 +10,11 @@
hbase viewer and editor
jar
- 1.0.5.0
+ 1.0.7.2
- local
+ localrepository
file://${project.basedir}/repo
@@ -91,6 +91,12 @@
forms_rt
7.0.3
+
+ com.sun
+ tools
+ compile
+ 1.6
+
@@ -128,8 +134,8 @@
lib/
- ${project.artifactId}
${project.version}
+ ${hbase.version}
diff --git a/repo/com/sun/tools/1.6/_maven.repositories b/repo/com/sun/tools/1.6/_maven.repositories
new file mode 100644
index 0000000..02cc1c6
--- /dev/null
+++ b/repo/com/sun/tools/1.6/_maven.repositories
@@ -0,0 +1,4 @@
+#NOTE: This is an internal implementation file, its format can be changed without prior notice.
+#Thu Apr 18 18:52:10 IDT 2013
+tools-1.6.jar>=
+tools-1.6.pom>=
diff --git a/repo/com/sun/tools/1.6/tools-1.6.jar b/repo/com/sun/tools/1.6/tools-1.6.jar
new file mode 100644
index 0000000..1d44f43
Binary files /dev/null and b/repo/com/sun/tools/1.6/tools-1.6.jar differ
diff --git a/repo/com/sun/tools/1.6/tools-1.6.pom b/repo/com/sun/tools/1.6/tools-1.6.pom
new file mode 100644
index 0000000..f3bb257
--- /dev/null
+++ b/repo/com/sun/tools/1.6/tools-1.6.pom
@@ -0,0 +1,9 @@
+
+
+ 4.0.0
+ com.sun
+ tools
+ 1.6
+ POM was created from install:install-file
+
diff --git a/repo/com/sun/tools/maven-metadata-local.xml b/repo/com/sun/tools/maven-metadata-local.xml
new file mode 100644
index 0000000..ace1dd4
--- /dev/null
+++ b/repo/com/sun/tools/maven-metadata-local.xml
@@ -0,0 +1,12 @@
+
+
+ com.sun
+ tools
+
+ 1.6
+
+ 1.6
+
+ 20130418155210
+
+
diff --git a/src/main/java/hrider/actions/Action.java b/src/main/java/hrider/actions/Action.java
new file mode 100644
index 0000000..33465e9
--- /dev/null
+++ b/src/main/java/hrider/actions/Action.java
@@ -0,0 +1,41 @@
+package hrider.actions;
+
+/**
+ * Copyright (C) 2012 NICE Systems ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @author Igor Cher
+ * @version %I%, %G%
+ *
+ * This class represents an executable delegate.
+ */
+public abstract class Action {
+
+ /**
+ * Executes the action.
+ *
+ * @throws Exception Any error.
+ */
+ public abstract R run() throws Exception;
+
+ /**
+ * The method is called when the error occurred.
+ *
+ * @param ex The error.
+ */
+ @SuppressWarnings("NoopMethodInAbstractClass")
+ public void onError(Exception ex) {
+
+ }
+}
diff --git a/src/main/java/hrider/actions/RunnableAction.java b/src/main/java/hrider/actions/RunnableAction.java
new file mode 100644
index 0000000..f125df2
--- /dev/null
+++ b/src/main/java/hrider/actions/RunnableAction.java
@@ -0,0 +1,150 @@
+package hrider.actions;
+
+import hrider.io.Log;
+import org.apache.commons.lang.time.StopWatch;
+
+/**
+ * Copyright (C) 2012 NICE Systems ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @author Igor Cher
+ * @version %I%, %G%
+ *
+ * This class represents a runnable action.
+ */
+public class RunnableAction implements Runnable {
+
+ //region Constants
+ private final static Log logger = Log.getLogger(RunnableAction.class);
+ //endregion
+
+ //region Variables
+ private String name;
+ private Action action;
+ private Thread thread;
+ private boolean isRunning;
+ private boolean interrupted;
+ private R result;
+ //endregion
+
+ //region Constructor
+ public RunnableAction(String name, Action action) {
+ this.name = name;
+ this.action = action;
+ }
+ //endregion
+
+ //region Public Properties
+ public R getResult() {
+ return result;
+ }
+
+ public boolean isCompleted() {
+ return !isRunning && !interrupted;
+ }
+
+ public boolean isRunning() {
+ return isRunning;
+ }
+ //endregion
+
+ //region Public Methods
+ public static RunnableAction run(String name, Action action) {
+ RunnableAction runnableAction = new RunnableAction(name, action);
+ runnableAction.start();
+
+ return runnableAction;
+ }
+
+ public static R runAndWait(String name, Action action, long timeout) {
+ RunnableAction runnableAction = new RunnableAction(name, action);
+ runnableAction.start();
+
+ runnableAction.waitOrAbort(timeout);
+ return runnableAction.result;
+ }
+
+ public void start() {
+ if (this.thread == null) {
+ this.thread = new Thread(this);
+ this.thread.setName(this.name);
+ this.thread.setDaemon(true);
+ this.thread.start();
+
+ this.isRunning = true;
+
+ logger.info("Action '%s' started.", this.name);
+ }
+ }
+
+ public void abort() {
+ if (this.isRunning) {
+ this.interrupted = true;
+
+ this.thread.interrupt();
+ this.thread = null;
+ }
+ }
+
+ public void waitUntil(long timeout) {
+ StopWatch stopWatch = new StopWatch();
+ stopWatch.start();
+
+ long localTimeout = timeout / 10;
+
+ while (this.isRunning) {
+ try {
+ Thread.sleep(localTimeout);
+
+ if (stopWatch.getTime() > timeout) {
+ break;
+ }
+ }
+ catch (InterruptedException ignore) {
+ }
+ }
+
+ stopWatch.stop();
+ }
+
+ public void waitOrAbort(long timeout) {
+ waitUntil(timeout);
+
+ if (this.isRunning) {
+ abort();
+ }
+ }
+
+ @Override
+ public void run() {
+ try {
+ this.result = action.run();
+ this.isRunning = false;
+
+ logger.info("Action '%s' completed.", this.name);
+ }
+ catch (Exception e) {
+ this.isRunning = false;
+
+ if (this.interrupted) {
+ logger.info("Action '%s' aborted.", this.name);
+ }
+ else {
+ logger.info("Action '%s' failed.", this.name);
+ this.action.onError(e);
+ }
+ }
+ }
+ //endregion
+}
diff --git a/src/main/java/hrider/config/ClusterConfig.java b/src/main/java/hrider/config/ClusterConfig.java
index 4b9682c..0406d69 100644
--- a/src/main/java/hrider/config/ClusterConfig.java
+++ b/src/main/java/hrider/config/ClusterConfig.java
@@ -85,14 +85,14 @@ public T getTableConfig(Class clazz, String table) {
/**
* Gets a configuration data related to the specified table and a corresponding column.
*
- * @param clazz The class that represents the type of the data to be returned.
- * @param table The name of the table.
- * @param column The name of the column.
- * @param The type of the data to be returned.
+ * @param clazz The class that represents the type of the data to be returned.
+ * @param table The name of the table.
+ * @param key The key to identify the data.
+ * @param The type of the data to be returned.
* @return The data of the specified type.
*/
- public T getTableConfig(Class clazz, String table, String column) {
- return get(clazz, String.format("table.%s.%s", table, column));
+ public T getTableConfig(Class clazz, String table, String key) {
+ return get(clazz, String.format("table.%s.%s", table, key));
}
/**
@@ -123,12 +123,12 @@ public void setTableConfig(String table, String value) {
/**
* Sets a configuration data for the specified table and column.
*
- * @param table The name of the table.
- * @param column The name of the column.
- * @param value The data to set.
+ * @param table The name of the table.
+ * @param key The identifier of the value.
+ * @param value The data to set.
*/
- public void setTableConfig(String table, String column, String value) {
- set(String.format("table.%s.%s", table, column), value);
+ public void setTableConfig(String table, String key, String value) {
+ set(String.format("table.%s.%s", table, key), value);
save();
}
diff --git a/src/main/java/hrider/config/ConnectionDetails.java b/src/main/java/hrider/config/ConnectionDetails.java
index 33d1dbf..b9e7169 100644
--- a/src/main/java/hrider/config/ConnectionDetails.java
+++ b/src/main/java/hrider/config/ConnectionDetails.java
@@ -1,10 +1,13 @@
package hrider.config;
+import hrider.actions.Action;
+import hrider.actions.RunnableAction;
import hrider.hbase.ConnectionManager;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import java.io.IOException;
+import java.io.Serializable;
/**
* Copyright (C) 2012 NICE Systems ltd.
@@ -26,7 +29,11 @@
*
* This class represents a data to be used to connect to the hbase and zookeeper nodes.
*/
-public class ConnectionDetails {
+public class ConnectionDetails implements Serializable {
+
+ //region Constants
+ private static final long serialVersionUID = -5808921673890223877L;
+ //endregion
//region Variables
private ServerDetails zookeeper;
@@ -44,13 +51,17 @@ public void setZookeeper(ServerDetails zookeeper) {
//region Public Methods
public boolean canConnect() {
- try {
- ConnectionManager.create(this);
- return true;
- }
- catch (IOException ignore) {
- return false;
- }
+ Boolean result = RunnableAction.runAndWait(
+ this.zookeeper.getHost(), new Action() {
+
+ @Override
+ public Boolean run() throws IOException {
+ ConnectionManager.create(ConnectionDetails.this);
+ return true;
+ }
+ }, GlobalConfig.instance().getConnectionCheckTimeout());
+
+ return result != null ? result : false;
}
public Configuration createConfig() {
@@ -59,6 +70,9 @@ public Configuration createConfig() {
config.set("hbase.zookeeper.property.clientPort", this.zookeeper.getPort());
config.set("hbase.client.retries.number", "3");
+// config.set("hbase.security.authentication", "kerberos");
+// config.set("hbase.rpc.engine", "org.apache.hadoop.hbase.ipc.SecureRpcEngine");
+
return config;
}
diff --git a/src/main/java/hrider/config/GlobalConfig.java b/src/main/java/hrider/config/GlobalConfig.java
index 846d799..0e45c90 100644
--- a/src/main/java/hrider/config/GlobalConfig.java
+++ b/src/main/java/hrider/config/GlobalConfig.java
@@ -1,5 +1,7 @@
package hrider.config;
+import hrider.io.PathHelper;
+
/**
* Copyright (C) 2012 NICE Systems ltd.
*
@@ -28,14 +30,20 @@ public class GlobalConfig extends PropertiesConfig {
private static final String KEY_EXTERNAL_VIEWER_DELIMETER = "global.externalViewerDelimiter";
private static final String KEY_BATCH_READ_SIZE = "global.batch.readSize";
private static final String KEY_BATCH_WRITE_SIZE = "global.batch.writeSize";
- private static final String KEY_COMPILATION_FOLDER = "global.compilation.folder";
+ private static final String KEY_CONNECTION_CHECK_TIMEOUT = "global.connection.check.timeout";
+ private static final String KEY_ROW_COUNT_OPERATION_TIMEOUT = "global.operation.timeout.rowCount";
+ private static final String KEY_CONVERTERS_CLASSES_FOLDER = "global.converters.classes.folder";
+ private static final String KEY_CONVERTERS_CODE_FOLDER = "global.converters.code.folder";
private static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss.SSS ZZ";
private static final String DEFAULT_EXTERNAL_VIEWER_FILE_EXTENSION = ".csv";
private static final String DEFAULT_EXTERNAL_VIEWER_DELIMETER = ",";
private static final String DEFAULT_BATCH_READ_SIZE = "1000";
private static final String DEFAULT_BATCH_WRITE_SIZE = "100";
- private static final String DEFAULT_COMPILATION_FOLDER = "dynamic";
+ private static final String DEFAULT_CONNECTION_CHECK_TIMEOUT = "5000";
+ private static final String DEFAULT_ROW_COUNT_OPERATION_TIMEOUT = "30000";
+ private static final String DEFAULT_CONVERTERS_CLASSES_FOLDER = "converters/classes";
+ private static final String DEFAULT_CONVERTERS_CODE_FOLDER = "converters/code";
//endregion
//region Variables
@@ -62,7 +70,7 @@ public static GlobalConfig instance() {
}
/**
- * Gets the date time format to be used to parse/convert date time strings.
+ * Gets the date time format to be used to parse/converters date time strings.
*
* @return A {@link String} representing date time format.
*/
@@ -107,12 +115,54 @@ public int getBatchSizeForWrite() {
}
/**
- * Gets a folder where the compiled files should be saved.
+ * Gets an amount of time to wait for hbase connection during check.
+ *
+ * @return An amount of time to wait.
+ */
+ public long getConnectionCheckTimeout() {
+ return get(Long.class, KEY_CONNECTION_CHECK_TIMEOUT, DEFAULT_CONNECTION_CHECK_TIMEOUT);
+ }
+
+ /**
+ * Gets an amount of time to wait before stopping row count operation.
+ *
+ * @return An amount of time to wait.
+ */
+ public long getRowCountTimeout() {
+ return get(Long.class, KEY_ROW_COUNT_OPERATION_TIMEOUT, DEFAULT_ROW_COUNT_OPERATION_TIMEOUT);
+ }
+
+ /**
+ * Gets a folder where the compiled classes of the custom converters should be located.
+ *
+ * @return A path to the folder.
+ */
+ public String getConvertersClassesFolder() {
+ return PathHelper.append(PathHelper.getCurrentFolder(), get(String.class, KEY_CONVERTERS_CLASSES_FOLDER, DEFAULT_CONVERTERS_CLASSES_FOLDER));
+ }
+
+ /**
+ * Gets a folder where the java code of the custom converters should be located.
*
- * @return A path to a folder.
+ * @return A path to the folder.
*/
- public String getCompilationFolder() {
- return get(String.class, KEY_COMPILATION_FOLDER, DEFAULT_COMPILATION_FOLDER);
+ public String getConvertersCodeFolder() {
+ return PathHelper.append(PathHelper.getCurrentFolder(), get(String.class, KEY_CONVERTERS_CODE_FOLDER, DEFAULT_CONVERTERS_CODE_FOLDER));
+ }
+ //endregion
+
+ //region Protected Methods
+ @Override
+ protected void onFileCreated() {
+ set(KEY_ROW_COUNT_OPERATION_TIMEOUT, DEFAULT_ROW_COUNT_OPERATION_TIMEOUT);
+ set(KEY_BATCH_READ_SIZE, DEFAULT_BATCH_READ_SIZE);
+ set(KEY_BATCH_WRITE_SIZE, DEFAULT_BATCH_WRITE_SIZE);
+ set(KEY_CONNECTION_CHECK_TIMEOUT, DEFAULT_CONNECTION_CHECK_TIMEOUT);
+ set(KEY_DATE_FORMAT, DEFAULT_DATE_FORMAT);
+ set(KEY_EXTERNAL_VIEWER_DELIMETER, DEFAULT_EXTERNAL_VIEWER_DELIMETER);
+ set(KEY_EXTERNAL_VIEWER_FILE_EXTENSION, DEFAULT_EXTERNAL_VIEWER_FILE_EXTENSION);
+
+ save();
}
//endregion
}
diff --git a/src/main/java/hrider/config/PropertiesConfig.java b/src/main/java/hrider/config/PropertiesConfig.java
index fd8a9a0..f1c371d 100644
--- a/src/main/java/hrider/config/PropertiesConfig.java
+++ b/src/main/java/hrider/config/PropertiesConfig.java
@@ -1,5 +1,6 @@
package hrider.config;
+import hrider.io.Log;
import hrider.reflection.Clazz;
import java.io.*;
@@ -28,9 +29,12 @@
* The base class for all configuration classes that are based on files represented as properties.
* This class also supports dynamic update of the configuration data meaning that a file is tracked for changes.
*/
+@SuppressWarnings("ResultOfMethodCallIgnored")
public abstract class PropertiesConfig {
//region Variables
+ private final static Log logger = Log.getLogger(PropertiesConfig.class);
+
private Properties properties;
private File file;
//endregion
@@ -53,13 +57,15 @@ protected PropertiesConfig(String name) {
folder.mkdirs();
}
this.file.createNewFile();
+
+ onFileCreated();
}
loadProperties(this.file);
loadSystemProperties();
}
catch (IOException e) {
- e.printStackTrace();
+ logger.error(e, "Failed to load properties from the file '%s'", name);
}
}
//endregion
@@ -107,10 +113,11 @@ public T get(Class clazz, String name) {
* @param The type of the value.
* @return A value of the requested property.
*/
+ @SuppressWarnings("unchecked")
public T get(Class clazz, String name, String defaultValue) {
String value = this.properties.getProperty(name, defaultValue);
if (value != null && !value.isEmpty()) {
- return (T)Clazz.primitiveToObject(clazz, value);
+ return (T)Clazz.fromPrimitive(clazz, value);
}
return null;
}
@@ -173,12 +180,18 @@ public void save() {
}
//endregion
+ //region Protected Methods
+ protected void onFileCreated() {
+
+ }
+ //endregion
+
//region Private Methods
private static File loadFile(String name) {
return new File("config/" + name + ".properties");
}
- private void loadProperties(File file) throws IOException {
+ private void loadProperties(File file) throws IOException, FileNotFoundException {
if (file != null) {
InputStream stream = null;
try {
diff --git a/src/main/java/hrider/config/ServerDetails.java b/src/main/java/hrider/config/ServerDetails.java
index 9ff4bb9..6813666 100644
--- a/src/main/java/hrider/config/ServerDetails.java
+++ b/src/main/java/hrider/config/ServerDetails.java
@@ -1,5 +1,7 @@
package hrider.config;
+import java.io.Serializable;
+
/**
* Copyright (C) 2012 NICE Systems ltd.
*
@@ -20,7 +22,11 @@
*
* This class represents a details of the server to connect to..
*/
-public class ServerDetails {
+public class ServerDetails implements Serializable {
+
+ //region Constants
+ private static final long serialVersionUID = -407779299748851910L;
+ //endregion
//region Variables
/**
@@ -37,6 +43,7 @@ public class ServerDetails {
/**
* Initializes a new instance of the {@link ServerDetails} class.
+ *
* @param host The server name.
* @param port The server port.
*/
@@ -50,6 +57,7 @@ public ServerDetails(String host, String port) {
/**
* Gets the name of the server.
+ *
* @return The name of the server.
*/
public String getHost() {
@@ -58,6 +66,7 @@ public String getHost() {
/**
* Sets a new server name.
+ *
* @param host A new server name.
*/
public void setHost(String host) {
@@ -66,6 +75,7 @@ public void setHost(String host) {
/**
* Gets a port.
+ *
* @return An {@link Integer} representing the port.
*/
public String getPort() {
@@ -74,6 +84,7 @@ public String getPort() {
/**
* Sets a new port.
+ *
* @param port A new port.
*/
public void setPort(String port) {
diff --git a/src/main/java/hrider/converters/BinaryStringConverter.java b/src/main/java/hrider/converters/BinaryStringConverter.java
new file mode 100644
index 0000000..fd67c2d
--- /dev/null
+++ b/src/main/java/hrider/converters/BinaryStringConverter.java
@@ -0,0 +1,44 @@
+package hrider.converters;
+
+import org.apache.hadoop.hbase.util.Bytes;
+
+/**
+ * Copyright (C) 2012 NICE Systems ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @author Igor Cher
+ * @version %I%, %G%
+ *
+ * This class is responsible for converting data from {@link byte[]} to {@link String} with support of specific bytes and vice versa.
+ */
+public class BinaryStringConverter extends StringConverter {
+
+ private static final long serialVersionUID = 3532868191797750281L;
+
+ @Override
+ public String toString(byte[] value) {
+ if (value == null) {
+ return null;
+ }
+ return Bytes.toStringBinary(value);
+ }
+
+ @Override
+ public byte[] toBytes(String value) {
+ if (value == null) {
+ return EMPTY_BYTES_ARRAY;
+ }
+ return Bytes.toBytesBinary(value);
+ }
+}
diff --git a/src/main/java/hrider/converters/BooleanConverter.java b/src/main/java/hrider/converters/BooleanConverter.java
new file mode 100644
index 0000000..c59ab3d
--- /dev/null
+++ b/src/main/java/hrider/converters/BooleanConverter.java
@@ -0,0 +1,44 @@
+package hrider.converters;
+
+import org.apache.hadoop.hbase.util.Bytes;
+
+/**
+ * Copyright (C) 2012 NICE Systems ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @author Igor Cher
+ * @version %I%, %G%
+ *
+ * This class is responsible for converting data from byte[] to boolean and vice versa.
+ */
+public class BooleanConverter extends TypeConverter {
+
+ private static final long serialVersionUID = 804613405302620990L;
+
+ @Override
+ public String toString(byte[] value) {
+ if (value == null) {
+ return null;
+ }
+ return Boolean.toString(Bytes.toBoolean(value));
+ }
+
+ @Override
+ public byte[] toBytes(String value) {
+ if (value == null) {
+ return EMPTY_BYTES_ARRAY;
+ }
+ return Bytes.toBytes(Boolean.parseBoolean(value));
+ }
+}
diff --git a/src/main/java/hrider/converters/ConvertersLoader.java b/src/main/java/hrider/converters/ConvertersLoader.java
new file mode 100644
index 0000000..6e8dc5a
--- /dev/null
+++ b/src/main/java/hrider/converters/ConvertersLoader.java
@@ -0,0 +1,268 @@
+package hrider.converters;
+
+import hrider.config.GlobalConfig;
+import hrider.io.Log;
+import hrider.io.PathHelper;
+import hrider.reflection.JavaPackage;
+
+import java.io.*;
+import java.lang.reflect.Modifier;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.*;
+
+/**
+ * Copyright (C) 2012 NICE Systems ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @author Igor Cher
+ * @version %I%, %G%
+ *
+ * This class is responsible for loading and creation of type converters.
+ */
+@SuppressWarnings({"CallToPrintStackTrace", "ResultOfMethodCallIgnored"})
+public class ConvertersLoader {
+
+ //region Variables
+ private static final Log logger = Log.getLogger(ConvertersLoader.class);
+ private static final List handlers;
+ private static Map converters;
+ //endregion
+
+ //region Constructor
+ static {
+ handlers = new ArrayList();
+ converters = load();
+ }
+
+ private ConvertersLoader() {
+ }
+ //endregion
+
+ //region Public Methods
+
+ /**
+ * Adds a handler.
+ *
+ * @param handler The new handler.
+ */
+ public static void addHandler(ConvertersLoaderHandler handler) {
+ handlers.add(handler);
+ }
+
+ /**
+ * Removes a handler.
+ *
+ * @param handler A handler to remove.
+ */
+ public static void removeHandler(ConvertersLoaderHandler handler) {
+ handlers.remove(handler);
+ }
+
+ /**
+ * Checks whether the specified converter exists.
+ *
+ * @param name The name of the converter to check.
+ * @return True if the specified converter exists or False otherwise.
+ */
+ public static boolean exists(String name) {
+ return converters.containsKey(name);
+ }
+
+ /**
+ * Gets all loaded converters.
+ *
+ * @return A list of type converters.
+ */
+ public static Collection getConverters() {
+ return converters.values();
+ }
+
+ /**
+ * Gets all loaded converters that support column name conversions.
+ *
+ * @return A list of type converters.
+ */
+ public static Collection getNameConverters() {
+ Collection list = new ArrayList();
+ for (TypeConverter converter : converters.values()) {
+ if (converter.isValidForNameConversion()) {
+ list.add(converter);
+ }
+ }
+ return list;
+ }
+
+ /**
+ * Gets a specific converter according to the provided name.
+ *
+ * @param name The name of the converter to get.
+ * @return A type converter if found or null otherwise.
+ */
+ public static TypeConverter getConverter(String name) {
+ return converters.get(name);
+ }
+
+ /**
+ * Handles converter editing.
+ *
+ * @param oldName A new converter name if the name was changed or the old one.
+ * @param newName A new converter name if the name was changed or the old one.
+ */
+ public static void editConverter(String oldName, String newName) {
+ if (!oldName.equals(newName)) {
+ deleteConverter(oldName);
+ }
+
+ reload();
+
+ for (ConvertersLoaderHandler handler : handlers) {
+ handler.onEdit(oldName, newName);
+ }
+ }
+
+ /**
+ * Removes converter from the loader's cache and from the file system.
+ *
+ * @param name The name of the converter to remove.
+ */
+ public static void removeConverter(String name) {
+ if (deleteConverter(name)) {
+ reload();
+
+ for (ConvertersLoaderHandler handler : handlers) {
+ handler.onRemove(name);
+ }
+ }
+ }
+
+ /**
+ * Reloads converters.
+ */
+ public static void reload() {
+ converters = load();
+
+ for (ConvertersLoaderHandler handler : handlers) {
+ handler.onLoad();
+ }
+ }
+ //endregion
+
+ //region Private Methods
+ private static boolean deleteConverter(String name) {
+ TypeConverter converter = converters.get(name);
+ if (converter != null) {
+ converters.remove(name);
+
+ File classFile = new File(
+ PathHelper.append(
+ PathHelper.append(GlobalConfig.instance().getConvertersClassesFolder(), "hrider/converters/custom"),
+ converter.getClass().getSimpleName() + ".class"));
+
+ if (classFile.exists()) {
+ classFile.delete();
+ }
+
+ File codeFile = new File(PathHelper.append(GlobalConfig.instance().getConvertersCodeFolder(), converter.getClass().getSimpleName() + ".java"));
+ if (codeFile.exists()) {
+ codeFile.delete();
+ }
+
+ return true;
+ }
+ return false;
+ }
+
+ private static Map load() {
+ Map map = new TreeMap();
+
+ loadPackage("hrider.converters", map);
+ loadFolder(GlobalConfig.instance().getConvertersClassesFolder(), "hrider.converters.custom", map);
+
+ return map;
+ }
+
+ private static void loadPackage(String packageName, Map map) {
+ try {
+ for (Class> clazz : JavaPackage.getClasses(packageName)) {
+ if (TypeConverter.class.isAssignableFrom(clazz) && !Modifier.isAbstract(clazz.getModifiers())) {
+ try {
+ TypeConverter converter = (TypeConverter)clazz.getConstructor().newInstance();
+ map.put(converter.getName(), converter);
+ }
+ catch (Exception e) {
+ logger.error(e, "Failed to load converter '%s'", clazz.getName());
+ }
+ }
+ }
+ }
+ catch (Exception e) {
+ logger.error(e, "Failed to load converters from the package '%s'", packageName);
+ }
+ }
+
+ private static void loadFolder(String folder, String packageName, Map map) {
+ try {
+ URLClassLoader loader = new URLClassLoader(
+ new URL[]{
+ new File(folder).toURI().toURL()
+ }, Thread.currentThread().getContextClassLoader());
+
+ for (Class> clazz : JavaPackage.getClassesFromFolder(loader, new File(folder), packageName)) {
+ if (TypeConverter.class.isAssignableFrom(clazz) && !Modifier.isAbstract(clazz.getModifiers())) {
+ try {
+ TypeConverter converter = (TypeConverter)clazz.getConstructor().newInstance();
+ converter.setCode(
+ loadCode(
+ new File(
+ PathHelper.append(GlobalConfig.instance().getConvertersCodeFolder(), converter.getClass().getSimpleName() + ".java"))));
+
+ map.put(converter.getName(), converter);
+ }
+ catch (Exception e) {
+ logger.error(e, "Failed to load converter '%s'", clazz.getName());
+ }
+ }
+ }
+ }
+ catch (Exception e) {
+ logger.error(e, "Failed to load converters from the folder '%s' for the package '%s'", folder, packageName);
+ }
+ }
+
+ private static String loadCode(File file) throws IOException, FileNotFoundException {
+ if (file.exists()) {
+ StringBuilder code = new StringBuilder();
+
+ BufferedReader reader = null;
+ try {
+ reader = new BufferedReader(new FileReader(file));
+
+ String line;
+ while ((line = reader.readLine()) != null) {
+ code.append(line);
+ code.append(PathHelper.LINE_SEPARATOR);
+ }
+ return code.toString();
+ }
+ finally {
+ if (reader != null) {
+ reader.close();
+ }
+ }
+ }
+ return null;
+ }
+ //endregion
+}
diff --git a/src/main/java/hrider/converters/ConvertersLoaderHandler.java b/src/main/java/hrider/converters/ConvertersLoaderHandler.java
new file mode 100644
index 0000000..4b068d3
--- /dev/null
+++ b/src/main/java/hrider/converters/ConvertersLoaderHandler.java
@@ -0,0 +1,42 @@
+package hrider.converters;
+
+/**
+ * Copyright (C) 2012 NICE Systems ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @author Igor Cher
+ * @version %I%, %G%
+ */
+public interface ConvertersLoaderHandler {
+
+ /**
+ * The method is called when all the converters were loaded.
+ */
+ void onLoad();
+
+ /**
+ * The method is called when a converter was edited.
+ *
+ * @param oldName The old name of the converter if it was changed.
+ * @param newName The new name of the converter if it was changed.
+ */
+ void onEdit(String oldName, String newName);
+
+ /**
+ * The method is called when the converter was deleted.
+ *
+ * @param name The name of the deleted converter.
+ */
+ void onRemove(String name);
+}
diff --git a/src/main/java/hrider/converters/DateAsLongConverter.java b/src/main/java/hrider/converters/DateAsLongConverter.java
new file mode 100644
index 0000000..163b914
--- /dev/null
+++ b/src/main/java/hrider/converters/DateAsLongConverter.java
@@ -0,0 +1,64 @@
+package hrider.converters;
+
+import hrider.config.GlobalConfig;
+import hrider.io.Log;
+import org.apache.hadoop.hbase.util.Bytes;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+/**
+ * Copyright (C) 2012 NICE Systems ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @author Igor Cher
+ * @version %I%, %G%
+ *
+ * This class is responsible for converting data from byte[] to Date represented as Long and vice versa.
+ */
+public class DateAsLongConverter extends TypeConverter {
+
+ private static final Log logger = Log.getLogger(DateAsLongConverter.class);
+ private static final long serialVersionUID = 7524770450006316409L;
+
+ @Override
+ public String toString(byte[] value) {
+ if (value == null) {
+ return null;
+ }
+
+ DateFormat df = new SimpleDateFormat(GlobalConfig.instance().getDateFormat(), Locale.ENGLISH);
+ return df.format(new Date(Bytes.toLong(value)));
+ }
+
+ @Override
+ public byte[] toBytes(String value) {
+ if (value == null) {
+ return EMPTY_BYTES_ARRAY;
+ }
+
+ DateFormat df = new SimpleDateFormat(GlobalConfig.instance().getDateFormat(), Locale.ENGLISH);
+ try {
+ Date date = df.parse(value);
+ return Bytes.toBytes(date.getTime());
+ }
+ catch (ParseException e) {
+ logger.error(e, "Failed to convert DateTime '%s' to byte array.", value);
+ return null;
+ }
+ }
+}
diff --git a/src/main/java/hrider/converters/DateAsStringConverter.java b/src/main/java/hrider/converters/DateAsStringConverter.java
new file mode 100644
index 0000000..3b0758b
--- /dev/null
+++ b/src/main/java/hrider/converters/DateAsStringConverter.java
@@ -0,0 +1,31 @@
+package hrider.converters;
+
+/**
+ * Copyright (C) 2012 NICE Systems ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @author Igor Cher
+ * @version %I%, %G%
+ *
+ * This class is responsible for converting data from byte[] to Date represented as String and vice versa.
+ */
+public class DateAsStringConverter extends StringConverter {
+
+ private static final long serialVersionUID = 8385887468388870228L;
+
+ @Override
+ public boolean isValidForNameConversion() {
+ return false;
+ }
+}
diff --git a/src/main/java/hrider/converters/DoubleConverter.java b/src/main/java/hrider/converters/DoubleConverter.java
new file mode 100644
index 0000000..0367b4a
--- /dev/null
+++ b/src/main/java/hrider/converters/DoubleConverter.java
@@ -0,0 +1,44 @@
+package hrider.converters;
+
+import org.apache.hadoop.hbase.util.Bytes;
+
+/**
+ * Copyright (C) 2012 NICE Systems ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @author Igor Cher
+ * @version %I%, %G%
+ *
+ * This class is responsible for converting data from byte[] to double and vice versa.
+ */
+public class DoubleConverter extends TypeConverter {
+
+ private static final long serialVersionUID = 2098974229929678180L;
+
+ @Override
+ public String toString(byte[] value) {
+ if (value == null) {
+ return null;
+ }
+ return Double.toString(Bytes.toDouble(value));
+ }
+
+ @Override
+ public byte[] toBytes(String value) {
+ if (value == null) {
+ return EMPTY_BYTES_ARRAY;
+ }
+ return Bytes.toBytes(Double.parseDouble(value));
+ }
+}
diff --git a/src/main/java/hrider/converters/FloatConverter.java b/src/main/java/hrider/converters/FloatConverter.java
new file mode 100644
index 0000000..2686886
--- /dev/null
+++ b/src/main/java/hrider/converters/FloatConverter.java
@@ -0,0 +1,44 @@
+package hrider.converters;
+
+import org.apache.hadoop.hbase.util.Bytes;
+
+/**
+ * Copyright (C) 2012 NICE Systems ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @author Igor Cher
+ * @version %I%, %G%
+ *
+ * This class is responsible for converting data from byte[] to float and vice versa.
+ */
+public class FloatConverter extends TypeConverter {
+
+ private static final long serialVersionUID = -4945353944739350169L;
+
+ @Override
+ public String toString(byte[] value) {
+ if (value == null) {
+ return null;
+ }
+ return Float.toString(Bytes.toFloat(value));
+ }
+
+ @Override
+ public byte[] toBytes(String value) {
+ if (value == null) {
+ return EMPTY_BYTES_ARRAY;
+ }
+ return Bytes.toBytes(Float.parseFloat(value));
+ }
+}
diff --git a/src/main/java/hrider/converters/IntegerConverter.java b/src/main/java/hrider/converters/IntegerConverter.java
new file mode 100644
index 0000000..3b6bf7e
--- /dev/null
+++ b/src/main/java/hrider/converters/IntegerConverter.java
@@ -0,0 +1,44 @@
+package hrider.converters;
+
+import org.apache.hadoop.hbase.util.Bytes;
+
+/**
+ * Copyright (C) 2012 NICE Systems ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @author Igor Cher
+ * @version %I%, %G%
+ *
+ * This class is responsible for converting data from byte[] to integer and vice versa.
+ */
+public class IntegerConverter extends TypeConverter {
+
+ private static final long serialVersionUID = -6800729143958428018L;
+
+ @Override
+ public String toString(byte[] value) {
+ if (value == null) {
+ return null;
+ }
+ return Integer.toString(Bytes.toInt(value));
+ }
+
+ @Override
+ public byte[] toBytes(String value) {
+ if (value == null) {
+ return EMPTY_BYTES_ARRAY;
+ }
+ return Bytes.toBytes(Integer.parseInt(value));
+ }
+}
diff --git a/src/main/java/hrider/converters/JsonConverter.java b/src/main/java/hrider/converters/JsonConverter.java
new file mode 100644
index 0000000..da84773
--- /dev/null
+++ b/src/main/java/hrider/converters/JsonConverter.java
@@ -0,0 +1,31 @@
+package hrider.converters;
+
+/**
+ * Copyright (C) 2012 NICE Systems ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @author Igor Cher
+ * @version %I%, %G%
+ *
+ * This class is a place holder for JSON editors.
+ */
+public class JsonConverter extends StringConverter {
+
+ private static final long serialVersionUID = 6603609684230707505L;
+
+ @Override
+ public boolean isValidForNameConversion() {
+ return false;
+ }
+}
diff --git a/src/main/java/hrider/converters/LongConverter.java b/src/main/java/hrider/converters/LongConverter.java
new file mode 100644
index 0000000..4edc3ed
--- /dev/null
+++ b/src/main/java/hrider/converters/LongConverter.java
@@ -0,0 +1,44 @@
+package hrider.converters;
+
+import org.apache.hadoop.hbase.util.Bytes;
+
+/**
+ * Copyright (C) 2012 NICE Systems ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @author Igor Cher
+ * @version %I%, %G%
+ *
+ * This class is responsible for converting data from byte[] to long and vice versa.
+ */
+public class LongConverter extends TypeConverter {
+
+ private static final long serialVersionUID = -7610134214895755716L;
+
+ @Override
+ public String toString(byte[] value) {
+ if (value == null) {
+ return null;
+ }
+ return Long.toString(Bytes.toLong(value));
+ }
+
+ @Override
+ public byte[] toBytes(String value) {
+ if (value == null) {
+ return EMPTY_BYTES_ARRAY;
+ }
+ return Bytes.toBytes(Long.parseLong(value));
+ }
+}
diff --git a/src/main/java/hrider/converters/ShortConverter.java b/src/main/java/hrider/converters/ShortConverter.java
new file mode 100644
index 0000000..f4374da
--- /dev/null
+++ b/src/main/java/hrider/converters/ShortConverter.java
@@ -0,0 +1,44 @@
+package hrider.converters;
+
+import org.apache.hadoop.hbase.util.Bytes;
+
+/**
+ * Copyright (C) 2012 NICE Systems ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @author Igor Cher
+ * @version %I%, %G%
+ *
+ * This class is responsible for converting data from byte[] to short and vice versa.
+ */
+public class ShortConverter extends TypeConverter {
+
+ private static final long serialVersionUID = -4063681955061981871L;
+
+ @Override
+ public String toString(byte[] value) {
+ if (value == null) {
+ return null;
+ }
+ return Short.toString(Bytes.toShort(value));
+ }
+
+ @Override
+ public byte[] toBytes(String value) {
+ if (value == null) {
+ return EMPTY_BYTES_ARRAY;
+ }
+ return Bytes.toBytes(Short.parseShort(value));
+ }
+}
diff --git a/src/main/java/hrider/converters/StringConverter.java b/src/main/java/hrider/converters/StringConverter.java
new file mode 100644
index 0000000..cc9cdb0
--- /dev/null
+++ b/src/main/java/hrider/converters/StringConverter.java
@@ -0,0 +1,49 @@
+package hrider.converters;
+
+import org.apache.hadoop.hbase.util.Bytes;
+
+/**
+ * Copyright (C) 2012 NICE Systems ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @author Igor Cher
+ * @version %I%, %G%
+ *
+ * This class is responsible for converting data from byte[] to string and vice versa.
+ */
+public class StringConverter extends TypeConverter {
+
+ private static final long serialVersionUID = 8661833153430116861L;
+
+ @Override
+ public byte[] toBytes(String value) {
+ if (value == null) {
+ return EMPTY_BYTES_ARRAY;
+ }
+ return Bytes.toBytes(value);
+ }
+
+ @Override
+ public String toString(byte[] value) {
+ if (value == null) {
+ return null;
+ }
+ return Bytes.toString(value);
+ }
+
+ @Override
+ public boolean isValidForNameConversion() {
+ return true;
+ }
+}
diff --git a/src/main/java/hrider/converters/TypeConverter.java b/src/main/java/hrider/converters/TypeConverter.java
new file mode 100644
index 0000000..1cf121d
--- /dev/null
+++ b/src/main/java/hrider/converters/TypeConverter.java
@@ -0,0 +1,139 @@
+package hrider.converters;
+
+import hrider.io.Log;
+
+import java.io.Serializable;
+
+/**
+ * Copyright (C) 2012 NICE Systems ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @author Igor Cher
+ * @version %I%, %G%
+ *
+ * This class is a base class for type converters.
+ */
+@SuppressWarnings("CallToSimpleGetterFromWithinClass")
+public abstract class TypeConverter implements Comparable, Serializable {
+
+ //region Constants
+ protected static final Log logger = Log.getLogger(TypeConverter.class);
+ protected static final byte[] EMPTY_BYTES_ARRAY = new byte[0];
+
+ private static final long serialVersionUID = 1490434164342371320L;
+ //endregion
+
+ //region Variables
+ private String name;
+ private String code;
+ //endregion
+
+ //region Constructor
+ protected TypeConverter() {
+ this.name = this.getClass().getSimpleName().replace("Converter", "");
+ }
+ //endregion
+
+ //region Public Methods
+
+ /**
+ * Gets the code of the type converter if available.
+ *
+ * @return The original code.
+ */
+ public String getCode() {
+ return code;
+ }
+
+ /**
+ * Sets the code for the type converter.
+ *
+ * @param code The code to set.
+ */
+ public void setCode(String code) {
+ this.code = code;
+ }
+
+ /**
+ * Gets the name of the converter to be presented as a column type.
+ *
+ * @return The name of the converter.
+ */
+ public String getName() {
+ return this.name;
+ }
+
+ /**
+ * Checks whether the type converter can be edited.
+ *
+ * @return True if the type converter can be edited or False otherwise.
+ */
+ public boolean isEditable() {
+ return code != null;
+ }
+
+ /**
+ * Indicates whether the type converter can be used for column name conversions.
+ *
+ * @return True if the type converter can be used for column name conversions or False otherwise.
+ */
+ public boolean isValidForNameConversion() {
+ return false;
+ }
+
+ /**
+ * Converts an {@link String} to an array of bytes.
+ *
+ * @param value An string to convert.
+ * @return A converted byte array.
+ */
+ public abstract byte[] toBytes(String value);
+
+ /**
+ * Converts an {@link byte[]} to a {@link String}.
+ *
+ * @param value An byte[] to convert.
+ * @return A converted string.
+ */
+ public abstract String toString(byte[] value);
+
+ @Override
+ public int compareTo(TypeConverter o) {
+ return this.getName().compareTo(o.getName());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+
+ if (TypeConverter.class.isAssignableFrom(obj.getClass())) {
+ return getName().equals(((TypeConverter)obj).getName());
+ }
+
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return getName().hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return getName();
+ }
+ //endregion
+}
diff --git a/src/main/java/hrider/converters/XmlConverter.java b/src/main/java/hrider/converters/XmlConverter.java
new file mode 100644
index 0000000..94aebfe
--- /dev/null
+++ b/src/main/java/hrider/converters/XmlConverter.java
@@ -0,0 +1,31 @@
+package hrider.converters;
+
+/**
+ * Copyright (C) 2012 NICE Systems ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @author Igor Cher
+ * @version %I%, %G%
+ *
+ * This class is a place holder for XML editors.
+ */
+public class XmlConverter extends StringConverter {
+
+ private static final long serialVersionUID = -2217840069549275853L;
+
+ @Override
+ public boolean isValidForNameConversion() {
+ return false;
+ }
+}
diff --git a/src/main/java/hrider/data/ColumnQualifier.java b/src/main/java/hrider/data/ColumnQualifier.java
index a6b8869..fc192b6 100644
--- a/src/main/java/hrider/data/ColumnQualifier.java
+++ b/src/main/java/hrider/data/ColumnQualifier.java
@@ -1,6 +1,10 @@
package hrider.data;
+import hrider.converters.TypeConverter;
+import hrider.io.Log;
+
import java.io.Serializable;
+import java.util.Arrays;
/**
* Copyright (C) 2012 NICE Systems ltd.
@@ -25,50 +29,54 @@
public class ColumnQualifier implements Serializable {
//region Constants
- public final static ColumnQualifier KEY = new ColumnQualifier("key");
+ public final static ColumnQualifier KEY = new ColumnQualifier("key", ColumnType.BinaryString.getConverter());
//endregion
//region Variables
+ private static final Log logger = Log.getLogger(ColumnQualifier.class);
private static final long serialVersionUID = 7851349292786398645L;
- private String name;
- private ColumnFamily columnFamily;
+
+ private byte[] name;
+ private ColumnFamily columnFamily;
+ private TypeConverter nameConverter;
//endregion
//region Constructor
- public ColumnQualifier(String name) {
- this(name, null);
+ public ColumnQualifier(String name, TypeConverter nameConverter) {
+ this(nameConverter.toBytes(name), null, nameConverter);
}
- public ColumnQualifier(String name, ColumnFamily columnFamily) {
- if (columnFamily == null) {
- if (name.contains(":")) {
- String[] parts = name.split(":");
-
- this.name = parts[1];
- this.columnFamily = new ColumnFamily(parts[0]);
- }
- else {
- this.name = name;
- }
- }
- else {
- this.name = name;
- this.columnFamily = columnFamily;
- }
+ public ColumnQualifier(String name, ColumnFamily columnFamily, TypeConverter nameConverter) {
+ this(nameConverter.toBytes(name), columnFamily, nameConverter);
+ }
+
+ public ColumnQualifier(byte[] name, ColumnFamily columnFamily, TypeConverter nameConverter) {
+ this.name = name;
+ this.columnFamily = columnFamily;
+ this.nameConverter = nameConverter;
}
//endregion
//region Public Properties
public String getName() {
+ try {
+ return this.nameConverter.toString(this.name);
+ }
+ catch (Exception e) {
+ logger.error(e, "Failed to convert byte array '%s' to string using '%s' converter.", Arrays.toString(this.name), this.nameConverter.getName());
+ return ColumnType.BinaryString.toString(this.name);
+ }
+ }
+
+ public byte[] getNameAsByteArray() {
return this.name;
}
public String getFullName() {
if (this.columnFamily != null) {
- return String.format("%s:%s", this.columnFamily, this.name);
+ return String.format("%s:%s", this.columnFamily, getName());
}
-
- return this.name;
+ return getName();
}
public String getFamily() {
@@ -81,11 +89,19 @@ public String getFamily() {
public ColumnFamily getColumnFamily() {
return this.columnFamily;
}
+
+ public TypeConverter getNameConverter() {
+ return nameConverter;
+ }
+
+ public void setNameConverter(TypeConverter nameConverter) {
+ this.nameConverter = nameConverter;
+ }
//endregion
//region Public Methods
public static boolean isKey(String name) {
- return KEY.name.equalsIgnoreCase(name);
+ return KEY.getName().equalsIgnoreCase(name);
}
public boolean isKey() {
diff --git a/src/main/java/hrider/data/ColumnType.java b/src/main/java/hrider/data/ColumnType.java
new file mode 100644
index 0000000..65cfab7
--- /dev/null
+++ b/src/main/java/hrider/data/ColumnType.java
@@ -0,0 +1,202 @@
+package hrider.data;
+
+import hrider.converters.ConvertersLoader;
+import hrider.converters.TypeConverter;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * Copyright (C) 2012 NICE Systems ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @author Igor Cher
+ * @version %I%, %G%
+ *
+ * This class represents a type of the data supported by the tool.
+ */
+public class ColumnType implements Serializable {
+
+ //region Constants
+ public final static ColumnType String = new ColumnType("String");
+ public final static ColumnType BinaryString = new ColumnType("BinaryString");
+ public final static ColumnType Boolean = new ColumnType("Boolean");
+ public final static ColumnType Integer = new ColumnType("Integer");
+ public final static ColumnType DateAsLong = new ColumnType("DateAsLong");
+ public final static ColumnType DateAsString = new ColumnType("DateAsString");
+ public final static ColumnType Double = new ColumnType("Double");
+ public final static ColumnType Float = new ColumnType("Float");
+ public final static ColumnType Long = new ColumnType("Long");
+ public final static ColumnType Short = new ColumnType("Short");
+ public final static ColumnType Json = new ColumnType("Json");
+ public final static ColumnType Xml = new ColumnType("Xml");
+
+ private static final long serialVersionUID = -5311385557088499851L;
+ //endregion
+
+ //region Variables
+ private TypeConverter converter;
+ //endregion
+
+ //region Constructor
+ public ColumnType(String name) {
+ this(ConvertersLoader.getConverter(name));
+ }
+
+ public ColumnType(TypeConverter converter) {
+ if (converter == null) {
+ throw new IllegalArgumentException("converter cannot be null");
+ }
+ this.converter = converter;
+ }
+ //endregion
+
+ //region Public Methods
+
+ /**
+ * Creates an instance of {@link ColumnType} from the name.
+ *
+ * @param name The name of the type to be created.
+ * @return A newly created {@link ColumnType}.
+ */
+ public static ColumnType fromName(String name) {
+ if (ConvertersLoader.exists(name)) {
+ return new ColumnType(name);
+ }
+ return null;
+ }
+
+ /**
+ * Creates an instance of {@link ColumnType} from the name. If there is no column type matching the name the default type is returned.
+ *
+ * @param name The name of the type to be created.
+ * @param defaultType The default type to be returned if the requested one does not exist.
+ * @return A newly created {@link ColumnType} if found or user provided default type otherwise.
+ */
+ public static ColumnType fromNameOrDefault(String name, ColumnType defaultType) {
+ if (ConvertersLoader.exists(name)) {
+ return new ColumnType(name);
+ }
+ return defaultType;
+ }
+
+ /**
+ * Gets all supported column types.
+ *
+ * @return A list of supported column types.
+ */
+ public static Collection getTypes() {
+ Collection types = new ArrayList();
+ for (TypeConverter converter : ConvertersLoader.getConverters()) {
+ types.add(new ColumnType(converter));
+ }
+ return types;
+ }
+
+ /**
+ * Gets column types that support column name conversions.
+ *
+ * @return A list of column types.
+ */
+ public static Collection getNameTypes() {
+ Collection types = new ArrayList();
+ for (TypeConverter converter : ConvertersLoader.getNameConverters()) {
+ types.add(new ColumnType(converter));
+ }
+ return types;
+ }
+
+ /**
+ * Gets the type of the column. The default value is {@link ColumnType#String} in case the column is not known.
+ *
+ * @param column The name of the column.
+ * @return The type of the column if found or a {@link ColumnType#String} as a default.
+ */
+ public static ColumnType fromColumn(String column) {
+ if (column.contains("timestamp")) {
+ return DateAsLong;
+ }
+ if ("key".equalsIgnoreCase(column)) {
+ return BinaryString;
+ }
+ return String;
+ }
+
+ /**
+ * Gets the name of the column type.
+ *
+ * @return The type's name.
+ */
+ public String getName() {
+ return converter.getName();
+ }
+
+ /**
+ * Gets a converter used to convert the values of the type.
+ *
+ * @return An instance of {@link TypeConverter}.
+ */
+ public TypeConverter getConverter() {
+ return converter;
+ }
+
+ /**
+ * Checks whether the column type can be edited.
+ *
+ * @return True if the column type can be edited or False otherwise.
+ */
+ public boolean isEditable() {
+ return converter.isEditable();
+ }
+
+ /**
+ * Converts an {@link Object} to a {@link String}.
+ *
+ * @param value An object to convert.
+ * @return A converted string.
+ */
+ public String toString(byte[] value) {
+ return converter.toString(value);
+ }
+
+ /**
+ * Converts a value represented as a {@link String} to an array of bytes according to the type.
+ *
+ * @param value The value to converters.
+ * @return A byte array representing the value or a null if something went wrong during conversion.
+ */
+ public byte[] toBytes(String value) {
+ return converter.toBytes(value);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof ColumnType) {
+ return this.converter.equals(((ColumnType)obj).converter);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return this.converter.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return this.converter.toString();
+ }
+ //endregion
+}
diff --git a/src/main/java/hrider/data/ConvertibleObject.java b/src/main/java/hrider/data/ConvertibleObject.java
new file mode 100644
index 0000000..a06313d
--- /dev/null
+++ b/src/main/java/hrider/data/ConvertibleObject.java
@@ -0,0 +1,174 @@
+package hrider.data;
+
+import java.io.Serializable;
+import java.util.Arrays;
+
+/**
+ * Copyright (C) 2012 NICE Systems ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @author Igor Cher
+ * @version %I%, %G%
+ *
+ * This class represents an object with type.
+ */
+public class ConvertibleObject implements Serializable {
+
+ //region Constants
+ private static final long serialVersionUID = 1701395986295913520L;
+ //endregion
+
+ //region Variables
+ /**
+ * The type of the {@link ConvertibleObject#value}.
+ */
+ private ColumnType type;
+ /**
+ * The original value.
+ */
+ private byte[] value;
+ //endregion
+
+ //region Constructor
+
+ /**
+ * Initializes a new instance of the {@link ConvertibleObject} class.
+ *
+ * @param type The type of the object.
+ * @param value The object.
+ */
+ public ConvertibleObject(ColumnType type, byte[] value) {
+ this.type = type;
+ this.value = value;
+ }
+ //endregion
+
+ //region Public Properties
+
+ /**
+ * Gets the value.
+ *
+ * @return A value.
+ */
+ public byte[] getValue() {
+ return this.value;
+ }
+
+ /**
+ * Sets a new value.
+ *
+ * @param value A new value.
+ */
+ public void setValue(byte[] value) {
+ this.value = value;
+ }
+
+ /**
+ * Sets a new value.
+ *
+ * @param value A new value.
+ */
+ public void setValueAsString(String value) {
+ this.value = this.type.toBytes(value);
+ }
+
+ /**
+ * Gets a byte[] value as a {@link String}.
+ *
+ * @return A string representing the original byte[] value.
+ */
+ public String getValueAsString() {
+ if (this.value != null) {
+ return this.type.toString(this.value);
+ }
+ return "";
+ }
+
+ /**
+ * Gets the type of the object.
+ *
+ * @return The object type.
+ */
+ public ColumnType getType() {
+ return this.type;
+ }
+
+ /**
+ * Sets a new object type.
+ *
+ * @param type A new object type.
+ */
+ public void setType(ColumnType type) {
+ this.type = type;
+ }
+
+ /**
+ * Tries to understand the type of the value based on value itself.
+ *
+ * @return A guessed type if successful or null.
+ */
+ public ColumnType guessType() {
+ if (type.equals(ColumnType.String)) {
+ String str = type.toString(this.value);
+
+ if (str.startsWith("{") && str.endsWith("}")) {
+ return ColumnType.Json;
+ }
+
+ if (str.startsWith("[") && str.endsWith("]")) {
+ return ColumnType.Json;
+ }
+
+ if (str.startsWith("<") && str.endsWith(">")) {
+ return ColumnType.Xml;
+ }
+ }
+ return null;
+ }
+ //endregion
+
+ //region Public Methods
+
+ /**
+ * Checks if the object has exactly the same value.
+ *
+ * @param value The value to check.
+ * @return True if the provided value equals to the object's value or False otherwise.
+ */
+ public boolean isEqual(String value) {
+ if (this.value != null) {
+ return this.type.toString(this.value).equals(value);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof ConvertibleObject) {
+ return Arrays.equals(((ConvertibleObject)obj).value, this.value);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return this.value != null ? Arrays.hashCode(this.value) : 0;
+ }
+
+ @Override
+ public String toString() {
+ return getValueAsString();
+ }
+ //endregion
+}
diff --git a/src/main/java/hrider/data/DataCell.java b/src/main/java/hrider/data/DataCell.java
index 7339512..4ed70c2 100644
--- a/src/main/java/hrider/data/DataCell.java
+++ b/src/main/java/hrider/data/DataCell.java
@@ -1,5 +1,9 @@
package hrider.data;
+import hrider.converters.TypeConverter;
+
+import java.io.Serializable;
+
/**
* Copyright (C) 2012 NICE Systems ltd.
*
@@ -20,21 +24,25 @@
*
* This class represents a cell in the grid or a key/value pair of the hbase row.
*/
-public class DataCell {
+public class DataCell implements Serializable {
+
+ //region Constants
+ private static final long serialVersionUID = 387452287094391735L;
+ //endregion
//region Variables
/**
* The row the current cell belongs to.
*/
- private DataRow row;
+ private DataRow row;
/**
* The column (an hbase qualifier) where the cell is located.
*/
- private ColumnQualifier column;
+ private ColumnQualifier column;
/**
* The value of the cell.
*/
- private TypedObject typedValue;
+ private ConvertibleObject convertibleValue;
//endregion
//region Constructor
@@ -42,14 +50,14 @@ public class DataCell {
/**
* Initializes a new instance of the {@link DataCell} with parameters.
*
- * @param row The owner of the cell.
- * @param column The name of the column/qualifier.
- * @param typedValue The value.
+ * @param row The owner of the cell.
+ * @param column The name of the column/qualifier.
+ * @param convertibleValue The value.
*/
- public DataCell(DataRow row, ColumnQualifier column, TypedObject typedValue) {
+ public DataCell(DataRow row, ColumnQualifier column, ConvertibleObject convertibleValue) {
this.row = row;
this.column = column;
- this.typedValue = typedValue;
+ this.convertibleValue = convertibleValue;
}
//endregion
@@ -92,21 +100,57 @@ public void setColumn(ColumnQualifier column) {
}
/**
- * Gets a cell value.
+ * Gets cell's value.
+ *
+ * @return A cell's value represented as a string.
+ */
+ public String getValue() {
+ return this.convertibleValue.getValueAsString();
+ }
+
+ /**
+ * Sets a new value for the cell.
+ *
+ * @param value A new value to set.
+ */
+ public void setValue(String value) {
+ this.convertibleValue.setValueAsString(value);
+ }
+
+ /**
+ * Gets cell's value as byte array.
*
- * @return A reference to the {@link TypedObject} that holds the value.
+ * @return A cell's value represented as a byte array.
*/
- public TypedObject getTypedValue() {
- return this.typedValue;
+ public byte[] getValueAsByteArray() {
+ return this.convertibleValue.getValue();
}
/**
- * Sets a new cell value.
+ * Gets cell's type.
*
- * @param typedValue A new cell value.
+ * @return The cell's type.
*/
- public void setTypedValue(TypedObject typedValue) {
- this.typedValue = typedValue;
+ public ColumnType getType() {
+ return this.convertibleValue.getType();
+ }
+
+ /**
+ * Sets a new object type for the cell.
+ *
+ * @param type A new object type.
+ */
+ public void setType(ColumnType type) {
+ this.convertibleValue.setType(type);
+ }
+
+ /**
+ * Sets new converter for column name.
+ *
+ * @param converter A new converter to set.
+ */
+ public void setColumnNameConverter(TypeConverter converter) {
+ this.column.setNameConverter(converter);
}
/**
@@ -115,11 +159,8 @@ public void setTypedValue(TypedObject typedValue) {
* @param value The value to check.
* @return True if the cell contains the value or False otherwise.
*/
- public boolean contains(Object value) {
- if (this.typedValue.getValue() != null) {
- return this.typedValue.getValue().equals(value);
- }
- return false;
+ public boolean hasValue(String value) {
+ return convertibleValue.isEqual(value);
}
/**
@@ -131,30 +172,20 @@ public boolean isKey() {
return this.column.isKey();
}
- /**
- * Converts a data represented as {@link String} to an actual type.
- *
- * @param data The data to convert.
- * @return A converted data.
- */
- public Object toObject(String data) {
- return this.typedValue.getType().toObject(data);
- }
-
/**
* Tries to understand the type of the value based on value itself.
*
* @return A guessed type if successful or null.
*/
- public ObjectType guessType() {
- return this.typedValue.guessType();
+ public ColumnType guessType() {
+ return this.convertibleValue.guessType();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof DataCell) {
DataCell cell = (DataCell)obj;
- return cell.typedValue.equals(this.typedValue) &&
+ return cell.convertibleValue.equals(this.convertibleValue) &&
cell.row.equals(this.row) &&
cell.column.equals(this.column);
}
@@ -163,12 +194,12 @@ public boolean equals(Object obj) {
@Override
public int hashCode() {
- return this.typedValue.hashCode();
+ return this.convertibleValue.hashCode();
}
@Override
public String toString() {
- return this.typedValue.toString();
+ return this.convertibleValue.toString();
}
//endregion
}
diff --git a/src/main/java/hrider/data/DataRow.java b/src/main/java/hrider/data/DataRow.java
index c76dd35..9c13935 100644
--- a/src/main/java/hrider/data/DataRow.java
+++ b/src/main/java/hrider/data/DataRow.java
@@ -1,5 +1,8 @@
package hrider.data;
+import hrider.converters.TypeConverter;
+
+import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
@@ -23,13 +26,17 @@
*
* This class represents a row in the grid or an hbase row.
*/
-public class DataRow {
+public class DataRow implements Serializable {
+
+ //region Constants
+ private static final long serialVersionUID = 5158560383309065143L;
+ //endregion
//region Variables
/**
* The row key.
*/
- private TypedObject key;
+ private ConvertibleObject key;
/**
* The list of cells that belong to the row.
*/
@@ -50,7 +57,7 @@ public DataRow() {
*
* @param key The row key.
*/
- public DataRow(TypedObject key) {
+ public DataRow(ConvertibleObject key) {
this();
this.key = key;
}
@@ -63,7 +70,7 @@ public DataRow(TypedObject key) {
*
* @return The key of the row.
*/
- public TypedObject getKey() {
+ public ConvertibleObject getKey() {
return this.key;
}
@@ -72,7 +79,7 @@ public TypedObject getKey() {
*
* @param key The new key to set.
*/
- public void setKey(TypedObject key) {
+ public void setKey(ConvertibleObject key) {
this.key = key;
}
@@ -88,6 +95,7 @@ public DataCell getCell(String columnName) {
/**
* Gets the cell according to the specified column qualifier.
+ *
* @param columnQualifier The column qualifier to look for the cell.
* @return The instance of the {@link DataCell} if found or null otherwise.
*/
@@ -147,11 +155,26 @@ public Iterable getCells() {
* @param columnName The name of the column which type should be updated.
* @param columnType The new column type.
*/
- public void updateColumnType(String columnName, ObjectType columnType) {
+ public void updateColumnType(String columnName, ColumnType columnType) {
DataCell cell = getCell(columnName);
- if (cell != null && cell.getTypedValue() != null) {
- cell.getTypedValue().setType(columnType);
+ if (cell != null) {
+ cell.setType(columnType);
+ }
+ }
+
+ /**
+ * Updates converter for the column name.
+ *
+ * @param converter The new column name converter.
+ */
+ public void updateColumnNameConverter(TypeConverter converter) {
+ Map map = new HashMap();
+ for (DataCell cell : this.cells.values()) {
+ cell.setColumnNameConverter(converter);
+
+ map.put(cell.getColumn().getFullName(), cell);
}
+ this.cells = map;
}
/**
@@ -162,7 +185,7 @@ public void updateColumnType(String columnName, ObjectType columnType) {
public Map toMap() {
Map values = new HashMap();
for (Map.Entry entry : this.cells.entrySet()) {
- values.put(entry.getKey(), entry.getValue().getTypedValue().getValue());
+ values.put(entry.getKey(), entry.getValue().getValue());
}
return values;
}
diff --git a/src/main/java/hrider/data/ObjectType.java b/src/main/java/hrider/data/ObjectType.java
deleted file mode 100644
index 7dd86e0..0000000
--- a/src/main/java/hrider/data/ObjectType.java
+++ /dev/null
@@ -1,251 +0,0 @@
-package hrider.data;
-
-import hrider.config.GlobalConfig;
-import org.apache.hadoop.hbase.util.Bytes;
-
-import java.text.DateFormat;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Locale;
-
-/**
- * Copyright (C) 2012 NICE Systems ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * @author Igor Cher
- * @version %I%, %G%
- *
- * This enum represents a type of the data supported by the tool.
- */
-@SuppressWarnings("UnnecessaryDefault")
-public enum ObjectType {
-
- String,
- BinaryString,
- Integer,
- Long,
- Float,
- Double,
- Boolean,
- Short,
- DateAsString,
- DateAsLong,
- Xml,
- Json;
-
- //region Constants
- private static final byte[] EMPTY_BYTES_ARRAY = new byte[0];
- //endregion
-
- //region Public Methods
-
- /**
- * Gets the type of the column. The default value is {@link ObjectType#String} in case the column is not known.
- *
- * @param column The name of the column.
- * @return The type of the column if found or a {@link ObjectType#String} as a default.
- */
- public static ObjectType fromColumn(String column) {
- if (column.toLowerCase().endsWith("timestamp")) {
- return DateAsLong;
- }
- return String;
- }
-
- /**
- * Converts a value represented as a {@link String} to an object according to the type.
- *
- * @param value The value to convert.
- * @return An object representing the value or a null if something went wrong during conversion.
- */
- public Object toObject(String value) {
- if (value == null) {
- return null;
- }
-
- switch (this) {
- case String:
- case BinaryString:
- return value;
- case Integer:
- return java.lang.Integer.parseInt(value);
- case Long:
- return java.lang.Long.parseLong(value);
- case Float:
- return java.lang.Float.parseFloat(value);
- case Double:
- return java.lang.Double.parseDouble(value);
- case Boolean:
- return java.lang.Boolean.parseBoolean(value);
- case Short:
- return java.lang.Short.parseShort(value);
- case DateAsLong:
- case DateAsString:
- DateFormat df = new SimpleDateFormat(GlobalConfig.instance().getDateFormat(), Locale.ENGLISH);
- try {
- return df.parse(value);
- }
- catch (ParseException ignored) {
- return null;
- }
- case Xml:
- case Json:
- return value;
- default:
- return value;
- }
- }
-
- /**
- * Converts a value represented as a {@link String} to an array of bytes according to the type.
- *
- * @param value The value to convert.
- * @return A byte array representing the value or a null if something went wrong during conversion.
- */
- public byte[] fromString(String value) {
- if (value == null) {
- return EMPTY_BYTES_ARRAY;
- }
-
- switch (this) {
- case String:
- return Bytes.toBytes(value);
- case BinaryString:
- return Bytes.toBytesBinary(value);
- case Integer:
- return Bytes.toBytes(java.lang.Integer.parseInt(value));
- case Long:
- return Bytes.toBytes(java.lang.Long.parseLong(value));
- case Float:
- return Bytes.toBytes(java.lang.Float.parseFloat(value));
- case Double:
- return Bytes.toBytes(java.lang.Double.parseDouble(value));
- case Boolean:
- return Bytes.toBytes(java.lang.Boolean.parseBoolean(value));
- case Short:
- return Bytes.toBytes(java.lang.Short.parseShort(value));
- case DateAsString:
- return Bytes.toBytes(value);
- case DateAsLong:
- return Bytes.toBytes(((Date)toObject(value)).getTime());
- case Xml:
- case Json:
- return Bytes.toBytes(value);
- default:
- return Bytes.toBytes(value);
- }
- }
-
- /**
- * Converts a value represented as a {@link byte[]} to an object according to the type.
- *
- * @param value The value to convert.
- * @return An object representing the value or a null if something went wrong during conversion.
- */
- public Object fromByteArray(byte[] value) {
- if (value == null) {
- return null;
- }
-
- switch (this) {
- case String:
- return Bytes.toString(value);
- case BinaryString:
- return Bytes.toStringBinary(value);
- case Integer:
- return Bytes.toInt(value);
- case Long:
- return Bytes.toLong(value);
- case Float:
- return Bytes.toFloat(value);
- case Double:
- return Bytes.toDouble(value);
- case Boolean:
- return Bytes.toBoolean(value);
- case Short:
- return Bytes.toShort(value);
- case DateAsString:
- DateFormat df = new SimpleDateFormat(GlobalConfig.instance().getDateFormat(), Locale.ENGLISH);
- try {
- return df.parse(Bytes.toString(value));
- }
- catch (ParseException ignored) {
- return null;
- }
- case DateAsLong:
- return new Date(Bytes.toLong(value));
- case Xml:
- case Json:
- return Bytes.toString(value);
- default:
- return Bytes.toString(value);
- }
- }
-
- /**
- * Converts a value represented as an {@link Object} to an array of bytes according to the type.
- *
- * @param value The value to convert.
- * @return A byte array representing the value or a null if something went wrong during conversion.
- */
- public byte[] fromObject(Object value) {
- if (value == null) {
- return EMPTY_BYTES_ARRAY;
- }
-
- switch (this) {
- case String:
- return Bytes.toBytes((String)value);
- case BinaryString:
- return Bytes.toBytesBinary((String)value);
- case Integer:
- return Bytes.toBytes((java.lang.Integer)value);
- case Long:
- return Bytes.toBytes((java.lang.Long)value);
- case Float:
- return Bytes.toBytes((java.lang.Float)value);
- case Double:
- return Bytes.toBytes((java.lang.Double)value);
- case Boolean:
- return Bytes.toBytes((java.lang.Boolean)value);
- case Short:
- return Bytes.toBytes((java.lang.Short)value);
- case DateAsString:
- if (value instanceof Date) {
- DateFormat df = new SimpleDateFormat(GlobalConfig.instance().getDateFormat(), Locale.ENGLISH);
- return Bytes.toBytes(df.format((Date)value));
- }
- else {
- return Bytes.toBytes((String)value);
- }
- case DateAsLong:
- if (value instanceof Date) {
- return Bytes.toBytes(((Date)value).getTime());
- }
- else if (value instanceof java.lang.Long) {
- return Bytes.toBytes((java.lang.Long)value);
- }
- else {
- return Bytes.toBytes(((Date)toObject((String)value)).getTime());
- }
- case Xml:
- case Json:
- return Bytes.toBytes((String)value);
- default:
- return Bytes.toBytes((String)value);
- }
- }
- //endregion
-}
diff --git a/src/main/java/hrider/data/TypedColumn.java b/src/main/java/hrider/data/TypedColumn.java
index bc1f61b..da75925 100644
--- a/src/main/java/hrider/data/TypedColumn.java
+++ b/src/main/java/hrider/data/TypedColumn.java
@@ -18,7 +18,7 @@
* @author Igor Cher
* @version %I%, %G%
*
- * The class represents a column with type. The type information is used to convert the value represented by an array of bytes to the actual object.
+ * The class represents a column with type. The type information is used to converters the value represented by an array of bytes to the actual object.
*/
public class TypedColumn {
@@ -30,7 +30,7 @@ public class TypedColumn {
/**
* The type of the column.
*/
- private ObjectType type;
+ private ColumnType type;
//endregion
//region Constructor
@@ -41,7 +41,7 @@ public class TypedColumn {
* @param column The qualifier of the column.
* @param columnType The type of the column.
*/
- public TypedColumn(ColumnQualifier column, ObjectType columnType) {
+ public TypedColumn(ColumnQualifier column, ColumnType columnType) {
this.column = column;
this.type = columnType;
}
@@ -72,7 +72,7 @@ public void setColumn(ColumnQualifier value) {
*
* @return The type of the column.
*/
- public ObjectType getType() {
+ public ColumnType getType() {
return this.type;
}
@@ -81,7 +81,7 @@ public ObjectType getType() {
*
* @param value A new column type.
*/
- public void setType(ObjectType value) {
+ public void setType(ColumnType value) {
this.type = value;
}
//endregion
diff --git a/src/main/java/hrider/data/TypedObject.java b/src/main/java/hrider/data/TypedObject.java
deleted file mode 100644
index 50a2b8c..0000000
--- a/src/main/java/hrider/data/TypedObject.java
+++ /dev/null
@@ -1,179 +0,0 @@
-package hrider.data;
-
-import hrider.config.GlobalConfig;
-
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Locale;
-
-/**
- * Copyright (C) 2012 NICE Systems ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * @author Igor Cher
- * @version %I%, %G%
- *
- * This class represents an object with type.
- */
-public class TypedObject {
-
- //region Variables
- /**
- * An actual value.
- */
- private Object value;
- /**
- * The type of the {@link TypedObject#value}.
- */
- private ObjectType type;
- /**
- * The original value.
- */
- private byte[] orgValue;
- //endregion
-
- //region Constructor
-
- /**
- * Initializes a new instance of the {@link TypedObject} class.
- *
- * @param objType The type of the object.
- * @param objValue The object.
- */
- public TypedObject(ObjectType objType, Object objValue) {
- this.type = objType;
-
- if (this.type == null) {
- this.type = ObjectType.String;
- }
-
- if (objValue instanceof byte[]) {
- this.orgValue = (byte[])objValue;
- this.value = this.type.fromByteArray((byte[])objValue);
- }
- else {
- this.orgValue = this.type.fromObject(objValue);
- this.value = objValue;
- }
- }
- //endregion
-
- //region Public Properties
-
- /**
- * Gets the value.
- *
- * @return A value.
- */
- public Object getValue() {
- return this.value;
- }
-
- /**
- * Sets a new value.
- *
- * @param obj A new value.
- */
- public void setValue(Object obj) {
- this.value = obj;
- }
-
- /**
- * Gets the type of the object.
- *
- * @return The object type.
- */
- public ObjectType getType() {
- return this.type;
- }
-
- /**
- * Sets a new object type.
- *
- * @param objType A new object type.
- */
- public void setType(ObjectType objType) {
- if (this.type != null) {
- Object objValue = objType.fromByteArray(this.orgValue);
- if (objValue != null) {
- this.value = objValue;
- this.type = objType;
- }
- }
- }
-
- /**
- * Tries to understand the type of the value based on value itself.
- *
- * @return A guessed type if successful or null.
- */
- public ObjectType guessType() {
- if (type == ObjectType.String) {
- String str = ((String)value).trim();
-
- if (str.startsWith("{") && str.endsWith("}")) {
- return ObjectType.Json;
- }
-
- if (str.startsWith("[") && str.endsWith("]")) {
- return ObjectType.Json;
- }
-
- if (str.startsWith("<") && str.endsWith(">")) {
- return ObjectType.Xml;
- }
- }
- return null;
- }
- //endregion
-
- //region Public Methods
-
- /**
- * Converts an object to a byte array according ot its type.
- *
- * @return A byte array.
- */
- public byte[] toByteArray() {
- return this.type.fromObject(this.value);
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj instanceof TypedObject) {
- TypedObject typedObject = (TypedObject)obj;
- return typedObject.value != null ? typedObject.value.equals(this.value) : this.value == null;
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- return this.value != null ? this.value.hashCode() : 0;
- }
-
- @Override
- public String toString() {
- if (this.value != null) {
- if (this.type == ObjectType.DateAsString || this.type == ObjectType.DateAsLong) {
- DateFormat df = new SimpleDateFormat(GlobalConfig.instance().getDateFormat(), Locale.ENGLISH);
- return df.format((Date)this.value);
- }
- return this.value.toString();
- }
- return "";
- }
- //endregion
-}
diff --git a/src/main/java/hrider/export/FileExporter.java b/src/main/java/hrider/export/FileExporter.java
index 7955833..d387913 100644
--- a/src/main/java/hrider/export/FileExporter.java
+++ b/src/main/java/hrider/export/FileExporter.java
@@ -85,7 +85,7 @@ public void write(DataRow row, Iterable columns) throws IOExcep
for (ColumnQualifier column : columns) {
DataCell cell = row.getCell(column);
if (cell != null) {
- formatter.append(cell.getTypedValue().getValue());
+ formatter.append(cell.getValue());
}
else {
formatter.append("");
diff --git a/src/main/java/hrider/hbase/Connection.java b/src/main/java/hrider/hbase/Connection.java
index 1e1bd3c..efaa583 100644
--- a/src/main/java/hrider/hbase/Connection.java
+++ b/src/main/java/hrider/hbase/Connection.java
@@ -182,6 +182,17 @@ public boolean tableExists(String tableName) throws IOException {
return tableName != null && this.hbaseAdmin.tableExists(tableName);
}
+ /**
+ * Checks whether the specified table is enabled.
+ *
+ * @param tableName The name of the table to check.
+ * @return True if the table is enabled or False otherwise.
+ * @throws IOException Error accessing hbase.
+ */
+ public boolean tableEnabled(String tableName) throws IOException {
+ return tableName != null && this.hbaseAdmin.isTableEnabled(tableName);
+ }
+
/**
* Creates a new table or modifies an existing one in the hbase cluster.
*
@@ -280,39 +291,44 @@ public void copyTable(TableDescriptor targetTable, TableDescriptor sourceTable,
scan.setCaching(GlobalConfig.instance().getBatchSizeForRead());
ResultScanner scanner = source.getScanner(scan);
- List puts = new ArrayList();
+ try {
+ List puts = new ArrayList();
- int batchSize = GlobalConfig.instance().getBatchSizeForWrite();
+ int batchSize = GlobalConfig.instance().getBatchSizeForWrite();
- boolean isValid;
+ boolean isValid;
- do {
- Result result = scanner.next();
+ do {
+ Result result = scanner.next();
- isValid = result != null;
- if (isValid) {
- Put put = new Put(result.getRow());
- for (KeyValue kv : result.list()) {
- put.add(kv);
- }
+ isValid = result != null;
+ if (isValid) {
+ Put put = new Put(result.getRow());
+ for (KeyValue kv : result.list()) {
+ put.add(kv);
+ }
- puts.add(put);
+ puts.add(put);
- if (puts.size() == batchSize) {
- target.put(puts);
- puts.clear();
- }
+ if (puts.size() == batchSize) {
+ target.put(puts);
+ puts.clear();
+ }
- for (HbaseActionListener listener : this.listeners) {
- listener.copyOperation(sourceCluster.serverName, sourceTable.getName(), this.serverName, targetTable.getName(), result);
+ for (HbaseActionListener listener : this.listeners) {
+ listener.copyOperation(sourceCluster.serverName, sourceTable.getName(), this.serverName, targetTable.getName(), result);
+ }
}
}
- }
- while (isValid);
+ while (isValid);
- // add the last puts to the table.
- if (!puts.isEmpty()) {
- target.put(puts);
+ // add the last puts to the table.
+ if (!puts.isEmpty()) {
+ target.put(puts);
+ }
+ }
+ finally {
+ scanner.close();
}
}
@@ -333,11 +349,13 @@ public void saveTable(String tableName, String path) throws IOException {
StoreFile.Writer writer = new StoreFile.WriterBuilder(
this.getConfiguration(), new CacheConfig(cacheConfig), fs, HFile.DEFAULT_BLOCKSIZE).withFilePath(new Path(path)).build();
+ ResultScanner scanner = null;
+
try {
Scan scan = new Scan();
scan.setCaching(GlobalConfig.instance().getBatchSizeForRead());
- ResultScanner scanner = table.getScanner(scan);
+ scanner = table.getScanner(scan);
boolean isValid;
do {
@@ -357,6 +375,10 @@ public void saveTable(String tableName, String path) throws IOException {
while (isValid);
}
finally {
+ if (scanner != null) {
+ scanner.close();
+ }
+
writer.close();
}
}
@@ -372,6 +394,16 @@ public void flushTable(String tableName) throws IOException, InterruptedExceptio
this.hbaseAdmin.flush(tableName);
}
+ /**
+ * Enables the table.
+ *
+ * @param tableName The name of the table to enable.
+ * @throws IOException Error accessing hbase.
+ */
+ public void enableTable(String tableName) throws IOException {
+ this.hbaseAdmin.enableTable(tableName);
+ }
+
/**
* Loads a locally saved HFile to an existing table.
*
@@ -435,7 +467,7 @@ public void loadTable(String tableName, String path) throws IOException, TableNo
familiesToCreate.clear();
}
- table.put(puts);
+ HTableUtil.bucketRsPut(table, puts);
puts.clear();
}
@@ -458,7 +490,7 @@ public void loadTable(String tableName, String path) throws IOException, TableNo
createFamilies(tableName, toDescriptors(familiesToCreate));
}
- table.put(puts);
+ HTableUtil.bucketRsPut(table, puts);
}
}
finally {
@@ -485,7 +517,7 @@ public void setRows(String tableName, Iterable rows) throws IOException
List puts = new ArrayList();
for (DataRow row : rows) {
- Put put = new Put(row.getKey().toByteArray());
+ Put put = new Put(row.getKey().getValue());
for (DataCell cell : row.getCells()) {
if (!cell.isKey()) {
@@ -495,7 +527,7 @@ public void setRows(String tableName, Iterable rows) throws IOException
byte[] family = Bytes.toBytesBinary(cell.getColumn().getFamily());
byte[] column = Bytes.toBytesBinary(cell.getColumn().getName());
- byte[] value = cell.getTypedValue().toByteArray();
+ byte[] value = cell.getValueAsByteArray();
put.add(family, column, value);
}
@@ -513,7 +545,7 @@ public void setRows(String tableName, Iterable rows) throws IOException
}
HTable table = this.factory.get(tableName);
- table.put(puts);
+ HTableUtil.bucketRsPut(table, puts);
}
/**
@@ -533,7 +565,7 @@ public void setRow(String tableName, DataRow row) throws IOException, TableNotFo
Collection familiesToCreate = new HashSet();
- Put put = new Put(row.getKey().toByteArray());
+ Put put = new Put(row.getKey().getValue());
for (DataCell cell : row.getCells()) {
if (!cell.isKey()) {
if (!families.contains(cell.getColumn().getColumnFamily())) {
@@ -542,7 +574,7 @@ public void setRow(String tableName, DataRow row) throws IOException, TableNotFo
byte[] family = Bytes.toBytesBinary(cell.getColumn().getFamily());
byte[] column = Bytes.toBytesBinary(cell.getColumn().getName());
- byte[] value = cell.getTypedValue().toByteArray();
+ byte[] value = cell.getValueAsByteArray();
put.add(family, column, value);
}
@@ -569,7 +601,7 @@ public void setRow(String tableName, DataRow row) throws IOException, TableNotFo
*/
public void deleteRow(String tableName, DataRow row) throws IOException {
HTable table = this.factory.get(tableName);
- table.delete(new Delete(row.getKey().toByteArray()));
+ table.delete(new Delete(row.getKey().getValue()));
for (HbaseActionListener listener : this.listeners) {
listener.rowOperation(tableName, row, "removed");
@@ -658,7 +690,7 @@ private void createFamilies(String tableName, Iterable famili
/**
* Converts column family to column descriptor.
*
- * @param families A list of column families to convert.
+ * @param families A list of column families to converters.
* @return A list of column descriptors.
*/
private static Iterable toDescriptors(Iterable families) {
diff --git a/src/main/java/hrider/hbase/Query.java b/src/main/java/hrider/hbase/Query.java
index f7562a2..1e9b18d 100644
--- a/src/main/java/hrider/hbase/Query.java
+++ b/src/main/java/hrider/hbase/Query.java
@@ -1,6 +1,6 @@
package hrider.hbase;
-import hrider.data.ObjectType;
+import hrider.data.ColumnType;
import java.util.Date;
@@ -35,7 +35,7 @@ public class Query {
* The type of the start key. The key must be converted to byte array before it can be used in scanner. In order for the scan to match
* the key it must be converted to the byte array according to its actual type otherwise we will have different byte arrays which will never match.
*/
- private ObjectType startKeyType;
+ private ColumnType startKeyType;
/**
* A key where the can should stop.
*/
@@ -43,7 +43,7 @@ public class Query {
/**
* The type of the end key.
*/
- private ObjectType endKeyType;
+ private ColumnType endKeyType;
/**
* The date to start the scan from. Each key/value pair in hbase has a timestamp.
*/
@@ -75,7 +75,7 @@ public class Query {
/**
* The type of the word.
*/
- private ObjectType wordType;
+ private ColumnType wordType;
//endregion
//region Public Properties
@@ -96,7 +96,7 @@ public byte[] getStartKey() {
*/
public String getStartKeyAsString() {
if (this.startKeyType != null) {
- return this.startKeyType.fromByteArray(this.startKey).toString();
+ return this.startKeyType.toString(this.startKey);
}
return null;
}
@@ -116,9 +116,9 @@ public void setStartKey(byte[] value) {
* @param keyType The type of the key to set.
* @param key The key to set.
*/
- public void setStartKey(ObjectType keyType, String key) {
+ public void setStartKey(ColumnType keyType, String key) {
this.startKeyType = keyType;
- this.startKey = keyType.fromString(key);
+ this.startKey = keyType.toBytes(key);
}
/**
@@ -126,7 +126,7 @@ public void setStartKey(ObjectType keyType, String key) {
*
* @return The start key type.
*/
- public ObjectType getStartKeyType() {
+ public ColumnType getStartKeyType() {
return this.startKeyType;
}
@@ -135,7 +135,7 @@ public ObjectType getStartKeyType() {
*
* @param keyType A new start key type.
*/
- public void setStartKeyType(ObjectType keyType) {
+ public void setStartKeyType(ColumnType keyType) {
this.startKeyType = keyType;
}
@@ -155,7 +155,7 @@ public byte[] getEndKey() {
*/
public String getEndKeyAsString() {
if (this.endKeyType != null) {
- return this.endKeyType.fromByteArray(this.endKey).toString();
+ return this.endKeyType.toString(this.endKey);
}
return null;
}
@@ -175,9 +175,9 @@ public void setEndKey(byte[] value) {
* @param keyType The key type.
* @param key The key.
*/
- public void setEndKey(ObjectType keyType, String key) {
+ public void setEndKey(ColumnType keyType, String key) {
this.endKeyType = keyType;
- this.endKey = keyType.fromString(key);
+ this.endKey = keyType.toBytes(key);
}
/**
@@ -185,7 +185,7 @@ public void setEndKey(ObjectType keyType, String key) {
*
* @return The end key type.
*/
- public ObjectType getEndKeyType() {
+ public ColumnType getEndKeyType() {
return this.endKeyType;
}
@@ -194,7 +194,7 @@ public ObjectType getEndKeyType() {
*
* @param keyType A new key type.
*/
- public void setEndKeyType(ObjectType keyType) {
+ public void setEndKeyType(ColumnType keyType) {
this.endKeyType = keyType;
}
@@ -315,7 +315,7 @@ public String getWord() {
* @return A byte array.
*/
public byte[] getWordAsByteArray() {
- return this.wordType.fromString(this.word);
+ return this.wordType.toBytes(this.word);
}
/**
@@ -337,7 +337,7 @@ public void setWord(String value) {
*
* @return A type of the word.
*/
- public ObjectType getWordType() {
+ public ColumnType getWordType() {
return this.wordType;
}
@@ -346,7 +346,7 @@ public ObjectType getWordType() {
*
* @param wordType A new word type.
*/
- public void setWordType(ObjectType wordType) {
+ public void setWordType(ColumnType wordType) {
this.wordType = wordType;
}
//endregion
diff --git a/src/main/java/hrider/hbase/Scanner.java b/src/main/java/hrider/hbase/Scanner.java
index dbd73c1..1c63f7f 100644
--- a/src/main/java/hrider/hbase/Scanner.java
+++ b/src/main/java/hrider/hbase/Scanner.java
@@ -1,8 +1,10 @@
package hrider.hbase;
import hrider.config.GlobalConfig;
+import hrider.converters.TypeConverter;
import hrider.data.*;
import hrider.ui.MessageHandler;
+import org.apache.commons.lang.time.StopWatch;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.HTableDescriptor;
@@ -10,7 +12,7 @@
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.client.ResultScanner;
import org.apache.hadoop.hbase.client.Scan;
-import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.hadoop.hbase.filter.FirstKeyOnlyFilter;
import java.io.IOException;
import java.util.*;
@@ -51,6 +53,10 @@ public class Scanner {
* This operation is time consuming and its result should be cached.
*/
private long rowsCount;
+ /**
+ * Holds a rows number in the table if the calculation has not been completed because of the timeout.
+ */
+ private long partialRowsCount;
/**
* The number of the last loaded row.
*/
@@ -58,7 +64,7 @@ public class Scanner {
/**
* The map of column types. The key is the name of the column and the value is the type of the objects within the column.
*/
- private Map columnTypes;
+ private Map columnTypes;
/**
* A list of markers. The marker is used for pagination to mark where the previous scan has stopped.
*/
@@ -71,6 +77,10 @@ public class Scanner {
* Indicates if this scanner should support only forward navigation.
*/
private boolean forwardNavigateOnly;
+ /**
+ * Represents a converter for column names.
+ */
+ private TypeConverter columnNameConverter;
//endregion
//region Constructor
@@ -137,6 +147,15 @@ public String getTableName() {
return this.tableName;
}
+ /**
+ * Indicates that the rows count has been partially calculated.
+ *
+ * @return True if the rows count has been stopped before reaching end of the table or False otherwise.
+ */
+ public boolean isRowsCountPartiallyCalculated() {
+ return this.partialRowsCount > 0;
+ }
+
/**
* Gets a list of columns. If there is no columns at this moment they will be loaded according to the provided rows number. In other words only columns
* of loaded rows will be returned.
@@ -164,7 +183,7 @@ public Collection getColumns(int rowsNumber) {
*
* @return A map of column types.
*/
- public Map getColumnTypes() {
+ public Map getColumnTypes() {
return this.columnTypes;
}
@@ -173,7 +192,7 @@ public Map getColumnTypes() {
*
* @param columnTypes A new map of column types.
*/
- public void setColumnTypes(Map columnTypes) {
+ public void setColumnTypes(Map columnTypes) {
this.columnTypes = columnTypes;
}
@@ -213,7 +232,7 @@ public boolean hasPrev() {
* @param columnName The name of the column.
* @param columnType The new column type.
*/
- public void updateColumnType(String columnName, ObjectType columnType) {
+ public void updateColumnType(String columnName, ColumnType columnType) {
Collection rows = this.current;
if (rows != null) {
for (DataRow row : rows) {
@@ -222,12 +241,28 @@ public void updateColumnType(String columnName, ObjectType columnType) {
}
}
+ /**
+ * Updates converter for the column name.
+ *
+ * @param converter The new column name converter.
+ */
+ public void updateColumnNameConverter(TypeConverter converter) {
+ this.columnNameConverter = converter;
+
+ Collection rows = this.current;
+ if (rows != null) {
+ for (DataRow row : rows) {
+ row.updateColumnNameConverter(converter);
+ }
+ }
+ }
+
/**
* Resets the cache.
*
* @param startKey The key the scan should start from. This parameter can be null.
*/
- public void resetCurrent(TypedObject startKey) {
+ public void resetCurrent(ConvertibleObject startKey) {
this.current = null;
this.rowsCount = 0;
this.lastRow = 0;
@@ -250,17 +285,22 @@ public DataRow getFirstRow() throws IOException {
HTable table = this.connection.getTableFactory().get(this.tableName);
ResultScanner scanner = table.getScanner(scan);
- Collection rows = new ArrayList();
- Collection columns = new ArrayList();
+ try {
+ Collection rows = new ArrayList();
+ Collection columns = new ArrayList();
- columns.add(ColumnQualifier.KEY);
+ columns.add(ColumnQualifier.KEY);
- loadRows(scanner, 0, 1, rows, columns);
- if (rows.isEmpty()) {
- return null;
- }
+ loadRows(scanner, 0, 1, rows, columns);
+ if (rows.isEmpty()) {
+ return null;
+ }
- return rows.iterator().next();
+ return rows.iterator().next();
+ }
+ finally {
+ scanner.close();
+ }
}
/**
@@ -336,6 +376,12 @@ public Collection prev() throws IOException {
this.lastRow -= this.current.size();
this.current = peekMarker().rows;
+
+ updateColumnNameConverter(columnNameConverter);
+
+ for (Map.Entry entry : this.columnTypes.entrySet()) {
+ updateColumnType(entry.getKey(), entry.getValue());
+ }
}
return this.current;
}
@@ -347,22 +393,40 @@ public Collection prev() throws IOException {
* @return A total number of rows in the table.
* @throws IOException Error accessing hbase.
*/
- public synchronized long getRowsCount() throws IOException {
+ public long getRowsCount(long timeout) throws IOException {
if (this.rowsCount == 0) {
+ this.partialRowsCount = 0;
+
Scan scan = getScanner();
+ scan.setFilter(new FirstKeyOnlyFilter());
scan.setCaching(GlobalConfig.instance().getBatchSizeForRead());
HTable table = this.connection.getTableFactory().get(this.tableName);
ResultScanner scanner = table.getScanner(scan);
- int count = 0;
- for (Result rr = scanner.next() ; rr != null ; rr = scanner.next()) {
- if (isValidRow(rr)) {
- ++count;
+ StopWatch stopWatch = new StopWatch();
+ stopWatch.start();
+
+ try {
+ int count = 0;
+ for (Result rr = scanner.next() ; rr != null ; rr = scanner.next()) {
+ if (isValidRow(rr)) {
+ ++count;
+ }
+
+ if (stopWatch.getTime() > timeout) {
+ this.partialRowsCount = count;
+
+ break;
+ }
}
- }
- this.rowsCount = count;
+ this.rowsCount = count;
+ }
+ finally {
+ stopWatch.stop();
+ scanner.close();
+ }
}
return this.rowsCount;
}
@@ -404,13 +468,15 @@ protected boolean isValidRow(Result row) {
* @return A key of the last loaded row. Used to mark the current position for the next scan.
* @throws IOException Error accessing hbase.
*/
- protected TypedObject loadRows(ResultScanner scanner, long offset, int rowsNumber, Collection rows, Collection columns) throws
- IOException {
- ObjectType keyType = this.columnTypes.get(ColumnQualifier.KEY.getName());
+ protected ConvertibleObject loadRows(
+ ResultScanner scanner, long offset, int rowsNumber, Collection rows, Collection columns) throws IOException {
+ ColumnType keyType = this.columnTypes.get(ColumnQualifier.KEY.getName());
int index = 0;
boolean isValid;
- TypedObject key = null;
+ ConvertibleObject key = null;
+
+ TypeConverter nameConverter = getColumnNameConverterInternal();
HTable table = this.connection.getTableFactory().get(this.tableName);
HTableDescriptor tableDescriptor = table.getTableDescriptor();
@@ -421,7 +487,7 @@ protected TypedObject loadRows(ResultScanner scanner, long offset, int rowsNumbe
isValid = result != null && rows.size() < rowsNumber;
if (isValid && isValidRow(result)) {
if (index >= offset) {
- key = new TypedObject(keyType, result.getRow());
+ key = new ConvertibleObject(keyType, result.getRow());
DataRow row = new DataRow(key);
row.addCell(new DataCell(row, ColumnQualifier.KEY, key));
@@ -431,9 +497,9 @@ protected TypedObject loadRows(ResultScanner scanner, long offset, int rowsNumbe
HColumnDescriptor columnDescriptor = tableDescriptor.getFamily(familyEntry.getKey());
for (NavigableMap.Entry> qualifierEntry : familyEntry.getValue().entrySet()) {
- ColumnQualifier qualifier = new ColumnQualifier(Bytes.toStringBinary(qualifierEntry.getKey()), new ColumnFamily(columnDescriptor));
+ ColumnQualifier qualifier = new ColumnQualifier(qualifierEntry.getKey(), new ColumnFamily(columnDescriptor), nameConverter);
- ObjectType columnType = ObjectType.String;
+ ColumnType columnType = ColumnType.String;
String columnName = qualifier.getFullName();
if (this.columnTypes.containsKey(columnName)) {
@@ -441,7 +507,7 @@ protected TypedObject loadRows(ResultScanner scanner, long offset, int rowsNumbe
}
for (NavigableMap.Entry cell : qualifierEntry.getValue().entrySet()) {
- row.addCell(new DataCell(row, qualifier, new TypedObject(columnType, cell.getValue())));
+ row.addCell(new DataCell(row, qualifier, new ConvertibleObject(columnType, cell.getValue())));
}
if (!columns.contains(qualifier)) {
@@ -480,31 +546,37 @@ private Collection loadColumns(int rowsNumber) throws IOExcepti
HTableDescriptor tableDescriptor = table.getTableDescriptor();
ResultScanner scanner = table.getScanner(scan);
+ try {
+ TypeConverter nameConverter = getColumnNameConverterInternal();
- Result row;
- int counter = 0;
+ Result row;
+ int counter = 0;
- do {
- row = scanner.next();
- if (row != null) {
- NavigableMap>> familyMap = row.getMap();
- for (NavigableMap.Entry>> familyEntry : familyMap.entrySet()) {
- HColumnDescriptor columnDescriptor = tableDescriptor.getFamily(familyEntry.getKey());
-
- for (byte[] quantifier : familyEntry.getValue().keySet()) {
- ColumnQualifier columnQualifier = new ColumnQualifier(Bytes.toStringBinary(quantifier), new ColumnFamily(columnDescriptor));
- if (!columns.contains(columnQualifier)) {
- columns.add(columnQualifier);
+ do {
+ row = scanner.next();
+ if (row != null) {
+ NavigableMap>> familyMap = row.getMap();
+ for (NavigableMap.Entry>> familyEntry : familyMap.entrySet()) {
+ HColumnDescriptor columnDescriptor = tableDescriptor.getFamily(familyEntry.getKey());
+
+ for (byte[] quantifier : familyEntry.getValue().keySet()) {
+ ColumnQualifier columnQualifier = new ColumnQualifier(quantifier, new ColumnFamily(columnDescriptor), nameConverter);
+ if (!columns.contains(columnQualifier)) {
+ columns.add(columnQualifier);
+ }
}
}
}
+
+ counter++;
}
+ while (row != null && counter < rowsNumber);
- counter++;
+ return columns;
+ }
+ finally {
+ scanner.close();
}
- while (row != null && counter < rowsNumber);
-
- return columns;
}
/**
@@ -522,7 +594,7 @@ private Collection next(long offset, int rowsNumber) throws IOException
scan.setCaching(itemsNumber);
if (!this.markers.isEmpty()) {
- scan.setStartRow(peekMarker().key.toByteArray());
+ scan.setStartRow(peekMarker().key.getValue());
}
if (this.forwardNavigateOnly) {
@@ -533,17 +605,22 @@ private Collection next(long offset, int rowsNumber) throws IOException
HTable table = this.connection.getTableFactory().get(this.tableName);
ResultScanner scanner = table.getScanner(scan);
- Collection rows = new ArrayList();
- Collection columns = new ArrayList();
+ try {
+ Collection rows = new ArrayList();
+ Collection columns = new ArrayList();
- columns.add(ColumnQualifier.KEY);
+ columns.add(ColumnQualifier.KEY);
- TypedObject lastKey = loadRows(scanner, offset, rowsNumber, rows, columns);
- if (lastKey != null) {
- this.markers.push(new Marker(lastKey, rows, columns));
- }
+ ConvertibleObject lastKey = loadRows(scanner, offset, rowsNumber, rows, columns);
+ if (lastKey != null) {
+ this.markers.push(new Marker(lastKey, rows, columns));
+ }
- return rows;
+ return rows;
+ }
+ finally {
+ scanner.close();
+ }
}
/**
@@ -569,6 +646,18 @@ private Marker peekMarker() {
}
return null;
}
+
+ /**
+ * Gets configured column name converter or a default one.
+ *
+ * @return A type converter.
+ */
+ private TypeConverter getColumnNameConverterInternal() {
+ if (this.columnNameConverter != null) {
+ return this.columnNameConverter;
+ }
+ return ColumnType.BinaryString.getConverter();
+ }
//endregion
/**
@@ -581,7 +670,7 @@ private static class Marker {
/**
* The last key loaded from the previous batch of rows.
*/
- private TypedObject key;
+ private ConvertibleObject key;
/**
* A list of previously loaded rows.
*/
@@ -601,7 +690,7 @@ private static class Marker {
* @param rows A list of loaded rows.
* @param columns A list of columns loaded from the rows.
*/
- private Marker(TypedObject key, Collection rows, Collection columns) {
+ private Marker(ConvertibleObject key, Collection rows, Collection columns) {
this.key = key;
this.rows = rows;
this.columns = columns;
diff --git a/src/main/java/hrider/io/CloseableHelper.java b/src/main/java/hrider/io/CloseableHelper.java
new file mode 100644
index 0000000..7f85be1
--- /dev/null
+++ b/src/main/java/hrider/io/CloseableHelper.java
@@ -0,0 +1,37 @@
+package hrider.io;
+
+import java.io.Closeable;
+
+/**
+ * Copyright (C) 2012 NICE Systems ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @author Igor Cher
+ * @version %I%, %G%
+ */
+public class CloseableHelper {
+
+ private CloseableHelper() {
+ }
+
+ public static void closeSilently(Closeable closeable) {
+ if (closeable != null) {
+ try {
+ closeable.close();
+ }
+ catch (Exception ignore) {
+ }
+ }
+ }
+}
diff --git a/src/main/java/hrider/io/Downloader.java b/src/main/java/hrider/io/Downloader.java
new file mode 100644
index 0000000..13385d2
--- /dev/null
+++ b/src/main/java/hrider/io/Downloader.java
@@ -0,0 +1,98 @@
+package hrider.io;
+
+import java.io.*;
+import java.net.URL;
+import java.net.URLConnection;
+
+/**
+ * Copyright (C) 2012 NICE Systems ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @author Igor Cher
+ * @version %I%, %G%
+ */
+public class Downloader {
+
+ //region Constants
+ private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
+ //endregion
+
+ //region Constructor
+ private Downloader() {
+ }
+ //endregion
+
+ //region Public Methods
+ public static File download(URL url) throws IOException, FileNotFoundException {
+ StringBuilder fileName = new StringBuilder();
+ StringBuilder extension = new StringBuilder();
+
+ extractFileNameAndExtension(url.getPath(), fileName, extension);
+
+ File temp = File.createTempFile(fileName.toString() + '-', extension.length() == 0 ? null : extension.toString());
+
+ InputStream in = url.openStream();
+ OutputStream out = new FileOutputStream(temp);
+
+ try {
+ copy(in, out);
+ }
+ finally {
+ try {
+ in.close();
+ }
+ catch (IOException ignore) {
+ }
+
+ try {
+ out.close();
+ }
+ catch (IOException ignore) {
+ }
+ }
+
+ return temp;
+ }
+ //endregion
+
+ //region Private Methods
+ private static void copy(InputStream in, OutputStream out) throws IOException {
+ byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
+
+ int bytesRead;
+
+ while (-1 != (bytesRead = in.read(buffer))) {
+ out.write(buffer, 0, bytesRead);
+ }
+ }
+
+ private static void extractFileNameAndExtension(String path, StringBuilder fileName, StringBuilder extension) {
+ String leaf = path.replace("\\", "/");
+
+ int index = leaf.lastIndexOf('/');
+ if (index != -1) {
+ leaf = leaf.substring(index + 1);
+ }
+
+ index = leaf.lastIndexOf('.');
+ if (index == -1) {
+ fileName.append(leaf);
+ }
+ else {
+ fileName.append(leaf.substring(0, index));
+ extension.append(leaf.substring(index));
+ }
+ }
+ //endregion
+}
diff --git a/src/main/java/hrider/io/FileHelper.java b/src/main/java/hrider/io/FileHelper.java
new file mode 100644
index 0000000..1191e75
--- /dev/null
+++ b/src/main/java/hrider/io/FileHelper.java
@@ -0,0 +1,69 @@
+package hrider.io;
+
+import org.apache.commons.io.FileUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * Copyright (C) 2012 NICE Systems ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @author Igor Cher
+ * @version %I%, %G%
+ */
+public class FileHelper {
+
+ //region Constructor
+ private FileHelper() {
+ }
+ //endregion
+
+ //region Public Methods
+ public static void delete(File path, String... exclude) {
+ List excludedPaths = Arrays.asList(exclude);
+
+ File[] files = path.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ if (!excludedPaths.contains(file.getName())) {
+ if (file.isDirectory()) {
+ delete(file);
+ }
+ file.delete();
+ }
+ }
+ }
+ }
+
+ public static File findFile(File folder, Pattern regex) {
+ File[] files = folder.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ if (regex.matcher(file.getName()).find()) {
+ return file;
+ }
+ }
+ }
+ return null;
+ }
+
+ public static void copy(File source, File target) throws IOException {
+ FileUtils.copyFile(source, target);
+ }
+ //endregion
+}
diff --git a/src/main/java/hrider/io/Log.java b/src/main/java/hrider/io/Log.java
new file mode 100644
index 0000000..fb1fa46
--- /dev/null
+++ b/src/main/java/hrider/io/Log.java
@@ -0,0 +1,58 @@
+package hrider.io;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Copyright (C) 2012 NICE Systems ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @author Igor Cher
+ * @version %I%, %G%
+ *
+ * This class is a log wrapper.
+ */
+public class Log {
+
+ //region Variables
+ private Logger logger;
+ //endregion
+
+ //region Constructor
+ private Log(Class> clazz) {
+ logger = Logger.getLogger(clazz);
+ }
+ //endregion
+
+ //region Public Methods
+ public static Log getLogger(Class> clazz) {
+ return new Log(clazz);
+ }
+
+ public void info(String format, Object... args) {
+ logger.info(String.format(format, args));
+ }
+
+ public void debug(String format, Object... args) {
+ logger.debug(String.format(format, args));
+ }
+
+ public void warn(Throwable t, String format, Object... args) {
+ logger.warn(String.format(format, args), t);
+ }
+
+ public void error(Throwable t, String format, Object... args) {
+ logger.error(String.format(format, args), t);
+ }
+ //endregion
+}
diff --git a/src/main/java/hrider/io/PathHelper.java b/src/main/java/hrider/io/PathHelper.java
index 1fc6762..444a6ec 100644
--- a/src/main/java/hrider/io/PathHelper.java
+++ b/src/main/java/hrider/io/PathHelper.java
@@ -1,13 +1,39 @@
package hrider.io;
+import java.io.File;
+import java.net.URI;
+import java.net.URISyntaxException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+/**
+ * Copyright (C) 2012 NICE Systems ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @author Igor Cher
+ * @version %I%, %G%
+ *
+ * This class is a helper class to work with path's.
+ */
public class PathHelper {
//region Constants
- private final static String FILE_SEPARATOR = System.getProperty("file.separator");
- private final static Pattern ENV_VAR = Pattern.compile("((?<=\\$\\{)[a-zA-Z_0-9]*(?=\\}))");
+ public final static String FILE_SEPARATOR = "/";
+ public final static String LINE_SEPARATOR = System.getProperty("line.separator");
+
+ private final static Log logger = Log.getLogger(PathHelper.class);
+ private final static Pattern ENV_VAR = Pattern.compile("((?<=\\$\\{)[a-zA-Z_0-9]*(?=\\}))");
//endregion
//region Constructor
@@ -16,6 +42,68 @@ private PathHelper() {
//endregion
//region Public Methods
+ /**
+ * Gets current folder of the executing process.
+ *
+ * @return A path to the current folder.
+ */
+ public static String getCurrentFolder() {
+ return getParent(new File(".").getAbsolutePath());
+ }
+
+ /**
+ * Removes extension from the file path.
+ *
+ * @param path The path to remove extension.
+ * @return A new path if the provided path contained extension or an original path otherwise.
+ */
+ public static String getPathWithoutExtension(String path) {
+ String normalisedPath = expand(path);
+
+ int index = normalisedPath.lastIndexOf('.');
+ if (index != -1) {
+ normalisedPath = normalisedPath.substring(0, index);
+ }
+
+ return normalisedPath;
+ }
+
+ public static String getFileNameWithoutExtension(String path) {
+ String leaf = getLeaf(path);
+
+ int index = leaf.lastIndexOf('.');
+ if (index != -1) {
+ leaf = leaf.substring(0, index);
+ }
+
+ return leaf;
+ }
+
+ public static String getFileExtension(String path) {
+ int index = path.lastIndexOf('.');
+ if (index != -1) {
+ return path.substring(index);
+ }
+ return null;
+ }
+
+ /**
+ * Removes media from the provided path. For example D://some_path/some_folder -> some_path/some_folder
+ *
+ * @param path The path to remove the media.
+ * @return A new path if the provided path contained media or an original path otherwise.
+ */
+ public static String getPathWithoutMedia(String path) {
+ String normalisedPath = expand(path);
+
+ int index = normalisedPath.indexOf(':' + FILE_SEPARATOR);
+ if (index != -1) {
+ normalisedPath = normalisedPath.substring(index + 1 + FILE_SEPARATOR.length());
+ }
+
+ return normalisedPath;
+ }
+
public static String expand(String path) {
String expandedPath = path;
if (expandedPath != null) {
@@ -27,16 +115,21 @@ public static String expand(String path) {
if (expVar != null) {
expandedPath = expandedPath.replace(String.format("${%s}", envVar), expVar);
}
+
+ expVar = System.getProperty(envVar);
+ if (expVar != null) {
+ expandedPath = expandedPath.replace(String.format("${%s}", envVar), expVar);
+ }
}
}
- return expandedPath;
+ return normalise(expandedPath);
}
public static String append(String path1, String path2) {
StringBuilder path = new StringBuilder();
if (path1 != null) {
- String normalisedPath = expand(normalise(path1));
+ String normalisedPath = expand(path1);
path.append(normalisedPath);
if (!normalisedPath.endsWith(FILE_SEPARATOR)) {
@@ -45,7 +138,7 @@ public static String append(String path1, String path2) {
}
if (path2 != null) {
- String normalisedPath = expand(normalise(path2));
+ String normalisedPath = expand(path2);
if (path.length() > 0) {
if (normalisedPath.startsWith(FILE_SEPARATOR)) {
path.append(normalisedPath.substring(FILE_SEPARATOR.length()));
@@ -64,7 +157,7 @@ public static String append(String path1, String path2) {
public static String getParent(String path) {
if (path != null) {
- String normalisedPath = expand(normalise(path));
+ String normalisedPath = expand(path);
if (normalisedPath.endsWith(FILE_SEPARATOR)) {
normalisedPath = normalisedPath.substring(0, normalisedPath.length() - FILE_SEPARATOR.length());
}
@@ -74,12 +167,13 @@ public static String getParent(String path) {
return normalisedPath.substring(0, index);
}
}
+
return null;
}
public static String getLeaf(String path) {
if (path != null) {
- String normalisedPath = expand(normalise(path));
+ String normalisedPath = expand(path);
if (normalisedPath.endsWith(FILE_SEPARATOR)) {
normalisedPath = normalisedPath.substring(0, normalisedPath.length() - FILE_SEPARATOR.length());
}
@@ -89,17 +183,41 @@ public static String getLeaf(String path) {
return normalisedPath.substring(index + 1);
}
}
+
return null;
}
public static String normalise(String path) {
if (path != null) {
- if ("\\".equals(FILE_SEPARATOR)) {
- return path.replace("/", "\\");
+ String normalizedPath = path;
+ boolean removeSlash = false;
+
+ try {
+ normalizedPath = normalizedPath.replace("\\", FILE_SEPARATOR);
+ removeSlash = !normalizedPath.startsWith(FILE_SEPARATOR);
+
+ if (!normalizedPath.startsWith("file:")) {
+ if (normalizedPath.startsWith(FILE_SEPARATOR)) {
+ normalizedPath = "file:" + normalizedPath;
+ }
+ else {
+ normalizedPath = "file:/" + normalizedPath;
+ }
+ }
+
+ URI uri = new URI(normalizedPath);
+ normalizedPath = uri.getPath();
}
- else {
- return path.replace("\\", "/");
+ catch (URISyntaxException e) {
+ logger.warn(e, "Path is not valid URI: '%s'", normalizedPath);
+ }
+
+ // Remove slash in the beginning of the path if it was added by the URI class.
+ if (removeSlash && normalizedPath.startsWith(FILE_SEPARATOR)) {
+ normalizedPath = normalizedPath.substring(FILE_SEPARATOR.length());
}
+
+ return normalizedPath;
}
return null;
}
diff --git a/src/main/java/hrider/io/PathWatcher.java b/src/main/java/hrider/io/PathWatcher.java
new file mode 100644
index 0000000..7d42a9d
--- /dev/null
+++ b/src/main/java/hrider/io/PathWatcher.java
@@ -0,0 +1,42 @@
+package hrider.io;
+
+import java.io.Closeable;
+import java.io.File;
+import java.util.Date;
+import java.util.Timer;
+import java.util.TimerTask;
+
+public abstract class PathWatcher extends TimerTask implements Closeable {
+
+ private long timeStamp;
+ private final File path;
+ private final Timer timer;
+
+ protected PathWatcher(File path) {
+ this.path = path;
+ this.timeStamp = path.lastModified();
+ this.timer = new Timer();
+ }
+
+ public void start(long period) {
+ this.timer.schedule(this, new Date(), period);
+ }
+
+ @Override
+ public final void run() {
+ long timestamp = this.path.lastModified();
+
+ if (this.timeStamp != timestamp) {
+ this.timeStamp = timestamp;
+
+ onChange(this.path);
+ }
+ }
+
+ @Override
+ public void close() {
+ this.timer.cancel();
+ }
+
+ protected abstract void onChange(File file);
+}
diff --git a/src/main/java/hrider/reflection/Clazz.java b/src/main/java/hrider/reflection/Clazz.java
index 458d0ce..2b07308 100644
--- a/src/main/java/hrider/reflection/Clazz.java
+++ b/src/main/java/hrider/reflection/Clazz.java
@@ -1,9 +1,12 @@
package hrider.reflection;
+import hrider.config.GlobalConfig;
+
import java.lang.reflect.Field;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.*;
/**
* Copyright (C) 2012 NICE Systems ltd.
@@ -27,8 +30,12 @@
*/
public class Clazz {
+ //region Constructor
private Clazz() {
}
+ //endregion
+
+ //region Public Methods
/**
* Converts a primitive value represented as {@link String} to object.
@@ -38,7 +45,7 @@ private Clazz() {
* @return A converted object.
*/
@SuppressWarnings("unchecked")
- public static Object primitiveToObject(Class type, String value) {
+ public static Object fromPrimitive(Class type, String value) {
if (isPrimitive(type)) {
if (value != null && !value.isEmpty()) {
String typeName = type.getSimpleName();
@@ -69,6 +76,19 @@ public static Object primitiveToObject(Class type, String value) {
if ("String".equals(typeName)) {
return value;
}
+ if ("date".equalsIgnoreCase(typeName)) {
+ if (isNumber(value)) {
+ return new Date(Long.parseLong(value));
+ }
+
+ DateFormat df = new SimpleDateFormat(GlobalConfig.instance().getDateFormat(), Locale.ENGLISH);
+ try {
+ return df.parse(value);
+ }
+ catch (ParseException ignored) {
+ return null;
+ }
+ }
if (type.isEnum()) {
return Enum.valueOf(type, value);
}
@@ -78,44 +98,41 @@ public static Object primitiveToObject(Class type, String value) {
}
/**
- * Sets a value to the field.
+ * Converts a {@link String} to a number represented by {@link Integer} or {@link Long}.
*
- * @param obj The instance which field is to be updated.
- * @param field The field to be updated.
- * @param value The value to be set.
- * @throws IllegalAccessException Error accessing the field.
+ * @param value The value to converters.
+ * @return A converted number if a conversion was successful or null otherwise.
*/
- public static void setValue(Object obj, Field field, Object value) throws IllegalAccessException {
- field.setAccessible(true);
-
- String typeName = field.getType().getSimpleName();
- if ("int".equals(typeName) || "Integer".equals(typeName)) {
- field.setInt(obj, (Integer)value);
- }
- else if ("long".equalsIgnoreCase(typeName)) {
- field.setLong(obj, (Long)value);
+ public static Object toNumber(String value) {
+ if (isNumber(value)) {
+ return Long.parseLong(value);
}
- else if ("boolean".equalsIgnoreCase(typeName)) {
- field.setBoolean(obj, (Boolean)value);
- }
- else if ("byte".equalsIgnoreCase(typeName)) {
- field.setByte(obj, (Byte)value);
- }
- else if ("char".equals(typeName) || "Character".equals(typeName)) {
- field.setChar(obj, (Character)value);
- }
- else if ("short".equalsIgnoreCase(typeName)) {
- field.setShort(obj, (Short)value);
- }
- else if ("float".equalsIgnoreCase(typeName)) {
- field.setFloat(obj, (Float)value);
- }
- else if ("double".equalsIgnoreCase(typeName)) {
- field.setDouble(obj, (Double)value);
- }
- else {
- field.set(obj, value);
+ return null;
+ }
+
+ /**
+ * Converts a {@link String} to a floating number represented by {@link Integer} or {@link Long}.
+ *
+ * @param value The value to converters.
+ * @return A converted number if a conversion was successful or null otherwise.
+ */
+ public static Object toDecimal(String value) {
+ if (isDecimal(value)) {
+ return Double.parseDouble(value);
}
+ return null;
+ }
+
+ /**
+ * Gets a value of the field.
+ *
+ * @param obj The instance which field's value is to be retrieved.
+ * @param fieldName The name of the field that holds the value.
+ * @return A value extracted from the field.
+ * @throws IllegalAccessException Error accessing the field.
+ */
+ public static Object getValue(Object obj, String fieldName) throws NoSuchFieldException, IllegalAccessException {
+ return getValue(obj, obj.getClass().getDeclaredField(fieldName));
}
/**
@@ -157,6 +174,59 @@ public static Object getValue(Object obj, Field field) throws IllegalAccessExcep
return field.get(obj);
}
+ /**
+ * Sets a value to the field.
+ *
+ * @param obj The instance which field is to be updated.
+ * @param fieldName The name of the field to be updated.
+ * @param value The value to be set.
+ * @throws IllegalAccessException Error accessing the field.
+ */
+ public static void setValue(Object obj, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException {
+ setValue(obj, obj.getClass().getDeclaredField(fieldName), value);
+ }
+
+ /**
+ * Sets a value to the field.
+ *
+ * @param obj The instance which field is to be updated.
+ * @param field The field to be updated.
+ * @param value The value to be set.
+ * @throws IllegalAccessException Error accessing the field.
+ */
+ public static void setValue(Object obj, Field field, Object value) throws IllegalAccessException {
+ field.setAccessible(true);
+
+ String typeName = field.getType().getSimpleName();
+ if ("int".equals(typeName) || "Integer".equals(typeName)) {
+ field.setInt(obj, (Integer)value);
+ }
+ else if ("long".equalsIgnoreCase(typeName)) {
+ field.setLong(obj, (Long)value);
+ }
+ else if ("boolean".equalsIgnoreCase(typeName)) {
+ field.setBoolean(obj, (Boolean)value);
+ }
+ else if ("byte".equalsIgnoreCase(typeName)) {
+ field.setByte(obj, (Byte)value);
+ }
+ else if ("char".equals(typeName) || "Character".equals(typeName)) {
+ field.setChar(obj, (Character)value);
+ }
+ else if ("short".equalsIgnoreCase(typeName)) {
+ field.setShort(obj, (Short)value);
+ }
+ else if ("float".equalsIgnoreCase(typeName)) {
+ field.setFloat(obj, (Float)value);
+ }
+ else if ("double".equalsIgnoreCase(typeName)) {
+ field.setDouble(obj, (Double)value);
+ }
+ else {
+ field.set(obj, value);
+ }
+ }
+
/**
* Returns all public/protected/private fields including super classes.
*
@@ -206,11 +276,90 @@ public static boolean isPrimitive(Class type) {
* @return True if the string represents a number or false otherwise.
*/
public static boolean isNumber(String value) {
- for (char letter : value.toCharArray()) {
+ if (value == null) {
+ return false;
+ }
+
+ char[] array = value.toCharArray();
+ for (int i = 0 ; i < array.length ; i++) {
+ char letter = array[i];
if (!Character.isDigit(letter)) {
- return false;
+ if (i > 0 || letter != '-') {
+ return false;
+ }
}
}
return true;
}
+
+ /**
+ * Checks whether the specified class represents a type that can hold a number.
+ *
+ * @param clazz The class to check.
+ * @return True if the specified type can hold the number or False otherwise.
+ */
+ public static boolean isNumber(Class> clazz) {
+ if (clazz == null) {
+ return false;
+ }
+
+ return clazz.equals(Integer.class) ||
+ clazz.equals(Long.class) ||
+ clazz.equals(Byte.class) ||
+ clazz.equals(Short.class);
+ }
+
+ /**
+ * Checks whether the string represents a floating number.
+ *
+ * @param value The string to check.
+ * @return True if the string represents a number or false otherwise.
+ */
+ public static boolean isDecimal(String value) {
+ if (value == null) {
+ return false;
+ }
+
+ boolean dotFound = false;
+
+ char[] array = value.toCharArray();
+ for (int i = 0 ; i < array.length ; i++) {
+ char letter = array[i];
+ if (!Character.isDigit(letter)) {
+ if (letter == '.') {
+ if (dotFound) {
+ return false;
+ }
+ if (i == 0 || i == array.length - 1) {
+ return false;
+ }
+ if (!Character.isDigit(array[i - 1]) || !Character.isDigit(array[i + 1])) {
+ return false;
+ }
+ dotFound = true;
+ }
+ if (letter == '-') {
+ if (i > 0) {
+ return false;
+ }
+ }
+ }
+ }
+ return dotFound;
+ }
+
+ /**
+ * Checks whether the specified class represents a type that can hold a number.
+ *
+ * @param clazz The class to check.
+ * @return True if the specified type can hold the number or False otherwise.
+ */
+ public static boolean isDecimal(Class> clazz) {
+ if (clazz == null) {
+ return false;
+ }
+
+ return clazz.equals(Float.class) || clazz.equals(Double.class);
+ }
+ //endregion
}
diff --git a/src/main/java/hrider/reflection/Compiler.java b/src/main/java/hrider/reflection/Compiler.java
deleted file mode 100644
index f37719a..0000000
--- a/src/main/java/hrider/reflection/Compiler.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package hrider.reflection;
-
-import hrider.config.GlobalConfig;
-import hrider.io.PathHelper;
-
-import javax.tools.*;
-import java.io.*;
-import java.util.Arrays;
-
-public class Compiler {
-
- private Compiler() {
- }
-
- public static Class> compile(String name, String code) throws IOException, ClassNotFoundException {
- String folder = GlobalConfig.instance().getCompilationFolder();
-
- String path = name.replace('.', '/');
- File file = saveCode(PathHelper.append(folder, path), code);
-
- JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
- StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
-
- Iterable extends JavaFileObject> units = fileManager.getJavaFileObjectsFromFiles(Arrays.asList(file));
- boolean success = compiler.getTask(null, fileManager, null, null, null, units).call();
- if (success) {
- ClassLoader classLoader = fileManager.getClassLoader(null);
- return classLoader.loadClass(name);
- }
-
- return null;
- }
-
- private static File saveCode(String path, String code) throws IOException {
- File file = new File(path);
- if (!file.exists()) {
- file.mkdirs();
- }
-
- FileOutputStream stream = null;
-
- try {
- stream = new FileOutputStream(file);
- stream.write(code.getBytes());
-
- return file;
- }
- finally {
- if (stream != null) {
- stream.close();
- }
- }
- }
-}
diff --git a/src/main/java/hrider/reflection/JavaCompiler.java b/src/main/java/hrider/reflection/JavaCompiler.java
new file mode 100644
index 0000000..71cd6de
--- /dev/null
+++ b/src/main/java/hrider/reflection/JavaCompiler.java
@@ -0,0 +1,111 @@
+package hrider.reflection;
+
+import hrider.io.PathHelper;
+
+import javax.tools.*;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * Copyright (C) 2012 NICE Systems ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @author Igor Cher
+ * @version %I%, %G%
+ *
+ * This class is responsible for compiling java code.
+ */
+public class JavaCompiler {
+
+ //region Variables
+ private static List errors;
+ //endregion
+
+ //region Constructor
+ static {
+ errors = new ArrayList();
+ }
+
+ private JavaCompiler() {
+ }
+ //endregion
+
+ //region Public Properties
+ public static List getErrors() {
+ return errors;
+ }
+ //endregion
+
+ //region Public Methods
+ public static boolean compile(File sourceCode, String outputFolder) throws IOException, FileNotFoundException {
+
+ errors.clear();
+
+ // make sure all directories are created.
+ new File(outputFolder).mkdirs();
+
+ StandardJavaFileManager fileManager = null;
+
+ try {
+ javax.tools.JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
+ fileManager = compiler.getStandardFileManager(null, null, null);
+
+ DiagnosticCollector diagnostics = new DiagnosticCollector();
+ Iterable extends JavaFileObject> units = fileManager.getJavaFileObjectsFromFiles(Arrays.asList(sourceCode));
+
+ Collection options = new ArrayList();
+ options.addAll(Arrays.asList("-classpath", System.getProperty("java.class.path"), "-d", outputFolder));
+
+ boolean success = compiler.getTask(null, fileManager, diagnostics, options, null, units).call();
+ if (!success) {
+ for (Diagnostic diagnostic : diagnostics.getDiagnostics()) {
+ errors.add(diagnostic.getMessage(Locale.ENGLISH));
+ }
+ }
+
+ return success;
+ }
+ finally {
+ if (fileManager != null) {
+ fileManager.close();
+ }
+ }
+ }
+
+ public static File saveCode(String className, String code, String outputFolder) throws IOException, FileNotFoundException {
+ // make sure all directories are created.
+ new File(outputFolder).mkdirs();
+
+ String path = PathHelper.append(outputFolder, className + ".java");
+ File file = new File(path);
+
+ FileOutputStream stream = null;
+
+ try {
+ stream = new FileOutputStream(file);
+ stream.write(code.getBytes());
+
+ return file;
+ }
+ finally {
+ if (stream != null) {
+ stream.close();
+ }
+ }
+ }
+ //endregion
+}
diff --git a/src/main/java/hrider/reflection/JavaPackage.java b/src/main/java/hrider/reflection/JavaPackage.java
new file mode 100644
index 0000000..b128171
--- /dev/null
+++ b/src/main/java/hrider/reflection/JavaPackage.java
@@ -0,0 +1,226 @@
+package hrider.reflection;
+
+import hrider.io.Log;
+import hrider.io.PathHelper;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.URL;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+import java.util.regex.Pattern;
+
+/**
+ * Copyright (C) 2012 NICE Systems ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @author Igor Cher
+ * @version %I%, %G%
+ *
+ * This class is a helper class to work with java packages.
+ */
+public class JavaPackage {
+
+ //region Constants
+ private static Log logger = Log.getLogger(JavaPackage.class);
+ private static final Pattern NUMBER_IN_CLASS_NAME = Pattern.compile("[$].*");
+ //endregion
+
+ //region Constructor
+ private JavaPackage() {
+ }
+ //endregion
+
+ //region Public Methods
+
+ /**
+ * Collects all classes located in the provided package.
+ *
+ * @param packageName The name of the package.
+ * @return A list of classes if found or an empty list otherwise.
+ * @throws IOException Error accessing classes on a file system.
+ * @throws ClassNotFoundException Class cannot be found.
+ * @throws FileNotFoundException File cannot be found.
+ */
+ public static Collection> getClasses(String packageName) throws IOException, ClassNotFoundException, FileNotFoundException {
+ return getClasses(Thread.currentThread().getContextClassLoader(), packageName);
+ }
+
+ /**
+ * Collects all classes located in the provided package.
+ *
+ * @param loader The class loader to use for classes.
+ * @param packageName The name of the package.
+ * @return A list of classes if found or an empty list otherwise.
+ * @throws IOException Error accessing classes on a file system.
+ * @throws ClassNotFoundException Class cannot be found.
+ * @throws FileNotFoundException File cannot be found.
+ */
+ public static Collection> getClasses(ClassLoader loader, String packageName) throws IOException, ClassNotFoundException, FileNotFoundException {
+ logger.info("Loading classes from package '%s'", packageName);
+
+ Collection> classes = new HashSet>();
+ String path = packageName.replace(".", "/");
+
+ Enumeration resources = loader.getResources(path + '/');
+ while (resources.hasMoreElements()) {
+ URL url = resources.nextElement();
+ String filePath = getPath(url);
+
+ logger.debug("Resource URL: %s", url.toString());
+ logger.debug("Normalized path: %s", filePath);
+
+ if (filePath.endsWith(".jar")) {
+ classes.addAll(getClassesFromJar(loader, new File(filePath), packageName));
+ }
+ else {
+ classes.addAll(getClassesFromFolder(loader, new File(filePath), packageName));
+ }
+ }
+ return classes;
+ }
+
+ /**
+ * Collects all classes located in the provided directory under the specified package.
+ *
+ * @param folder The directory to search for the classes.
+ * @param packageName The name of the package.
+ * @return A list of classes if found or an empty list otherwise.
+ * @throws ClassNotFoundException Class cannot be found.
+ */
+ public static Collection> getClassesFromFolder(File folder, String packageName) throws ClassNotFoundException {
+ return getClassesFromFolder(Thread.currentThread().getContextClassLoader(), folder, packageName);
+ }
+
+ /**
+ * Collects all classes located in the provided directory under the specified package.
+ *
+ * @param loader The class loader to use for classes.
+ * @param folder The directory to search for the classes.
+ * @param packageName The name of the package.
+ * @return A list of classes if found or an empty list otherwise.
+ * @throws ClassNotFoundException Class cannot be found.
+ */
+ public static Collection> getClassesFromFolder(ClassLoader loader, File folder, String packageName) throws ClassNotFoundException {
+ logger.info("Loading classes from folder '%s'", folder.getAbsolutePath());
+
+ Collection> classes = new HashSet>();
+ Collection loadedClasses = new HashSet();
+
+ if (folder.exists()) {
+ for (String fileName : folder.list()) {
+ File file = new File(PathHelper.append(folder.getAbsolutePath(), fileName));
+ if (file.isDirectory()) {
+ classes.addAll(getClassesFromFolder(loader, file, packageName));
+ }
+ else {
+ if (fileName.endsWith(".class")) {
+ String className = packageName + '.' +
+ PathHelper.getPathWithoutExtension(NUMBER_IN_CLASS_NAME.matcher(fileName).replaceAll(""));
+
+ if (!loadedClasses.contains(className)) {
+ logger.info("Loading class '%s'", className);
+
+ loadedClasses.add(className);
+ classes.add(loader.loadClass(className));
+ }
+ }
+ }
+ }
+ }
+
+ return classes;
+ }
+
+ /**
+ * Collects all classes located in the provided jar file under the specified package.
+ *
+ * @param jarFile The path to the jar file.
+ * @param packageName The name of the package to look into.
+ * @return A list of classes if found or an empty list otherwise.
+ * @throws IOException Error accessing classes on a file system.
+ * @throws ClassNotFoundException Class cannot be found.
+ * @throws FileNotFoundException File cannot be found.
+ */
+ public static Collection> getClassesFromJar(File jarFile, String packageName) throws IOException, ClassNotFoundException, FileNotFoundException {
+ return getClassesFromJar(Thread.currentThread().getContextClassLoader(), jarFile, packageName);
+ }
+
+ /**
+ * Collects all classes located in the provided jar file under the specified package.
+ *
+ * @param loader The class loader to use for classes.
+ * @param jarFile The path to the jar file.
+ * @param packageName The name of the package to look into.
+ * @return A list of classes if found or an empty list otherwise.
+ * @throws IOException Error accessing classes on a file system.
+ * @throws ClassNotFoundException Class cannot be found.
+ * @throws FileNotFoundException File cannot be found.
+ */
+ public static Collection> getClassesFromJar(ClassLoader loader, File jarFile, String packageName) throws IOException, ClassNotFoundException,
+ FileNotFoundException {
+
+ logger.info("Loading classes from jar '%s'", jarFile.getAbsolutePath());
+
+ Collection> classes = new HashSet>();
+ Collection loadedClasses = new HashSet();
+
+ if (jarFile.exists()) {
+ JarInputStream stream = null;
+ try {
+ stream = new JarInputStream(new FileInputStream(jarFile));
+
+ JarEntry entry;
+ while ((entry = stream.getNextJarEntry()) != null) {
+ if (entry.getName().endsWith(".class")) {
+ String className = PathHelper.getPathWithoutExtension(NUMBER_IN_CLASS_NAME.matcher(entry.getName()).replaceAll("")).replace('/', '.');
+
+ if (className.startsWith(packageName) && !loadedClasses.contains(className)) {
+ logger.info("Loading class '%s'", className);
+
+ loadedClasses.add(className);
+ classes.add(loader.loadClass(className));
+ }
+ }
+ }
+ }
+ finally {
+ if (stream != null) {
+ stream.close();
+ }
+ }
+ }
+
+ return classes;
+ }
+ //endregion
+
+ //region Private Methods
+ private static String getPath(URL url) {
+ String path = PathHelper.normalise(url.getFile());
+
+ int index = path.indexOf('!');
+ if (index != -1) {
+ path = path.substring(0, index);
+ }
+
+ return path;
+ }
+ //endregion
+}
diff --git a/src/main/java/hrider/system/Clipboard.java b/src/main/java/hrider/system/Clipboard.java
index 9bbbee9..f4f920b 100644
--- a/src/main/java/hrider/system/Clipboard.java
+++ b/src/main/java/hrider/system/Clipboard.java
@@ -36,6 +36,7 @@ private Clipboard() {
/**
* Checks if the clipboard has text.
+ *
* @return True if there is any text in the system clipboard or False otherwise.
*/
public static boolean hasText() {
@@ -51,6 +52,7 @@ public static boolean hasText() {
/**
* Sets a provided text to the system clipboard.
+ *
* @param text The text to set.
*/
public static void setText(String text) {
@@ -59,12 +61,13 @@ public static void setText(String text) {
/**
* Gets a text from the system clipboard.
+ *
* @return A text if there is any or a null.
*/
public static String getText() {
Transferable data = Toolkit.getDefaultToolkit().getSystemClipboard().getContents(null);
try {
- return (String)data.getTransferData(DataFlavor.stringFlavor);
+ return (String)data.getTransferData(DataFlavor.stringFlavor);
}
catch (Exception ignore) {
return null;
diff --git a/src/main/java/hrider/system/Version.java b/src/main/java/hrider/system/Version.java
new file mode 100644
index 0000000..29363d1
--- /dev/null
+++ b/src/main/java/hrider/system/Version.java
@@ -0,0 +1,77 @@
+package hrider.system;
+
+import java.util.Arrays;
+import java.util.regex.Pattern;
+
+/**
+ * Copyright (C) 2012 NICE Systems ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @author Igor Cher
+ * @version %I%, %G%
+ */
+public class Version implements Comparable {
+
+ //region Constants
+ private static final Pattern DELIMITER = Pattern.compile("\\.");
+ //endregion
+
+ //region Variables
+ private int[] version;
+ //endregion
+
+ //region Public Methods
+ public static int compare(String ver1, String ver2) {
+ Version version1 = new Version(ver1);
+ Version version2 = new Version(ver2);
+
+ return version1.compareTo(version2);
+ }
+
+ public Version(String version) {
+ String[] parts = DELIMITER.split(version);
+ if (parts.length < 2 && parts.length > 4) {
+ throw new IllegalArgumentException(String.format("incorrect version format: %s", version));
+ }
+
+ this.version = new int[] {0, 0, 0, 0};
+ for (int i = 0; i < parts.length; i++) {
+ this.version[i] = Integer.parseInt(parts[i]);
+ }
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof Version) {
+ return Arrays.equals(version, ((Version)obj).version);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(version);
+ }
+
+ @Override
+ public int compareTo(Version o) {
+ for (int i = 0; i < version.length && i < o.version.length; i++) {
+ if (version[i] != o.version[i]) {
+ return version[i] - o.version[i];
+ }
+ }
+ return 0;
+ }
+ //endregion
+}
diff --git a/src/main/java/hrider/ui/ChangeTracker.java b/src/main/java/hrider/ui/ChangeTracker.java
index 3597690..cd31ac1 100644
--- a/src/main/java/hrider/ui/ChangeTracker.java
+++ b/src/main/java/hrider/ui/ChangeTracker.java
@@ -3,6 +3,7 @@
import hrider.data.DataCell;
import hrider.data.DataRow;
+import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
@@ -26,7 +27,11 @@
*
* This class represents a tracker for all changes performed on cells.
*/
-public class ChangeTracker {
+public class ChangeTracker implements Serializable {
+
+ //region Constants
+ private static final long serialVersionUID = -7106669090052759327L;
+ //endregion
//region Variables
/**
diff --git a/src/main/java/hrider/ui/MessageHandler.java b/src/main/java/hrider/ui/MessageHandler.java
index 70ac7c1..8e74374 100644
--- a/src/main/java/hrider/ui/MessageHandler.java
+++ b/src/main/java/hrider/ui/MessageHandler.java
@@ -1,5 +1,7 @@
package hrider.ui;
+import hrider.io.Log;
+
import java.util.ArrayList;
import java.util.Collection;
@@ -26,6 +28,8 @@
*/
public class MessageHandler {
+ private final static Log logger = Log.getLogger(MessageHandler.class);
+
/**
* A list of registered listeners.
*/
@@ -45,6 +49,10 @@ private MessageHandler() {
* @param ex The exception.
*/
public static void addError(String message, Exception ex) {
+ if (message != null && !message.isEmpty()) {
+ logger.error(ex, message);
+ }
+
for (MessageHandlerListener listener : listeners) {
listener.onError(message, ex);
}
@@ -57,11 +65,27 @@ public static void addError(String message, Exception ex) {
* @param message The message to report.
*/
public static void addInfo(String message) {
+ if (message != null && !message.isEmpty()) {
+ logger.info(message);
+ }
+
for (MessageHandlerListener listener : listeners) {
listener.onInfo(message);
}
}
+ /**
+ * This method is called by the component that wants to report to the user additional information with the option to perform
+ * an action.
+ *
+ * @param action The action to execute if clicked.
+ */
+ public static void addAction(UIAction action) {
+ for (MessageHandlerListener listener : listeners) {
+ listener.onAction(action);
+ }
+ }
+
/**
* Adds a listener to the list of registered listeners.
*
diff --git a/src/main/java/hrider/ui/MessageHandlerListener.java b/src/main/java/hrider/ui/MessageHandlerListener.java
index 312ca1e..466a4c8 100644
--- a/src/main/java/hrider/ui/MessageHandlerListener.java
+++ b/src/main/java/hrider/ui/MessageHandlerListener.java
@@ -36,4 +36,11 @@ public interface MessageHandlerListener {
* @param ex An exception.
*/
void onError(String message, Exception ex);
+
+ /**
+ * The method is called when a component wants to show the user message with the action.
+ *
+ * @param action The action to execute if clicked.
+ */
+ void onAction(UIAction action);
}
diff --git a/src/main/java/hrider/ui/UIAction.java b/src/main/java/hrider/ui/UIAction.java
new file mode 100644
index 0000000..d9ff024
--- /dev/null
+++ b/src/main/java/hrider/ui/UIAction.java
@@ -0,0 +1,44 @@
+package hrider.ui;
+
+/**
+ * Copyright (C) 2012 NICE Systems ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @author Igor Cher
+ * @version %I%, %G%
+ */
+public interface UIAction {
+
+ /**
+ * Executes the UI action.
+ */
+ void execute();
+
+ /**
+ * Gets a formatted message. The array might contain 1-3 elements where:
+ * 1. The first part of the message if exist.
+ * 2. The word/expression that describes the action.
+ * 3. The last part of the message if exist.
+ *
+ * For example:
+ * new String[]{
+ * "The selected table is disabled, do you want to",
+ * "enable",
+ * "it?"
+ * };
+ *
+ * @return A three part message.
+ */
+ String[] getFormattedMessage();
+}
diff --git a/src/main/java/hrider/ui/controls/WideComboBox.java b/src/main/java/hrider/ui/controls/WideComboBox.java
new file mode 100644
index 0000000..9aba6a9
--- /dev/null
+++ b/src/main/java/hrider/ui/controls/WideComboBox.java
@@ -0,0 +1,50 @@
+package hrider.ui.controls;
+
+import javax.swing.*;
+import java.awt.*;
+
+public class WideComboBox extends JComboBox {
+
+ //region Constants
+ private static final long serialVersionUID = -2142335227903444963L;
+ //endregion
+
+ //region Variables
+ private boolean layingOut;
+ //endregion
+
+ //region Constructor
+ public WideComboBox() {
+ }
+
+ public WideComboBox(Object[] items) {
+ super(items);
+ }
+
+ public WideComboBox(ComboBoxModel model) {
+ super(model);
+ }
+ //endregion
+
+ //region Public Methods
+ @Override
+ public void doLayout() {
+ try {
+ layingOut = true;
+ super.doLayout();
+ }
+ finally {
+ layingOut = false;
+ }
+ }
+
+ @Override
+ public Dimension getSize() {
+ Dimension dim = super.getSize();
+ if (!layingOut) {
+ dim.width = Math.max(dim.width, getPreferredSize().width);
+ }
+ return dim;
+ }
+ //endregion
+}
diff --git a/src/main/java/hrider/ui/controls/json/JsonEditor.java b/src/main/java/hrider/ui/controls/json/JsonEditor.java
index d791995..26d5390 100644
--- a/src/main/java/hrider/ui/controls/json/JsonEditor.java
+++ b/src/main/java/hrider/ui/controls/json/JsonEditor.java
@@ -50,7 +50,7 @@ public class JsonEditor extends JPanel {
/**
* Initializes a new instance of the {@link JsonEditor} class.
*/
- public JsonEditor() {
+ public JsonEditor(final CellEditor cellEditor) {
this.textPane = new JsonTextPane();
this.textPane.setLayout(new BorderLayout());
@@ -70,8 +70,8 @@ public JsonEditor() {
JPanel buttonsPanel = new JPanel();
buttonsPanel.setBorder(new EmptyBorder(0, 0, 0, 0));
- JButton saveButton = new JButton("Save");
- saveButton.setPreferredSize(new Dimension(75, 24));
+ JButton saveButton = new JButton("Mark for save");
+ saveButton.setPreferredSize(new Dimension(115, 24));
saveButton.addActionListener(
new ActionListener() {
@Override
@@ -81,6 +81,7 @@ public void actionPerformed(ActionEvent e) {
JsonEditor.this.textPane.validateJson();
JsonEditor.this.textField.setText(JsonEditor.this.textPane.getText());
+ cellEditor.getCellEditorValue();
}
catch (JsonSyntaxException ex) {
JOptionPane.showMessageDialog(JsonEditor.this, ex.getMessage(), "Invalid JSON", JOptionPane.ERROR_MESSAGE);
diff --git a/src/main/java/hrider/ui/controls/json/JsonTextPane.java b/src/main/java/hrider/ui/controls/json/JsonTextPane.java
index c0078eb..7041259 100644
--- a/src/main/java/hrider/ui/controls/json/JsonTextPane.java
+++ b/src/main/java/hrider/ui/controls/json/JsonTextPane.java
@@ -3,6 +3,7 @@
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonParser;
+import hrider.io.Log;
import javax.swing.*;
@@ -29,6 +30,7 @@
public class JsonTextPane extends JTextPane {
//region Constants
+ private static final Log logger = Log.getLogger(JsonTextPane.class);
private static final long serialVersionUID = 6270183148379328084L;
//endregion
@@ -41,6 +43,7 @@ public JsonTextPane() {
/**
* Replaces the content of the text pane with the provided text.
+ *
* @param t The text to set.
*/
@Override
@@ -65,6 +68,7 @@ public void validateJson() {
/**
* Formats JSON.
+ *
* @param json A JSON to format.
* @return A formatted JSON.
*/
@@ -73,7 +77,8 @@ private static String formatJson(String json) {
Gson gson = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create();
return gson.toJson(new JsonParser().parse(json));
}
- catch (Exception ignore) {
+ catch (Exception e) {
+ logger.error(e, "Failed to format json '%s'.", json);
return json;
}
}
diff --git a/src/main/java/hrider/ui/controls/xml/XmlEditor.java b/src/main/java/hrider/ui/controls/xml/XmlEditor.java
index 37f3f4e..2a5fdef 100644
--- a/src/main/java/hrider/ui/controls/xml/XmlEditor.java
+++ b/src/main/java/hrider/ui/controls/xml/XmlEditor.java
@@ -48,7 +48,7 @@ public class XmlEditor extends JPanel {
/**
* Initializes a new instance of the {@link XmlEditor} class.
*/
- public XmlEditor() {
+ public XmlEditor(final CellEditor cellEditor) {
this.textPane = new XmlTextPane();
this.textPane.setLayout(new BorderLayout());
@@ -68,8 +68,8 @@ public XmlEditor() {
JPanel buttonsPanel = new JPanel();
buttonsPanel.setBorder(new EmptyBorder(0, 0, 0, 0));
- JButton saveButton = new JButton("Save");
- saveButton.setPreferredSize(new Dimension(75, 24));
+ JButton saveButton = new JButton("Mark for save");
+ saveButton.setPreferredSize(new Dimension(115, 24));
saveButton.addActionListener(
new ActionListener() {
@Override
@@ -79,6 +79,7 @@ public void actionPerformed(ActionEvent e) {
XmlEditor.this.textPane.validateXml();
XmlEditor.this.textField.setText(XmlEditor.this.textPane.getText());
+ cellEditor.getCellEditorValue();
}
catch (Exception ex) {
JOptionPane.showMessageDialog(XmlEditor.this, ex.getMessage(), "Invalid XML", JOptionPane.ERROR_MESSAGE);
diff --git a/src/main/java/hrider/ui/controls/xml/XmlTextPane.java b/src/main/java/hrider/ui/controls/xml/XmlTextPane.java
index 525322c..6485a84 100644
--- a/src/main/java/hrider/ui/controls/xml/XmlTextPane.java
+++ b/src/main/java/hrider/ui/controls/xml/XmlTextPane.java
@@ -2,6 +2,7 @@
import com.sun.org.apache.xml.internal.serialize.OutputFormat;
import com.sun.org.apache.xml.internal.serialize.XMLSerializer;
+import hrider.io.Log;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
@@ -13,7 +14,6 @@
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
-import java.io.Writer;
/**
* Copyright (C) 2012 NICE Systems ltd.
@@ -38,7 +38,9 @@
public class XmlTextPane extends JTextPane {
//region Constants
+ private static final Log logger = Log.getLogger(XmlTextPane.class);
private static final long serialVersionUID = 6270183148379328084L;
+ private static final int LINE_WIDTH = 500;
//endregion
//region Constructor
@@ -99,16 +101,17 @@ private static String formatXml(String xml) {
OutputFormat format = new OutputFormat(document);
format.setIndenting(true);
format.setIndent(4);
- format.setLineWidth(500);
+ format.setLineWidth(LINE_WIDTH);
- Writer out = new StringWriter();
+ StringWriter out = new StringWriter();
XMLSerializer serializer = new XMLSerializer(out, format);
serializer.serialize(document);
return out.toString();
}
- catch (Exception ignore) {
+ catch (Exception e) {
+ logger.error(e, "Failed to format XML '%s'.", xml);
return xml;
}
}
diff --git a/src/main/java/hrider/ui/design/JCellEditor.java b/src/main/java/hrider/ui/design/JCellEditor.java
index d9210e7..017d8e7 100644
--- a/src/main/java/hrider/ui/design/JCellEditor.java
+++ b/src/main/java/hrider/ui/design/JCellEditor.java
@@ -2,8 +2,9 @@
import com.michaelbaranov.microba.calendar.DatePicker;
import hrider.config.GlobalConfig;
+import hrider.data.ColumnType;
import hrider.data.DataCell;
-import hrider.data.ObjectType;
+import hrider.io.Log;
import hrider.ui.ChangeTracker;
import hrider.ui.controls.json.JsonEditor;
import hrider.ui.controls.xml.XmlEditor;
@@ -11,7 +12,6 @@
import javax.swing.*;
import javax.swing.table.TableCellEditor;
import java.awt.*;
-import java.beans.PropertyVetoException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
@@ -41,6 +41,7 @@
public class JCellEditor extends AbstractCellEditor implements TableCellEditor {
//region Constants
+ private static final Log logger = Log.getLogger(JCellEditor.class);
private static final long serialVersionUID = -2190137522499893284L;
//endregion
@@ -70,9 +71,9 @@ public JCellEditor(ChangeTracker changeTracker, int typeColumn, boolean canEdit)
this.dateEditor.setBorder(BorderFactory.createEmptyBorder());
this.dateEditor.setDateFormat(new SimpleDateFormat(GlobalConfig.instance().getDateFormat(), Locale.ENGLISH));
this.dateEditor.setFieldEditable(canEdit);
- this.xmlEditor = new XmlEditor();
+ this.xmlEditor = new XmlEditor(this);
this.xmlEditor.setEditable(canEdit);
- this.jsonEditor = new JsonEditor();
+ this.jsonEditor = new JsonEditor(this);
this.jsonEditor.setEditable(canEdit);
}
//endregion
@@ -105,28 +106,28 @@ public void setEditable(boolean editable) {
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
this.editorType = EditorType.Text;
- ObjectType type = ObjectType.String;
+ ColumnType type = ColumnType.String;
// Check if the value contains information regarding its type.
if (value instanceof DataCell) {
this.cell = (DataCell)value;
- type = this.cell.getTypedValue().getType();
+ type = this.cell.getType();
}
else {
this.cell = null;
}
if (this.typeColumn != -1) {
- type = (ObjectType)table.getValueAt(row, this.typeColumn);
+ type = (ColumnType)table.getValueAt(row, this.typeColumn);
}
- if (type == ObjectType.DateAsString || type == ObjectType.DateAsLong) {
+ if (type.equals(ColumnType.DateAsString) || type.equals(ColumnType.DateAsLong)) {
this.editorType = EditorType.Date;
}
- else if (type == ObjectType.Xml) {
+ else if (type.equals(ColumnType.Xml)) {
this.editorType = EditorType.Xml;
}
- else if (type == ObjectType.Json) {
+ else if (type.equals(ColumnType.Json)) {
this.editorType = EditorType.Json;
}
@@ -169,9 +170,8 @@ public Object getCellEditorValue() {
}
if (this.cell != null) {
- Object value = this.cell.toObject(text);
- if (value == null || !this.cell.contains(value)) {
- this.cell.getTypedValue().setValue(value);
+ if (!this.cell.hasValue(text)) {
+ this.cell.setValue(text);
if (this.changeTracker != null) {
this.changeTracker.addChange(this.cell);
@@ -194,12 +194,15 @@ public Object getCellEditorValue() {
private void initializeEditor(Object value) {
switch (this.editorType) {
case Date:
- try {
- if (this.cell != null) {
- this.dateEditor.setDate((Date)this.cell.getTypedValue().getValue());
+ if (this.cell != null) {
+ DateFormat df = new SimpleDateFormat(GlobalConfig.instance().getDateFormat(), Locale.ENGLISH);
+ try {
+ Date date = df.parse(this.cell.getValue());
+ this.dateEditor.setDate(date);
+ }
+ catch (Exception e) {
+ logger.error(e, "Failed to convert value to Date.", value);
}
- }
- catch (PropertyVetoException ignore) {
}
break;
case Text:
diff --git a/src/main/java/hrider/ui/design/JListRenderer.java b/src/main/java/hrider/ui/design/JListRenderer.java
new file mode 100644
index 0000000..20b9944
--- /dev/null
+++ b/src/main/java/hrider/ui/design/JListRenderer.java
@@ -0,0 +1,34 @@
+package hrider.ui.design;
+
+import hrider.hbase.Connection;
+
+import javax.swing.*;
+import java.awt.*;
+import java.io.IOException;
+
+public class JListRenderer extends DefaultListCellRenderer {
+
+ private static final long serialVersionUID = 8219559461829225540L;
+
+ private Connection connection;
+
+ public JListRenderer(Connection connection) {
+ this.connection = connection;
+ }
+
+ @Override
+ public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
+ Component component = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
+ if (value instanceof String) {
+ try {
+ boolean enabled = connection.tableEnabled((String)value);
+ if (!enabled) {
+ setForeground(Color.gray);
+ }
+ }
+ catch (IOException ignore) {
+ }
+ }
+ return component;
+ }
+}
diff --git a/src/main/java/hrider/ui/forms/AddColumnDialog.java b/src/main/java/hrider/ui/forms/AddColumnDialog.java
index 632c2e9..f2817e2 100644
--- a/src/main/java/hrider/ui/forms/AddColumnDialog.java
+++ b/src/main/java/hrider/ui/forms/AddColumnDialog.java
@@ -4,6 +4,7 @@
import com.intellij.uiDesigner.core.GridLayoutManager;
import hrider.data.ColumnFamily;
import hrider.data.ColumnQualifier;
+import hrider.data.ColumnType;
import hrider.ui.design.JCellEditor;
import hrider.ui.design.JTableModel;
@@ -13,7 +14,6 @@
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.DefaultTableModel;
-import javax.swing.table.TableCellEditor;
import java.awt.*;
import java.awt.event.*;
import java.util.Map;
@@ -256,7 +256,8 @@ public boolean showDialog(Component owner) {
public ColumnQualifier getColumn() {
if (this.okPressed) {
- return new ColumnQualifier(this.columnNameTextField.getText().trim(), (ColumnFamily)this.comboBox.getSelectedItem());
+ return new ColumnQualifier(
+ this.columnNameTextField.getText(), (ColumnFamily)this.comboBox.getSelectedItem(), ColumnType.BinaryString.getConverter());
}
return null;
}
diff --git a/src/main/java/hrider/ui/forms/AddRowDialog.java b/src/main/java/hrider/ui/forms/AddRowDialog.java
index ff1030c..c43408c 100644
--- a/src/main/java/hrider/ui/forms/AddRowDialog.java
+++ b/src/main/java/hrider/ui/forms/AddRowDialog.java
@@ -3,13 +3,13 @@
import com.intellij.uiDesigner.core.GridConstraints;
import com.intellij.uiDesigner.core.GridLayoutManager;
import hrider.data.*;
+import hrider.ui.controls.WideComboBox;
import hrider.ui.design.JCellEditor;
import hrider.ui.design.JCheckBoxRenderer;
import hrider.ui.design.JTableModel;
import javax.swing.*;
import javax.swing.table.DefaultTableModel;
-import javax.swing.table.TableCellEditor;
import java.awt.*;
import java.awt.event.*;
@@ -65,10 +65,10 @@ public AddRowDialog(Iterable columns, final Iterable
this.rowsTable.getColumn("Use").setCellEditor(new JCheckBoxRenderer(new CheckedRow(1, ColumnQualifier.KEY)));
this.rowsTable.getColumn("Use").setPreferredWidth(20);
- JComboBox comboBox = new JComboBox();
+ JComboBox comboBox = new WideComboBox();
- for (ObjectType objectType : ObjectType.values()) {
- comboBox.addItem(objectType);
+ for (ColumnType columnType : ColumnType.getTypes()) {
+ comboBox.addItem(columnType);
}
this.rowsTable.getColumn("Column Type").setCellEditor(new DefaultCellEditor(comboBox));
@@ -128,7 +128,7 @@ public void actionPerformed(ActionEvent e) {
int rowIndex = getRowIndex(rowsTable, 1, column);
if (rowIndex == -1) {
- tableModel.addRow(new Object[]{Boolean.TRUE, column, ObjectType.String, null});
+ tableModel.addRow(new Object[]{Boolean.TRUE, column, ColumnType.String, null});
rowIndex = tableModel.getRowCount() - 1;
}
@@ -156,14 +156,14 @@ public DataRow getRow() {
boolean use = (Boolean)this.rowsTable.getValueAt(i, 0);
if (use) {
ColumnQualifier columnQualifier = (ColumnQualifier)this.rowsTable.getValueAt(i, 1);
- ObjectType columnType = (ObjectType)this.rowsTable.getValueAt(i, 2);
- Object value = columnType.fromString((String)this.rowsTable.getValueAt(i, 3));
+ ColumnType columnType = (ColumnType)this.rowsTable.getValueAt(i, 2);
+ byte[] value = columnType.toBytes((String)this.rowsTable.getValueAt(i, 3));
if (columnQualifier.isKey()) {
- row.setKey(new TypedObject(columnType, value));
+ row.setKey(new ConvertibleObject(columnType, value));
}
- row.addCell(new DataCell(row, columnQualifier, new TypedObject(columnType, value)));
+ row.addCell(new DataCell(row, columnQualifier, new ConvertibleObject(columnType, value)));
}
}
return row;
@@ -209,9 +209,9 @@ private void onOK() {
if (use) {
value = (String)this.rowsTable.getValueAt(i, 3);
qualifier = (ColumnQualifier)this.rowsTable.getValueAt(i, 1);
- ObjectType valueType = (ObjectType)this.rowsTable.getValueAt(i, 2);
+ ColumnType valueType = (ColumnType)this.rowsTable.getValueAt(i, 2);
- valueType.toObject(value);
+ valueType.toBytes(value);
}
}
diff --git a/src/main/java/hrider/ui/forms/ConnectionDetailsDialog.form b/src/main/java/hrider/ui/forms/ConnectionDetailsDialog.form
index f211e3f..d598d46 100644
--- a/src/main/java/hrider/ui/forms/ConnectionDetailsDialog.form
+++ b/src/main/java/hrider/ui/forms/ConnectionDetailsDialog.form
@@ -8,7 +8,7 @@
-
+
@@ -16,10 +16,10 @@
-
+
-
+
@@ -44,7 +44,7 @@
-
+
diff --git a/src/main/java/hrider/ui/forms/ConnectionDetailsDialog.java b/src/main/java/hrider/ui/forms/ConnectionDetailsDialog.java
index df37a21..e8cccbf 100644
--- a/src/main/java/hrider/ui/forms/ConnectionDetailsDialog.java
+++ b/src/main/java/hrider/ui/forms/ConnectionDetailsDialog.java
@@ -32,6 +32,8 @@
public class ConnectionDetailsDialog extends JDialog {
//region Variables
+ private static final long serialVersionUID = 4254076848011995814L;
+
private JPanel contentPane;
private JButton buttonConnect;
private JButton buttonCancel;
@@ -104,34 +106,34 @@ public ConnectionDetails getConnectionDetails() {
//region Private Methods
private void onOK() {
- this.contentPane.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
-
this.connectionDetails = new ConnectionDetails() {{
setZookeeper(
new ServerDetails(
ConnectionDetailsDialog.this.zooKeeperServer.getText(), ConnectionDetailsDialog.this.zooKeeperPort.getValue().toString()));
}};
- try {
+ this.contentPane.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
- ConnectionManager.create(this.connectionDetails);
+ boolean canConnect = false;
- GlobalConfig.instance().set("connection.zookeeper.defaultPort", this.zooKeeperPort.getValue().toString());
- GlobalConfig.instance().save();
+ try {
+ canConnect = this.connectionDetails.canConnect();
+ }
+ finally {
+ this.contentPane.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ if (canConnect) {
dispose();
}
- catch (Exception ex) {
+ else {
JOptionPane.showMessageDialog(
- this, String.format(
- "%s\n\nMake sure you have access to all nodes of the cluster you try\nto connect to. In case you don't, map the nodes in your hosts file.",
- ex.getMessage()), "Failed to connect...", JOptionPane.ERROR_MESSAGE);
+ ConnectionDetailsDialog.this,
+ "Failed to connect to hbase.\n\nMake sure you have access to all hadoop nodes\nIn case you don't, map the nodes in your hosts file.",
+ "Connection failed...", JOptionPane.ERROR_MESSAGE);
- ConnectionManager.release(this.connectionDetails);
- this.connectionDetails = null;
- }
- finally {
- this.contentPane.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ ConnectionManager.release(connectionDetails);
+ connectionDetails = null;
}
}
@@ -159,16 +161,16 @@ private void onCancel() {
contentPane = new JPanel();
contentPane.setLayout(new GridLayoutManager(2, 1, new Insets(10, 10, 10, 10), -1, -1));
final JPanel panel1 = new JPanel();
- panel1.setLayout(new GridLayoutManager(2, 1, new Insets(5, 0, 0, 0), -1, -1));
+ panel1.setLayout(new GridLayoutManager(2, 2, new Insets(5, 0, 0, 0), -1, -1));
contentPane.add(
panel1, new GridConstraints(
1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
1, null, null, null, 0, false));
final JPanel panel2 = new JPanel();
- panel2.setLayout(new GridLayoutManager(1, 2, new Insets(0, 0, 0, 0), -1, -1, true, false));
+ panel2.setLayout(new GridLayoutManager(1, 2, new Insets(0, 0, 0, 0), -1, -1));
panel1.add(
panel2, new GridConstraints(
- 1, 0, 1, 1, GridConstraints.ANCHOR_EAST, GridConstraints.FILL_VERTICAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
+ 1, 1, 1, 1, GridConstraints.ANCHOR_EAST, GridConstraints.FILL_VERTICAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));
buttonConnect = new JButton();
buttonConnect.setText("Connect");
@@ -185,7 +187,7 @@ buttonCancel, new GridConstraints(
final JSeparator separator1 = new JSeparator();
panel1.add(
separator1, new GridConstraints(
- 0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_WANT_GROW,
+ 0, 0, 1, 2, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_WANT_GROW,
null, null, null, 0, false));
final JPanel panel3 = new JPanel();
panel3.setLayout(new GridLayoutManager(1, 4, new Insets(0, 0, 0, 0), -1, -1));
diff --git a/src/main/java/hrider/ui/forms/CustomConverterDialog.form b/src/main/java/hrider/ui/forms/CustomConverterDialog.form
new file mode 100644
index 0000000..f2e4e1c
--- /dev/null
+++ b/src/main/java/hrider/ui/forms/CustomConverterDialog.form
@@ -0,0 +1,202 @@
+
+
diff --git a/src/main/java/hrider/ui/forms/CustomConverterDialog.java b/src/main/java/hrider/ui/forms/CustomConverterDialog.java
new file mode 100644
index 0000000..e30847e
--- /dev/null
+++ b/src/main/java/hrider/ui/forms/CustomConverterDialog.java
@@ -0,0 +1,449 @@
+package hrider.ui.forms;
+
+import com.intellij.uiDesigner.core.GridConstraints;
+import com.intellij.uiDesigner.core.GridLayoutManager;
+import com.intellij.uiDesigner.core.Spacer;
+import hrider.config.GlobalConfig;
+import hrider.converters.TypeConverter;
+import hrider.io.Log;
+import hrider.io.PathHelper;
+import hrider.reflection.JavaCompiler;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.*;
+import java.io.File;
+import java.util.regex.Pattern;
+
+public class CustomConverterDialog extends JDialog {
+
+ //region Constants
+ private final static String IMPORTS_PLACE_HOLDER = "$$IMPORTS_PLACE_HOLDER$$";
+ private final static String CONVERTER_NAME_PLACE_HOLDER = "$$CONVERTER_NAME_PLACE_HOLDER$$";
+ private final static String BYTES_TO_STRING_CODE_PLACE_HOLDER = "$$BYTES_TO_STRING_CODE_PLACE_HOLDER$$";
+ private final static String STRING_TO_BYTES_CODE_PLACE_HOLDER = "$$STRING_TO_BYTES_CODE_PLACE_HOLDER$$";
+ private final static String IS_VALID_FOR_NAME_CONVERSION_PLACE_HOLDER = "$$IS_VALID_FOR_NAME_CONVERSION_PLACE_HOLDER$$";
+
+ private final static String TEMPLATE = "package hrider.converters.custom;\n" +
+ '\n' +
+ "$$IMPORTS_PLACE_HOLDER$$\n" +
+ '\n' +
+ "public class $$CONVERTER_NAME_PLACE_HOLDER$$ extends hrider.converters.TypeConverter {\n" +
+ '\n' +
+ " /**\n" +
+ " * Converts an array of bytes to String.\n" +
+ " */\n" +
+ " @Override\n" +
+ " public String toString(byte[] value) {\n" +
+ " if (value == null) {\n" +
+ " return null;\n" +
+ " }\n" +
+ "$$BYTES_TO_STRING_CODE_PLACE_HOLDER$$\n" +
+ " }\n" +
+ '\n' +
+ " /**\n" +
+ " * Converts a String to an array of bytes.\n" +
+ " */\n" +
+ " @Override\n" +
+ " public byte[] toBytes(String value) {\n" +
+ " if (value == null) {\n" +
+ " return EMPTY_BYTES_ARRAY;\n" +
+ " }\n" +
+ "$$STRING_TO_BYTES_CODE_PLACE_HOLDER$$\n" +
+ " }\n" +
+ '\n' +
+ " /**\n" +
+ " * Indicates whether the type converter can be used for column name conversions.\n" +
+ " */\n" +
+ " @Override\n" +
+ " public boolean isValidForNameConversion() {\n" +
+ " return $$IS_VALID_FOR_NAME_CONVERSION_PLACE_HOLDER$$;\n" +
+ " }" +
+ "}\n";
+
+ private static final long serialVersionUID = -8239690036592242474L;
+ private final static Log logger = Log.getLogger(PathHelper.class);
+ //endregion
+
+ //region Variables
+ private JPanel contentPane;
+ private JButton buttonOK;
+ private JButton buttonCancel;
+ private JTextField tfConverterName;
+ private JTextArea taBytesToObjectMethod;
+ private JTextArea taImports;
+ private JTextArea taObjectToBytesMethod;
+ private JCheckBox chbNameConverter;
+ private boolean okPressed;
+ //endregion
+
+ //region Constructor
+ public CustomConverterDialog(TypeConverter converter) {
+ setContentPane(contentPane);
+ setModal(true);
+ setTitle(converter == null ? "Create custom type converter" : "Edit custom type converter");
+ getRootPane().setDefaultButton(buttonOK);
+
+ if (converter != null) {
+ tfConverterName.setText(converter.getClass().getSimpleName());
+ chbNameConverter.setSelected(converter.isValidForNameConversion());
+
+ loadImports(converter.getCode());
+ loadMethods(converter.getCode());
+ }
+ else {
+ tfConverterName.select(0, 6);
+ }
+
+ buttonOK.addActionListener(
+ new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ onOK();
+ }
+ });
+
+ buttonCancel.addActionListener(
+ new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ onCancel();
+ }
+ });
+
+ // call onCancel() when cross is clicked
+ setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
+ addWindowListener(
+ new WindowAdapter() {
+ @Override
+ public void windowClosing(WindowEvent e) {
+ onCancel();
+ }
+ });
+
+ // call onCancel() on ESCAPE
+ contentPane.registerKeyboardAction(
+ new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ onCancel();
+ }
+ }, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
+ }
+ //endregion
+
+ //region Public Methods
+ public boolean showDialog(Component owner) {
+ this.setComponentOrientation(owner.getComponentOrientation());
+ this.pack();
+ this.setResizable(false);
+ this.setLocationRelativeTo(owner);
+ this.setVisible(true);
+
+ return this.okPressed;
+ }
+
+ public String getConverterName() {
+ return tfConverterName.getText().replace("Converter", "");
+ }
+ //endregion
+
+ //region Private Methods
+ private void loadImports(String code) {
+ StringBuilder imports = new StringBuilder();
+
+ for (String line : code.replace(" ", " ").split(Pattern.quote(PathHelper.LINE_SEPARATOR))) {
+ String trimmedLine = line.trim();
+ if (trimmedLine.startsWith("import")) {
+ imports.append(trimmedLine);
+ imports.append(PathHelper.LINE_SEPARATOR);
+ }
+ }
+
+ taImports.setText(imports.toString());
+ }
+
+ private void loadMethods(String code) {
+ Methods method = Methods.None;
+
+ int skipCount = 0;
+ int bracketsCount = 0;
+
+ StringBuilder body = new StringBuilder();
+
+ for (String line : code.replace(" ", " ").split(Pattern.quote(PathHelper.LINE_SEPARATOR))) {
+ String trimmedLine = line.trim();
+
+ if (trimmedLine.startsWith("public String toString(byte[] value) {")) {
+ bracketsCount++;
+ method = Methods.BytesToString;
+
+ continue;
+ }
+
+ if (trimmedLine.startsWith("public byte[] toBytes(String value) {")) {
+ bracketsCount++;
+ method = Methods.StringToBytes;
+
+ continue;
+ }
+
+ if (method != Methods.None) {
+ if (trimmedLine.endsWith("{")) {
+ bracketsCount++;
+ }
+
+ if (trimmedLine.endsWith("}")) {
+ bracketsCount--;
+ }
+
+ if (trimmedLine.startsWith("if (value == null) {")) {
+ skipCount++;
+ }
+
+ if (bracketsCount == 0) {
+ setMethodBody(method, body);
+
+ method = Methods.None;
+ skipCount = 0;
+ }
+ else {
+ if (skipCount == 0 || skipCount == 4) {
+ if (body.length() > 0) {
+ body.append(PathHelper.LINE_SEPARATOR);
+ }
+
+ body.append(indent(bracketsCount));
+ body.append(trimmedLine);
+ }
+ else {
+ skipCount++;
+ }
+ }
+ }
+ }
+ }
+
+ private void setMethodBody(Methods method, StringBuilder body) {
+ if (method != Methods.None) {
+ switch (method) {
+ case None:
+ break;
+ case BytesToString:
+ taBytesToObjectMethod.setText(body.toString());
+ break;
+ case StringToBytes:
+ taObjectToBytesMethod.setText(body.toString());
+ break;
+ }
+
+ body.setLength(0);
+ }
+ }
+
+ private static String indent(int length) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0 ; i < length ; i++) {
+ sb.append(" ");
+ }
+ return sb.toString();
+ }
+
+ private void onOK() {
+ // add your code here
+
+ String code = TEMPLATE;
+
+ code = code.replace(IMPORTS_PLACE_HOLDER, taImports.getText());
+ code = code.replace(CONVERTER_NAME_PLACE_HOLDER, tfConverterName.getText());
+ code = code.replace(BYTES_TO_STRING_CODE_PLACE_HOLDER, taBytesToObjectMethod.getText());
+ code = code.replace(STRING_TO_BYTES_CODE_PLACE_HOLDER, taObjectToBytesMethod.getText());
+ code = code.replace(IS_VALID_FOR_NAME_CONVERSION_PLACE_HOLDER, chbNameConverter.isSelected() ? "true" : "false");
+
+ contentPane.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+ try {
+ File sourceCode = JavaCompiler.saveCode(tfConverterName.getText(), code, GlobalConfig.instance().getConvertersCodeFolder());
+ if (JavaCompiler.compile(sourceCode, GlobalConfig.instance().getConvertersClassesFolder())) {
+ this.okPressed = true;
+
+ dispose();
+ }
+ else {
+ StringBuilder message = new StringBuilder();
+ for (String error : JavaCompiler.getErrors()) {
+ message.append(error);
+ }
+
+ JOptionPane.showMessageDialog(contentPane, message, "Compilation Errors", JOptionPane.ERROR_MESSAGE);
+ }
+ }
+ catch (Exception e) {
+ logger.error(e, "Failed to compile code.");
+ JOptionPane.showMessageDialog(contentPane, e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
+ }
+ finally {
+ contentPane.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ }
+
+ private void onCancel() {
+ // add your code here if necessary
+ dispose();
+ }
+
+ {
+ // GUI initializer generated by IntelliJ IDEA GUI Designer
+ // >>> IMPORTANT!! <<<
+ // DO NOT EDIT OR ADD ANY CODE HERE!
+ $$$setupUI$$$();
+ }
+
+ /**
+ * Method generated by IntelliJ IDEA GUI Designer
+ * >>> IMPORTANT!! <<<
+ * DO NOT edit this method OR call it in your code!
+ *
+ * @noinspection ALL
+ */
+ private void $$$setupUI$$$() {
+ contentPane = new JPanel();
+ contentPane.setLayout(new GridLayoutManager(4, 1, new Insets(10, 10, 10, 10), -1, -1));
+ final JPanel panel1 = new JPanel();
+ panel1.setLayout(new GridLayoutManager(1, 2, new Insets(0, 0, 0, 0), -1, -1));
+ contentPane.add(
+ panel1, new GridConstraints(
+ 3, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
+ 1, null, null, null, 0, false));
+ final Spacer spacer1 = new Spacer();
+ panel1.add(
+ spacer1, new GridConstraints(
+ 0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, 1, null, null, null, 0, false));
+ final JPanel panel2 = new JPanel();
+ panel2.setLayout(new GridLayoutManager(1, 2, new Insets(0, 0, 0, 0), -1, -1, true, false));
+ panel1.add(
+ panel2, new GridConstraints(
+ 0, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
+ GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));
+ buttonOK = new JButton();
+ buttonOK.setText("Save");
+ panel2.add(
+ buttonOK, new GridConstraints(
+ 0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL,
+ GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
+ buttonCancel = new JButton();
+ buttonCancel.setText("Cancel");
+ panel2.add(
+ buttonCancel, new GridConstraints(
+ 0, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL,
+ GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
+ final JPanel panel3 = new JPanel();
+ panel3.setLayout(new GridLayoutManager(9, 1, new Insets(0, 0, 0, 0), -1, -1));
+ contentPane.add(
+ panel3, new GridConstraints(
+ 1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
+ GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));
+ final JScrollPane scrollPane1 = new JScrollPane();
+ panel3.add(
+ scrollPane1, new GridConstraints(
+ 3, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW,
+ GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, null, new Dimension(400, 70), null, 0, false));
+ taBytesToObjectMethod = new JTextArea();
+ taBytesToObjectMethod.setText(" return Bytes.toString(value); // replace with your code");
+ scrollPane1.setViewportView(taBytesToObjectMethod);
+ final JLabel label1 = new JLabel();
+ label1.setText("Imports:");
+ panel3.add(
+ label1, new GridConstraints(
+ 0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null,
+ null, 0, false));
+ final JScrollPane scrollPane2 = new JScrollPane();
+ panel3.add(
+ scrollPane2, new GridConstraints(
+ 1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW,
+ GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, null, new Dimension(400, 40), null, 0, false));
+ taImports = new JTextArea();
+ taImports.setText("import org.apache.hadoop.hbase.util.Bytes;");
+ scrollPane2.setViewportView(taImports);
+ final JLabel label2 = new JLabel();
+ label2.setText("public String toString(byte[] value) { // bytes to string conversion");
+ panel3.add(
+ label2, new GridConstraints(
+ 2, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null,
+ null, 0, false));
+ final JLabel label3 = new JLabel();
+ label3.setText("}");
+ panel3.add(
+ label3, new GridConstraints(
+ 4, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null,
+ null, 0, false));
+ final JLabel label4 = new JLabel();
+ label4.setText("public byte[] toBytes(String value) { // string to bytes conversion");
+ panel3.add(
+ label4, new GridConstraints(
+ 5, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null,
+ null, 0, false));
+ final JScrollPane scrollPane3 = new JScrollPane();
+ panel3.add(
+ scrollPane3, new GridConstraints(
+ 6, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW,
+ GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, null, new Dimension(400, 70), null, 0, false));
+ taObjectToBytesMethod = new JTextArea();
+ taObjectToBytesMethod.setText(" return Bytes.toBytes(value); // replace with your code");
+ scrollPane3.setViewportView(taObjectToBytesMethod);
+ final JLabel label5 = new JLabel();
+ label5.setText("}");
+ panel3.add(
+ label5, new GridConstraints(
+ 7, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null,
+ null, 0, false));
+ chbNameConverter = new JCheckBox();
+ chbNameConverter.setSelected(true);
+ chbNameConverter.setText("The converter can be used for column name conversions as well.");
+ panel3.add(
+ chbNameConverter, new GridConstraints(
+ 8, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
+ GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
+ final JPanel panel4 = new JPanel();
+ panel4.setLayout(new GridLayoutManager(1, 2, new Insets(0, 0, 0, 0), -1, -1));
+ contentPane.add(
+ panel4, new GridConstraints(
+ 0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,
+ GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));
+ final JLabel label6 = new JLabel();
+ label6.setText("Converter Name:");
+ panel4.add(
+ label6, new GridConstraints(
+ 0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null,
+ null, 0, false));
+ tfConverterName = new JTextField();
+ tfConverterName.setText("CustomConverter");
+ panel4.add(
+ tfConverterName, new GridConstraints(
+ 0, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED,
+ null, new Dimension(150, 24), null, 0, false));
+ final JSeparator separator1 = new JSeparator();
+ contentPane.add(
+ separator1, new GridConstraints(
+ 2, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_WANT_GROW,
+ null, null, null, 0, false));
+ label1.setLabelFor(taImports);
+ label2.setLabelFor(taBytesToObjectMethod);
+ label4.setLabelFor(taObjectToBytesMethod);
+ label6.setLabelFor(tfConverterName);
+ }
+
+ /**
+ * @noinspection ALL
+ */
+ public JComponent $$$getRootComponent$$$() {
+ return contentPane;
+ }
+
+ //endregion
+
+ private enum Methods {
+ None,
+ BytesToString,
+ StringToBytes
+ }
+}
diff --git a/src/main/java/hrider/ui/forms/ExportTableDialog.java b/src/main/java/hrider/ui/forms/ExportTableDialog.java
index 4fdb03a..2369df6 100644
--- a/src/main/java/hrider/ui/forms/ExportTableDialog.java
+++ b/src/main/java/hrider/ui/forms/ExportTableDialog.java
@@ -3,13 +3,19 @@
import com.intellij.uiDesigner.core.GridConstraints;
import com.intellij.uiDesigner.core.GridLayoutManager;
import com.intellij.uiDesigner.core.Spacer;
+import hrider.actions.*;
+import hrider.actions.Action;
import hrider.config.GlobalConfig;
import hrider.data.ColumnQualifier;
+import hrider.data.ColumnType;
import hrider.data.DataRow;
-import hrider.data.ObjectType;
import hrider.data.TypedColumn;
import hrider.export.FileExporter;
-import hrider.hbase.*;
+import hrider.hbase.Connection;
+import hrider.hbase.HbaseActionListener;
+import hrider.hbase.QueryScanner;
+import hrider.hbase.Scanner;
+import hrider.io.Log;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.client.Result;
@@ -43,6 +49,10 @@
*/
public class ExportTableDialog extends JDialog {
+ //region Constants
+ private static final Log logger = Log.getLogger(ExportTableDialog.class);
+ //endregion
+
//region Variables
private JPanel contentPane;
private JButton btExport;
@@ -56,7 +66,7 @@ public class ExportTableDialog extends JDialog {
private JButton btClose;
private JButton btExportWithQueryButton;
private JComboBox cmbFileType;
- private JLabel labelDelimiter;
+ private JLabel labelDelimiter;
private String filePath;
private boolean canceled;
//endregion
@@ -90,7 +100,7 @@ public void actionPerformed(ActionEvent e) {
try {
Collection columns = new ArrayList();
for (ColumnQualifier column : scanner.getColumns(100)) {
- columns.add(new TypedColumn(column, ObjectType.String));
+ columns.add(new TypedColumn(column, ColumnType.String));
}
ScanDialog dialog = new ScanDialog(null, columns);
@@ -202,17 +212,25 @@ public void actionPerformed(ActionEvent e) {
}
}, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
- new Thread(
- new Runnable() {
- @Override
- public void run() {
- try {
- totalRowsCount.setText(String.valueOf(scanner.getRowsCount()));
- }
- catch (Exception ignore) {
- }
+ RunnableAction.run(
+ "export table rows count", new Action
diff --git a/src/main/java/hrider/ui/forms/Window.java b/src/main/java/hrider/ui/forms/Window.java
index 9f4fdf4..54d4959 100644
--- a/src/main/java/hrider/ui/forms/Window.java
+++ b/src/main/java/hrider/ui/forms/Window.java
@@ -2,6 +2,8 @@
import com.intellij.uiDesigner.core.GridConstraints;
import com.intellij.uiDesigner.core.GridLayoutManager;
+import hrider.actions.Action;
+import hrider.actions.RunnableAction;
import hrider.config.ClusterConfig;
import hrider.config.ConnectionDetails;
import hrider.config.PropertiesConfig;
@@ -9,11 +11,18 @@
import hrider.data.DataTable;
import hrider.hbase.Connection;
import hrider.hbase.ConnectionManager;
+import hrider.io.CloseableHelper;
+import hrider.io.Downloader;
+import hrider.io.FileHelper;
+import hrider.io.Log;
+import hrider.system.Clipboard;
import hrider.system.ClipboardData;
import hrider.system.InMemoryClipboard;
+import hrider.system.Version;
import hrider.ui.MessageHandler;
import hrider.ui.MessageHandlerListener;
import hrider.ui.TabClosedListener;
+import hrider.ui.UIAction;
import hrider.ui.controls.JCloseButton;
import hrider.ui.controls.JLinkButton;
import hrider.ui.views.DesignerView;
@@ -22,13 +31,12 @@
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.jar.Attributes;
-import java.util.jar.JarFile;
+import java.awt.event.WindowEvent;
+import java.io.*;
+import java.net.URL;
+import java.util.*;
+import java.util.jar.Manifest;
+import java.util.regex.Pattern;
/**
* Copyright (C) 2012 NICE Systems ltd.
@@ -50,36 +58,114 @@
*/
public class Window {
+ //region Constants
+ private static final String UPDATE_FILE_URL = "https://raw.github.com/NiceSystems/hrider/master/update.properties";
+ private static final Log logger = Log.getLogger(Window.class);
+ //endregion
+
//region Variables
private JPanel topPanel;
- private JTextArea statusLabel;
private JTabbedPane tabbedPane;
private JLinkButton connectToCluster;
+ private JPanel actionPanel;
+ private JLabel actionLabel1;
+ private JLinkButton actionLabel2;
+ private JLabel actionLabel3;
+ private JButton copyToClipboard;
+ private JLinkButton newVersionAvailable;
private boolean canceled;
+ private UIAction uiAction;
+ private String lastError;
private Map viewMap;
+ private Properties updateInfo;
+ private JFrame frame;
//endregion
//region Constructor
public Window() {
+ this.updateInfo = new Properties();
this.viewMap = new HashMap();
+ Font font = this.actionLabel1.getFont();
+ this.actionLabel1.setFont(font.deriveFont(font.getStyle() | ~Font.BOLD));
+ this.actionLabel2.setFont(font.deriveFont(font.getStyle() | ~Font.BOLD));
+ this.actionLabel3.setFont(font.deriveFont(font.getStyle() | ~Font.BOLD));
+
MessageHandler.addListener(
new MessageHandlerListener() {
@Override
public void onInfo(String message) {
- statusLabel.setText(message);
- statusLabel.paintImmediately(statusLabel.getBounds());
+ lastError = null;
+ uiAction = null;
+
+ copyToClipboard.setVisible(false);
+
+ actionLabel1.setText(message);
+
+ // this trick allows to keep all labels on the same level. The empty text on the link button moves all labels up for a couple of pixels.
+ actionLabel2.setText(" ");
+ actionLabel2.setLinkColor(new Color(240, 240, 240)); // the control color which is not visible.
+
+ actionLabel3.setText("");
}
@Override
public void onError(String message, Exception ex) {
- String error = message;
+ uiAction = null;
+
+ copyToClipboard.setVisible(true);
+
+ String error = ' ' + message;
+ lastError = error;
+
if (ex != null) {
- error += ": " + ex.toString();
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+
+ ex.printStackTrace(pw);
+
+ lastError += ": " + sw.toString();
+ error += ": " + ex.getMessage();
+
+ pw.close();
}
- statusLabel.setText(error);
- statusLabel.paintImmediately(statusLabel.getBounds());
+ actionLabel1.setText(error);
+ actionLabel2.setText(" ");
+ actionLabel2.setLinkColor(new Color(240, 240, 240));
+ actionLabel3.setText("");
+ }
+
+ @Override
+ public void onAction(UIAction action) {
+ lastError = null;
+ uiAction = action;
+
+ copyToClipboard.setVisible(false);
+
+ String[] messageParts = action.getFormattedMessage();
+ if (messageParts.length > 0) {
+ actionLabel1.setText(messageParts[0]);
+ }
+ else {
+ actionLabel1.setText("");
+ }
+
+ if (messageParts.length > 1) {
+ actionLabel2.setText(messageParts[1]);
+ actionLabel2.setLinkColor(new Color(0, 0, 255)); // the blue link color.
+ }
+ else {
+ actionLabel2.setText(" ");
+ actionLabel2.setLinkColor(new Color(240, 240, 240));
+ }
+
+ if (messageParts.length > 2) {
+ actionLabel3.setText(messageParts[2]);
+ }
+ else {
+ actionLabel3.setText("");
+ }
}
});
@@ -99,6 +185,66 @@ public void actionPerformed(ActionEvent e) {
}
}
});
+
+ this.actionLabel2.addActionListener(
+ new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ if (uiAction != null) {
+ uiAction.execute();
+ }
+ }
+ });
+
+ this.copyToClipboard.addActionListener(
+ new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ if (lastError != null) {
+ Clipboard.setText(lastError);
+ }
+ }
+ });
+
+ this.newVersionAvailable.addActionListener(
+ new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ UpdateDialog dialog = new UpdateDialog(updateInfo.getProperty("hrider.version"), getHbaseVersion());
+ if (dialog.showDialog(topPanel)) {
+ try {
+ File jar = FileHelper.findFile(
+ new File("."), Pattern.compile("h-rider-updater-?[0-9]{0,4}\\.?[0-9]{0,4}\\.?[0-9]{0,4}\\.?[0-9]{0,4}\\.jar"));
+
+ File temporary = File.createTempFile("h-rider-updater-", ".jar");
+ FileHelper.copy(jar, temporary);
+
+ String url = updateInfo.getProperty("hbase." + getHbaseVersion() + ".reduced");
+ if (url == null) {
+ url = updateInfo.getProperty("hbase." + getHbaseVersion());
+ }
+
+ Runtime.getRuntime().exec(
+ String.format("java -jar %s \"%s\" \"%s\"", temporary.getName(), url, jar.getParentFile().getAbsolutePath()), null,
+ temporary.getParentFile());
+
+ frame.dispatchEvent(new WindowEvent(frame, WindowEvent.WINDOW_CLOSING));
+ }
+ catch (IOException ex) {
+ JOptionPane.showMessageDialog(topPanel, ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
+ }
+ }
+ }
+ });
+
+ RunnableAction.run(
+ "compare-versions", new Action