public abstract class Action<R> {

    /**
     * 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) {

    }
} public class RunnableAction<R> implements Runnable {

    //region Constants
    private final static Log logger = Log.getLogger(RunnableAction.class);
    //endregion

    //region Variables
    private String    name;
    private Action<R> action;
    private Thread    thread;
    private boolean   isRunning;
    private boolean   interrupted;
    private R         result;
    //endregion

    //region Constructor
    public RunnableAction(String name, Action<R> 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 <R> RunnableAction<R> run(String name, Action<R> action) {
        RunnableAction<R> runnableAction = new RunnableAction<R>(name, action);
        runnableAction.start();

        return runnableAction;
    }

    public static <R> R runAndWait(String name, Action<R> action, long timeout) {
        RunnableAction<R> runnableAction = new RunnableAction<R>(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
} public class ClusterConfig extends PropertiesConfig {

    //region Constructor

    /**
     * Initializes a new instance of the {@link ClusterConfig} class.
     *
     * @param name The name of the configuration file.
     */
    public ClusterConfig(String name) {
        super(name);
    }
    //endregion

    //region Public Methods

    /**
     * Gets a connection details used to connect to the hbase cluster.
     *
     * @return A reference to the {@link ConnectionDetails} class.
     */
    public ConnectionDetails getConnection() {
        final String zookeeperHost = get(String.class, "connection.zookeeper.host", null);
        final String zookeeperPort = get(String.class, "connection.zookeeper.port", null);

        if (zookeeperHost == null || zookeeperPort == null) {
            return null;
        }

        return new ConnectionDetails() {{
            setZookeeper(new ServerDetails(zookeeperHost, zookeeperPort));
        }};
    }

    /**
     * Saves a connection details to the configuration.
     *
     * @param connection A connection details to save.
     */
    public void setConnection(ConnectionDetails connection) {
        set("connection.zookeeper.host", connection.getZookeeper().getHost());
        set("connection.zookeeper.port", connection.getZookeeper().getPort());
    }

    /**
     * Gets a configuration data related to the specified table.
     *
     * @param clazz The class that represents the type of the data to be returned.
     * @param table The name of the table.
     * @param <T>   The type of the data to be returned.
     * @return The data of the specified type.
     */
    public <T> T getTableConfig(Class<T> clazz, String table) {
        return get(clazz, String.format("table.%s", 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 key   The key to identify the data.
     * @param <T>   The type of the data to be returned.
     * @return The data of the specified type.
     */
    public <T> T getTableConfig(Class<T> clazz, String table, String key) {
        return get(clazz, String.format("table.%s.%s", table, key));
    }

    /**
     * Gets a configuration data related to the specified table of the provided column and a corresponding key.
     *
     * @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 key    The key to identify the data.
     * @param <T>    The type of the data to be returned.
     * @return The data of the specified type.
     */
    public <T> T getTableConfig(Class<T> clazz, String table, String column, String key) {
        return get(clazz, String.format("table.%s.%s.%s", table, column, key));
    }

    /**
     * Sets a configuration data for the specified table.
     *
     * @param table The name of the table.
     * @param value The data to set.
     */
    public void setTableConfig(String table, String value) {
        set(String.format("table.%s", table), value);
    }

    /**
     * Sets a configuration data for the specified table and column.
     *
     * @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 key, String value) {
        set(String.format("table.%s.%s", table, key), value);
    }

    /**
     * Sets a configuration data for the specified table, column and key.
     *
     * @param table  The name of the table.
     * @param column The name of the column.
     * @param key    The additional identifier.
     * @param value  The data to set.
     */
    public void setTableConfig(String table, String column, String key, String value) {
        set(String.format("table.%s.%s.%s", table, column, key), value);
    }

    /**
     * Gets a list of table filters.
     *
     * @return A list of table filters.
     */
    public List<String> getTableFilters() {
        String filters = get(String.class, "table.filters");
        if (filters != null && !filters.isEmpty()) {
            String[] parts = filters.split(";");
            return Arrays.asList(parts);
        }
        return new ArrayList<String>();
    }

    /**
     * Gets a previously used table filter.
     *
     * @return A filter.
     */
    public String getSelectedTableFilter() {
        return get(String.class, "table.filters.selected");
    }

    /**
     * Gets a list of column filters.
     *
     * @param table The name of the table.
     * @return A list of column filters.
     */
    public List<String> getColumnFilters(String table) {
        String filters = get(String.class, String.format("table.%s.column.filters", table));
        if (filters != null && !filters.isEmpty()) {
            String[] parts = filters.split(";");
            return Arrays.asList(parts);
        }
        return new ArrayList<String>();
    }

    /**
     * Gets a previously used column filter.
     *
     * @param table The name of the table.
     * @return A filter.
     */
    public String getSelectedColumnFilter(String table) {
        return get(String.class, String.format("table.%s.column.filters.selected", table));
    }

    /**
     * Saves table filter(s).
     *
     * @param filters Filter(s) to save.
     */
    public void setTablesFilter(Iterable<String> filters) {
        StringBuilder sb = new StringBuilder();
        for (String filter : filters) {
            if (sb.length() > 0) {
                sb.append(';');
            }
            sb.append(filter);
        }

        set("table.filters", sb.toString());
    }

    /**
     * Saves currently used table filter.
     *
     * @param filter The filter to save.
     */
    public void setSelectedTableFilter(String filter) {
        set("table.filters.selected", filter);
    }

    /**
     * Saves column filter(s).
     *
     * @param table   The name of the table.
     * @param filters Filter(s) to save.
     */
    public void setColumnsFilter(String table, Iterable<String> filters) {
        StringBuilder sb = new StringBuilder();
        for (String filter : filters) {
            if (sb.length() > 0) {
                sb.append(';');
            }
            sb.append(filter);
        }

        set(String.format("table.%s.column.filters", table), sb.toString());
    }

    /**
     * Saves currently used column filter.
     *
     * @param table  The name of the table.
     * @param filter The filter to save.
     */
    public void setSelectedColumnFilter(String table, String filter) {
        set(String.format("table.%s.column.filters.selected", table), filter);
    }
    //endregion
} public class GlobalConfig extends PropertiesConfig {

    //region Constants
    private static final String KEY_DATE_FORMAT                    = "global.dateFormat";
    private static final String KEY_DATE_TIME_ZONE                 = "global.dateTimeZone";
    private static final String KEY_EXTERNAL_VIEWER_FILE_EXTENSION = "global.externalViewerFileExtension";
    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_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_DATE_TIME_ZONE                 = "UTC";
    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_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
    private static GlobalConfig instance;
    //endregion

    //region Constructor

    static {
        instance = new GlobalConfig();
    }

    /**
     * Initializes a new instance of the {@link PropertiesConfig} class with a file name.
     */
    private GlobalConfig() {
        super("global");
    }
    //endregion

    //region Public Methods
    public static GlobalConfig instance() {
        return instance;
    }

    /**
     * Gets the date time format to be used to parse/converters date time strings.
     *
     * @return A {@link String} representing date time format.
     */
    public String getDateFormat() {
        return get(String.class, KEY_DATE_FORMAT, DEFAULT_DATE_FORMAT);
    }

    /**
     * Gets the date time zone to be used to parse/converters date time strings.
     *
     * @return A {@link String} representing date time zone.
     */
    public String getDateTimeZone() {
        return get(String.class, KEY_DATE_TIME_ZONE, DEFAULT_DATE_TIME_ZONE);
    }

    /**
     * Gets an extension to be used for a file to be created for the external viewer.
     *
     * @return A {@link String} representing a file extension.
     */
    public String getExternalViewerFileExtension() {
        return get(String.class, KEY_EXTERNAL_VIEWER_FILE_EXTENSION, DEFAULT_EXTERNAL_VIEWER_FILE_EXTENSION);
    }

    /**
     * Gets a delimiter to be used in the file to separate the data.
     *
     * @return A character to be used as a delimiter to separate the data in the file.
     */
    public char getExternalViewerDelimeter() {
        return get(Character.class, KEY_EXTERNAL_VIEWER_DELIMETER, DEFAULT_EXTERNAL_VIEWER_DELIMETER);
    }

    /**
     * Gets a size of the batch to be used for read operations.
     *
     * @return A size of the batch.
     */
    public int getBatchSizeForRead() {
        return get(Integer.class, KEY_BATCH_READ_SIZE, DEFAULT_BATCH_READ_SIZE);
    }

    /**
     * Gets a size of the batch to be used for write operations.
     *
     * @return A size of the batch.
     */
    public int getBatchSizeForWrite() {
        return get(Integer.class, KEY_BATCH_WRITE_SIZE, DEFAULT_BATCH_WRITE_SIZE);
    }

    /**
     * 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 the 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_DATE_TIME_ZONE, DEFAULT_DATE_TIME_ZONE);
        set(KEY_EXTERNAL_VIEWER_DELIMETER, DEFAULT_EXTERNAL_VIEWER_DELIMETER);
        set(KEY_EXTERNAL_VIEWER_FILE_EXTENSION, DEFAULT_EXTERNAL_VIEWER_FILE_EXTENSION);

        save();
    }
    //endregion
} @SuppressWarnings({"CallToPrintStackTrace", "ResultOfMethodCallIgnored"})
public class ConvertersLoader {

    //region Variables
    private static final Log logger = Log.getLogger(ConvertersLoader.class);
    private static final List<ConvertersLoaderHandler> handlers;
    private static       Map<String, TypeConverter>    converters;
    //endregion

    //region Constructor
    static {
        handlers = new ArrayList<ConvertersLoaderHandler>();
        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<TypeConverter> getConverters() {
        return converters.values();
    }

    /**
     * Gets all loaded converters that support column name conversions.
     *
     * @return A list of type converters.
     */
    public static Collection<TypeConverter> getNameConverters() {
        Collection<TypeConverter> list = new ArrayList<TypeConverter>();
        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<String, TypeConverter> load() {
        Map<String, TypeConverter> map = new TreeMap<String, TypeConverter>();

        loadPackage("hrider.converters", map);
        loadFolder(GlobalConfig.instance().getConvertersClassesFolder(), "hrider.converters.custom", map);

        return map;
    }

    private static void loadPackage(String packageName, Map<String, TypeConverter> 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<String, TypeConverter> 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
} 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);
} @SuppressWarnings("CallToSimpleGetterFromWithinClass")
public abstract class TypeConverter implements Comparable<TypeConverter>, 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);

    /**
     * Checks whether the provided value can be converted to the type supported by this converter.
     *
     * @param value The value to check.
     * @return True if the value can be converted by the converter to the required type of False otherwise.
     */
    public abstract boolean canConvert(byte[] value);

    /**
     * Indicates whether the type converter supports formatting of the data.
     *
     * @return True if the type converter can be used to format the data or False otherwise.
     */
    public abstract boolean supportsFormatting();

    /**
     * Gets the mapping between the regular expression and the color to be used for drawing the text.
     *
     * @return The color to regular expression mappings.
     */
    public Map<Pattern, Color> getColorMappings() {
        return null;
    }

    /**
     * Apply the custom formatting on the data.
     *
     * @param value The data to format.
     * @return The formatted data.
     */
    public String format(String value) {
        return 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
} public class FileExporter implements Exporter {

    //region Constants
    private static final byte[] LINE_SEPARATOR = System.getProperty("line.separator").getBytes();
    //endregion

    //region Variables
    private Character    delimiter;
    private OutputStream stream;
    private boolean      headerWritten;
    //endregion

    //region Constructor

    /**
     * Initializes a new instance of the {@link FileExporter} class.
     *
     * @param stream    An output stream to write to.
     * @param delimiter A character to be used as a data delimiter.
     */
    public FileExporter(OutputStream stream, char delimiter) {
        this.stream = stream;
        this.delimiter = delimiter;
        this.headerWritten = false;
    }
    //endregion

    //region Public Methods

    /**
     * Writes a list of rows to the output stream.
     *
     * @param row A row to write.
     * @throws IOException
     */
    @Override
    public void write(DataRow row, Iterable<ColumnQualifier> columns) throws IOException {
        if (!this.headerWritten) {

            Formatter formatter = new CharacterDelimitedFormatter(this.delimiter);
            for (ColumnQualifier column : columns) {
                formatter.append(column);
            }

            this.stream.write(formatter.getFormattedString().getBytes());
            this.stream.write(LINE_SEPARATOR);

            this.headerWritten = true;
        }

        Formatter formatter = new CharacterDelimitedFormatter(this.delimiter);

        for (ColumnQualifier column : columns) {
            DataCell cell = row.getCell(column);
            if (cell != null) {
                formatter.append(cell.getValue());
            }
            else {
                formatter.append("");
            }
        }

        this.stream.write(formatter.getFormattedString().getBytes());
        this.stream.write(LINE_SEPARATOR);
    }
    //endregion
} public class EmptyFilter implements Filter {

    @Override
    public boolean match(String value) {
        return true;
    }
} public interface Filter {

    boolean match(String value);
} public class PatternFilter implements Filter {

    private Pattern regex;
    private boolean isNegative;

    public PatternFilter(String regex) {
        this.isNegative = regex.startsWith("~");

        if (this.isNegative) {
            this.regex = Pattern.compile(regex.substring(2, regex.length() - 1));
        }
        else {
            this.regex = Pattern.compile(regex);
        }
    }

    @Override
    public boolean match(String value) {
        boolean result = this.regex.matcher(value).find();
        if (isNegative) {
            return !result;
        }
        return result;
    }
} public class PathHelper {

    //region Constants
    public final static String FILE_SEPARATOR = System.getProperty("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
    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 normalise(".");
    }

    /**
     * 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) {
            Matcher matcher = ENV_VAR.matcher(expandedPath);
            while (matcher.find()) {
                String envVar = matcher.group();

                String expVar = System.getenv(envVar);
                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 normalise(expandedPath);
    }

    public static String append(String path1, String path2) {
        StringBuilder path = new StringBuilder();

        if (path1 != null) {
            path.append(path1);
            path.append(FILE_SEPARATOR);
        }

        if (path2 != null) {
            path.append(path2);
        }

        return expand(path.toString());
    }

    public static String getParent(String path) {
        if (path != null) {
            String normalisedPath = expand(path);
            if (normalisedPath.endsWith(FILE_SEPARATOR)) {
                normalisedPath = normalisedPath.substring(0, normalisedPath.length() - FILE_SEPARATOR.length());
            }

            int index = normalisedPath.lastIndexOf(FILE_SEPARATOR);
            if (index != -1) {
                return normalisedPath.substring(0, index);
            }
        }
        return path;
    }

    public static String getLeaf(String path) {
        if (path != null) {
            String normalisedPath = expand(path);
            if (normalisedPath.endsWith(FILE_SEPARATOR)) {
                normalisedPath = normalisedPath.substring(0, normalisedPath.length() - FILE_SEPARATOR.length());
            }

            int index = normalisedPath.lastIndexOf(FILE_SEPARATOR);
            if (index != -1) {
                return normalisedPath.substring(index + 1);
            }
        }
        return path;
    }

    public static String normalise(String path) {
        try {
            return new File(path).getCanonicalPath();
        }
        catch (IOException e) {
            logger.warn(e, "Failed to canonicalize the path: '%s'", path);
            return path;
        }
    }
    //endregion
} public class ClipboardData<T> {

    private T data;

    public ClipboardData(T data) {
        this.data = data;
    }

    public T getData() {
        return this.data;
    }
} public interface ClipboardListener {

    /**
     * This event is raised each time the clipboard is updated by the application.
     * @param data The reason why the event was raised.
     */
    void onChanged(ClipboardData data);
} public class InMemoryClipboard {

    //region Variables
    /**
     * The data to be saved in clipboard.
     */
    private static ClipboardData           data;
    /**
     * A list of listeners.
     */
    private static List<ClipboardListener> listeners;
    //endregion

    //region Constructor
    static {
        listeners = new ArrayList<ClipboardListener>();
    }

    private InMemoryClipboard() {
    }
    //endregion

    //region Public Methods

    /**
     * Adds a new listener to the clipboard.
     *
     * @param listener A listener to add.
     */
    public static void addListener(ClipboardListener listener) {
        listeners.add(listener);
    }

    /**
     * Removes a listener from clipboard.
     *
     * @param listener A listener to remove.
     */
    public static void removeListener(ClipboardListener listener) {
        listeners.remove(listener);
    }

    /**
     * Sets a data into the clipboard.
     *
     * @param clipboardData The data to set.
     * @param <T>           The type of the data.
     */
    public static <T> void setData(ClipboardData<T> clipboardData) {
        data = clipboardData;

        for (ClipboardListener listener : listeners) {
            listener.onChanged(clipboardData);
        }
    }

    /**
     * Gets the data from the clipboard.
     *
     * @param <T> The type of the data to retrieve.
     * @return A requested data if found or null.
     */
    @SuppressWarnings("unchecked")
    public static <T> ClipboardData<T> getData() {
        return data;
    }
    //endregion
} 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
}
import com.intellij.uiDesigner.core.GridConstraints;
import com.intellij.uiDesigner.core.GridLayoutManager;
import com.intellij.uiDesigner.core.Spacer;
import hrider.actions.Action;
import hrider.actions.RunnableAction;
import hrider.config.ClusterConfig;
import hrider.config.GlobalConfig;
import hrider.converters.ConvertersLoader;
import hrider.converters.ConvertersLoaderHandler;
import hrider.converters.TypeConverter;
import hrider.data.*;
import hrider.export.FileExporter;
import hrider.filters.EmptyFilter;
import hrider.filters.Filter;
import hrider.filters.PatternFilter;
import hrider.hbase.*;
import hrider.io.PathHelper;
import hrider.system.ClipboardData;
import hrider.system.ClipboardListener;
import hrider.system.InMemoryClipboard;
import hrider.ui.*;
import hrider.ui.controls.WideComboBox;
import hrider.ui.design.*;
import hrider.ui.forms.*;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.util.Bytes;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.plaf.basic.BasicSplitPaneDivider;
import javax.swing.table.*;
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.*;
import java.util.List;
                                }
                            }
                        }
                        catch (Exception ex) {
                            setError(String.format("Failed to get column families for table '%s'.", getSelectedTableName()), ex);
                        }
                    }
                });

        rowDelete.addActionListener(
                new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        clearError();

                        JTableModel.stopCellEditing(rowsTable);

                        int decision = JOptionPane.showConfirmDialog(
                                topPanel, "Are you sure you want to delete the selected rows?", "Confirmation", JOptionPane.OK_CANCEL_OPTION);

                        if (decision == JOptionPane.OK_OPTION) {
                            owner.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
                            try {
                                int[] selectedRows = rowsTable.getSelectedRows();
                                for (int selectedRow : selectedRows) {
                                    try {
                                        DataCell key = (DataCell)rowsTable.getValueAt(selectedRow, 0);
                                        connection.deleteRow(getSelectedTableName(), key.getRow());
                                    }
                                    catch (Exception ex) {
                                        setError("Failed to delete row in HBase: ", ex);
                                    }
                                }

                                if (scanner != null) {
                                    scanner.resetCurrent(null);
                                }

                                populateColumnsTable(true);
                                populateRowsTable(Direction.Current);
                            }
                            finally {
                                owner.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
                            }
                        }
                    }
                });


        rowCopy.addActionListener(
                new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        clearError();

                        JTableModel.stopCellEditing(rowsTable);

                        copySelectedRowsToClipboard();
                    }
                });

        rowPaste.addActionListener(
                new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        clearError();

                        JTableModel.stopCellEditing(rowsTable);

                        pasteRowsFromClipboard();
                    }
                });

        rowSave.addActionListener(
                new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        clearError();

                        JTableModel.stopCellEditing(rowsTable);

                        int decision = JOptionPane.showConfirmDialog(
                                topPanel,
                                "You are going to save modified rows to hbase; make sure the selected column's types\nare correct otherwise the data will not be read by the application.",
                                "Confirmation", JOptionPane.OK_CANCEL_OPTION);

                        if (decision == JOptionPane.OK_OPTION) {
                            owner.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
                            try {
                                for (DataRow row : changeTracker.getChanges()) {
                                    try {
                                        connection.setRow(getSelectedTableName(), row);
                                    }
                                    catch (Exception ex) {
                                        setError("Failed to update rows in HBase: ", ex);
                                    }
                                }

                                changeTracker.clear();
                                rowSave.setEnabled(changeTracker.hasChanges());
                            }
                            finally {
                                owner.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
                            }
                        }
                    }
                });

        tableAdd.addActionListener(
                new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        clearError();

                        AddTableDialog dialog = new AddTableDialog();
                        if (dialog.showDialog(topPanel)) {
                            owner.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
                            try {
                                connection.createOrModifyTable(dialog.getTableDescriptor());

                                Filter filter;