package org.reactfx;

import java.util.function.Supplier;

import javafx.beans.InvalidationListener;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableBooleanValue;

import org.reactfx.util.ListHelper;

/**
 * @deprecated Indicator is unsafe on recursion: it may report misleading
 * changes. Use {@link SuspendableNo} instead.
 */
@Deprecated
public class Indicator implements ObservableBooleanValue, Guardian {

    private ListHelper<InvalidationListener> iListeners;
    private ListHelper<ChangeListener<? super Boolean>> cListeners;

    private int on = 0;

    /**
     * Turns this indicator on.
     * @return a Guard that, when closed, resets this indicator to the
     * original state.
     */
    public Guard on() {
        if(++on == 1) {
            notifyListeners(true);
        }

        return ((Guard) this::release).closeableOnce();
    }

    private void release() {
        assert on > 0;
        if(--on == 0) {
            notifyListeners(false);
        }
    }

    /**
     * Equivalent to {@link #on()}.
     */
    @Override
    public Guard guard() {
        return on();
    }

    /**
     * Runs the given computation, making sure this indicator is on.
     * When done, this indicator is reset to the previous state.
     *
     *
Equivalent to
     *

     * try(Guard g = on()) {
     *     r.run();
     * }
     *

     */
    public void onWhile(Runnable r) {
        try(Guard g = on()) {
            r.run();
        }
    }

    /**
     * Runs the given computation, making sure this indicator is on.
     * When done, this indicator is reset to the previous state.
     *
     *

     * T t = indicator.onWhile(f);
     *

     *
     * is equivalent to
     *
     *

     * T t;
     * try(Guard g = on()) {
     *     t = f.get();
     * }
     *

     */
    public <T> T onWhile(Supplier<T> f) {
        try(Guard g = on()) {
            return f.get();
        }
    }

    public boolean isOn() {
        return on > 0;
    }

    public boolean isOff() {
        return on == 0;
    }

    @Override
    public boolean get() {
        return on > 0;
    }

    @Override
    public Boolean getValue() {
        return on > 0;
    }

    public EventStream<Void> ons() {
        return EventStreams.valuesOf(this).filterMap(on -> on, on -> null);
    }

    public EventStream<Void> offs() {
        return EventStreams.valuesOf(this).filterMap(on -> !on, on -> null);
    }

    private void notifyListeners(boolean value) {
        ListHelper.forEach(iListeners, l -> l.invalidated(this));
        ListHelper.forEach(cListeners, l -> l.changed(this, !value, value));
    }

    @Override
    public void addListener(ChangeListener<? super Boolean> listener) {
        cListeners = ListHelper.add(cListeners, listener);
    }

    @Override
    public void removeListener(ChangeListener<? super Boolean> listener) {
        cListeners = ListHelper.remove(cListeners, listener);
    }

    @Override
    public void addListener(InvalidationListener listener) {
        iListeners = ListHelper.add(iListeners, listener);
    }

    @Override
    public void removeListener(InvalidationListener listener) {
        iListeners = ListHelper.remove(iListeners, listener);
    }
}


package org.reactfx;

/**
 * An {@linkplain Observable} that does not maintain its own collection of
 * observers, but rather adapts and registers every given observer with the
 * underlying {@linkplain Observable}
 * @param <O> observer type accepted by this {@linkplain Observable}
 * @param
 observer type accepted by the underlying {@linkplain Observable}
 * @param  type of the underlying observable to which observers are delegated
 */
public abstract class ProxyObservable<O, P, U extends Observable
>
implements Observable<O> {
    private final U underlying;

    protected ProxyObservable(U underlying) {
        this.underlying = underlying;
    }

    /**
     * Adapts the given observer to observer of the underlying
     * {@linkplain Observable}.
     *
     *
Important: It is required that
     * the transformation applied to two observers that are _equal_ yields two
     * adapted observers that are _equal_. In other words, if `o1.equals(o2)`,
     * then it must be the case that
     * `adaptObserver(o1).equals(adaptObserver(o2))`.
     *
     * @param observer observer to be adapted for the underlying
     * {@linkplain Observable}
     * @return observer adapted for the underlying {@linkplain Observable}
     */
    protected abstract P adaptObserver(O observer);

    protected final U getUnderlyingObservable() {
        return underlying;
    }

    @Override
    public final void addObserver(O observer) {
        P adapted = adaptObserver(observer);

        assert adapted.equals(adaptObserver(observer))
        : "Two adaptations of the same observer resulted in non-equal"
        + " adapted observers";

        underlying.addObserver(adapted);
    }

    @Override
    public final void removeObserver(O observer) {
        underlying.removeObserver(adaptObserver(observer));
    }

    // Overridden to avoid second transformation on removeObserver
    @Override
    public final Subscription observe(O observer) {
        P adapted = adaptObserver(observer);
        underlying.addObserver(adapted);
        return () -> underlying.removeObserver(adapted);
    }
}


package com.github.abel533.utils;

import com.github.abel533.database.*;
import com.github.abel533.database.introspector.DatabaseIntrospector;
import com.github.abel533.database.introspector.OracleIntrospector;
import com.github.abel533.database.introspector.PGIntrospector;
import com.github.abel533.database.introspector.SqlServerIntrospector;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * 数据库操作
 *
 * @author liuzh
 */
public class DBMetadataUtils {
    private SimpleDataSource dataSource;
    private Connection connection;
    private LetterCase letterCase;
    private Dialect dialect;
    private DatabaseIntrospector introspector;
    private DatabaseMetaData databaseMetaData;
    private List<String> catalogs;
    private List<String> schemas;

    public DBMetadataUtils(SimpleDataSource dataSource) {
        this(dataSource, false, true);
    }

    public DBMetadataUtils(SimpleDataSource dataSource, boolean forceBigDecimals, boolean useCamelCase) {
        if (dataSource == null) {
            throw new NullPointerException("Argument dataSource can't be null!");
        }
        this.dataSource = dataSource;
        this.dialect = dataSource.getDialect();
        try {
            initLetterCase();
            this.introspector = getDatabaseIntrospector(forceBigDecimals, useCamelCase);
            getConnection();
            this.catalogs = introspector.getCatalogs();
            this.schemas = introspector.getSchemas();
            closeConnection();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    private void initLetterCase() {
        try {
            DatabaseMetaData databaseMetaData = getConnection().getMetaData();
            if (databaseMetaData.storesLowerCaseIdentifiers()) {
                letterCase = LetterCase.LOWER;
            } else if (databaseMetaData.storesUpperCaseIdentifiers()) {
                letterCase = LetterCase.UPPER;
            } else {
                letterCase = LetterCase.NORMAL;
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    public String convertLetterByCase(String value) {
        if (value == null) {
            return null;
        }
        switch (letterCase) {
            case UPPER:
                return value.toUpperCase();
            case LOWER:
                return value.toLowerCase();
            default:
                return value;
        }
    }

    public DatabaseMetaData getDatabaseMetaData() throws SQLException {
        if (connection != null && !connection.isClosed()) {
            if (databaseMetaData != null) {
                return databaseMetaData;
            } else {
                databaseMetaData = connection.getMetaData();
                return databaseMetaData;
            }
        } else {
            databaseMetaData = getConnection().getMetaData();
            return databaseMetaData;
        }
    }

    public DatabaseIntrospector getIntrospector() {
        return introspector;
    }

    private DatabaseIntrospector getDatabaseIntrospector(boolean forceBigDecimals, boolean useCamelCase) {
        switch (dataSource.getDialect()) {
            case ORACLE:
                return new OracleIntrospector(this, forceBigDecimals, useCamelCase);
            case POSTGRESQL:
                return new PGIntrospector(this, forceBigDecimals, useCamelCase);
            case SQLSERVER:
                return new SqlServerIntrospector(this, forceBigDecimals, useCamelCase);
            case DB2:
            case HSQLDB:
            case MARIADB:
            case MYSQL:
            default:
                return new DatabaseIntrospector(this, forceBigDecimals, useCamelCase);
        }
    }

    public List<String> getCatalogs() throws SQLException {
        return catalogs;
    }

    public List<String> getSchemas() throws SQLException {
        return schemas;
    }

    public List<IntrospectedTable> introspectTables(DatabaseConfig config) throws SQLException {
        getConnection();
        try {
            return introspector.introspectTables(config);
        } finally {
            closeConnection();
        }
    }

    public static void sortTables(List<IntrospectedTable> tables) {
        if (StringUtils.isNotEmpty(tables)) {
            Collections.sort(tables, new Comparator<IntrospectedTable>() {
                @Override
                public int compare(IntrospectedTable o1, IntrospectedTable o2) {
                    return o1.getName().compareTo(o2.getName());
                }
            });
        }
    }

    public static void sortColumns(List<IntrospectedColumn> columns) {
        if (StringUtils.isNotEmpty(columns)) {
            Collections.sort(columns, new Comparator<IntrospectedColumn>() {
                @Override
                public int compare(IntrospectedColumn o1, IntrospectedColumn o2) {
                    int result = o1.getTableName().compareTo(o2.getTableName());
                    if (result == 0) {
                        result = o1.getName().compareTo(o2.getName());
                    }
                    return result;
                }
            });
        }
    }

    private void closeConnection() {
        if (connection != null) {
            try {
                connection.close();
                connection = null;
                databaseMetaData = null;
            } catch (SQLException e) {
            }
        }
    }

    public boolean testConnection() {
        try {
            if (!getConnection().isClosed()) {
                return true;
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            closeConnection();
        }
        return false;
    }

    public Connection getConnection() throws SQLException {
        if (connection != null && !connection.isClosed()) {
            return connection;
        }
        try {
            connection = dataSource.getConnection();
            return connection;
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    private enum LetterCase {
        UPPER, LOWER, NORMAL
    }

    public Dialect getDialect() {
        return dialect;
    }

    /**
     * 获取默认的Config
     *
     * @return
     * @throws SQLException
     */
    public DatabaseConfig getDefaultConfig() throws SQLException {
        DatabaseConfig config = null;
        if (catalogs.size() == 1) {
            if (schemas.size() == 1) {
                config = new DatabaseConfig(catalogs.get(0), schemas.get(0));
            } else if (schemas.size() == 0) {
                config = new DatabaseConfig(catalogs.get(0), null);
            }
        } else if (catalogs.size() == 0) {
            if (schemas.size() == 1) {
                config = new DatabaseConfig(null, schemas.get(0));
            } else if (schemas.size() == 0) {
                config = new DatabaseConfig(null, null);
            }
        }
        if (config == null) {
            switch (getDialect()) {
                case DB2:
                case ORACLE:
                    config = new DatabaseConfig(null, dataSource.getUser());
                    break;
                case MYSQL:
                    if (schemas.size() > 0) {
                        break;
                    }
                    String url = dataSource.getUrl();
                    if (url.indexOf('/') > 0) {
                        String dbName = url.substring(url.lastIndexOf('/') + 1);
                        for (String catalog : catalogs) {
                            if (dbName.equalsIgnoreCase(catalog)) {
                                config = new DatabaseConfig(catalog, null);
                                break;
                            }
                        }
                    }
                    break;
                case SQLSERVER:
                    String sqlserverUrl = dataSource.getUrl();
                    String sqlserverCatalog = null;
                    if (sqlserverUrl.indexOf('/') > 0) {
                        String dbName = sqlserverUrl.substring(sqlserverUrl.lastIndexOf('/') + 1);
                        for (String catalog : catalogs) {
                            if (dbName.equalsIgnoreCase(catalog)) {
                                sqlserverCatalog = catalog;
                                break;
                            }
                        }
                        if (sqlserverCatalog != null) {
                            for (String schema : schemas) {
                                if ("dbo".equalsIgnoreCase(schema)) {
                                    config = new DatabaseConfig(sqlserverCatalog, "dbo");
                                    break;
                                }
                            }
                        }
                    }
                    break;
                case POSTGRESQL:
                    String postgreUrl = dataSource.getUrl();
                    String postgreCatalog = null;
                    if (postgreUrl.indexOf('/') > 0) {
                        String dbName = postgreUrl.substring(postgreUrl.lastIndexOf('/') + 1);
                        for (String catalog : catalogs) {
                            if (dbName.equalsIgnoreCase(catalog)) {
                                postgreCatalog = catalog;
                                break;
                            }
                        }
                        if (postgreCatalog != null) {
                            for (String schema : schemas) {
                                if ("public".equalsIgnoreCase(schema)) {
                                    config = new DatabaseConfig(postgreCatalog, "public");
                                    break;
                                }
                            }
                        }
                    }
                    break;
                default:
                    break;
            }

        }
        return config;
    }
}


package com.github.abel533.database.introspector;

import com.github.abel533.database.DatabaseConfig;
import com.github.abel533.utils.DBMetadataUtils;
import com.github.abel533.utils.StringUtils;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;

public class SqlServerIntrospector extends DatabaseIntrospector {

    public SqlServerIntrospector(DBMetadataUtils dbMetadataUtils) {
        super(dbMetadataUtils);
    }

    public SqlServerIntrospector(DBMetadataUtils dbMetadataUtils, boolean forceBigDecimals, boolean useCamelCase) {
        super(dbMetadataUtils, forceBigDecimals, useCamelCase);
    }

    /**
     * 获取表名和注释映射
     *
     * @param config
     * @return
     * @throws java.sql.SQLException
     */
    protected Map<String, String> getTableComments(DatabaseConfig config) throws SQLException {
        Map<String, String> answer = new HashMap<String, String>();
        try {
            StringBuilder sqlBuilder = new StringBuilder("select a.name tname, b.value comments ");
            sqlBuilder.append("from sys.tables a left join sys.extended_properties b on a.object_id=b.major_id    ");
            sqlBuilder.append("where b.minor_id=0 and b.name = 'MS_Description'   ");
            sqlBuilder.append("and a.schema_id in (   ");
            sqlBuilder.append("    select schema_id from sys.schemas  ");
            if (StringUtils.isNotEmpty(config.getSchemaPattern())) {
                sqlBuilder.append(" where name like ?   ");
            }
            sqlBuilder.append(")  and b.value is not null ");
            PreparedStatement preparedStatement = dbMetadataUtils.getConnection().prepareStatement(sqlBuilder.toString());
            if (StringUtils.isNotEmpty(config.getSchemaPattern())) {
                preparedStatement.setString(1, config.getSchemaPattern());
            }
            ResultSet rs = preparedStatement.executeQuery();
            while (rs.next()) {
                answer.put(rs.getString(dbMetadataUtils.convertLetterByCase("tname")), rs.getString(dbMetadataUtils.convertLetterByCase("comments")));
            }
            closeResultSet(rs);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return answer;
    }

    /**
     * 获取表字段注释
     *
     * @param config
     * @return
     * @throws java.sql.SQLException
     */
    @Override
    protected Map<String, Map<String, String>> getColumnComments(DatabaseConfig config) throws SQLException {
        Map<String, Map<String, String>> answer = new HashMap<String, Map<String, String>>();
        try {
            StringBuilder sqlBuilder = new StringBuilder("select a.name tname, b.name cname, c.value as comments    ");
            sqlBuilder.append("from sys.tables a left join sys.columns b on a.object_id=b.object_id   ");
            sqlBuilder.append("left join sys.extended_properties c on a.object_id=c.major_id   ");
            sqlBuilder.append("where c.minor_id<>0 and b.column_id=c.minor_id   ");
            sqlBuilder.append("and a.schema_id in (   ");
            sqlBuilder.append("    select schema_id from sys.schemas  ");
            if (StringUtils.isNotEmpty(config.getSchemaPattern())) {
                sqlBuilder.append(" where name like ?   ");
            }
            sqlBuilder.append(")  and c.value is not null");

            PreparedStatement preparedStatement = dbMetadataUtils.getConnection().prepareStatement(sqlBuilder.toString());
            if (StringUtils.isNotEmpty(config.getSchemaPattern())) {
                preparedStatement.setString(1, config.getSchemaPattern());
            }
            ResultSet rs = preparedStatement.executeQuery();
            while (rs.next()) {
                String tname = rs.getString(dbMetadataUtils.convertLetterByCase("tname"));
                if (!answer.containsKey(tname)) {
                    answer.put(tname, new HashMap<String, String>());
                }
                answer.get(tname).put(rs.getString(dbMetadataUtils.convertLetterByCase("cname")), rs.getString(dbMetadataUtils.convertLetterByCase("comments")));
            }
            closeResultSet(rs);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return answer;
    }
}


package com.github.abel533.database.introspector;

import com.github.abel533.database.DatabaseConfig;
import com.github.abel533.utils.DBMetadataUtils;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;

public class PGIntrospector extends DatabaseIntrospector {

    public PGIntrospector(DBMetadataUtils dbMetadataUtils) {
        super(dbMetadataUtils);
    }

    public PGIntrospector(DBMetadataUtils dbMetadataUtils, boolean forceBigDecimals, boolean useCamelCase) {
        super(dbMetadataUtils, forceBigDecimals, useCamelCase);
    }

    /**
     * 获取表名和注释映射
     *
     * @param config
     * @return
     * @throws java.sql.SQLException
     */
    protected Map<String, String> getTableComments(DatabaseConfig config) throws SQLException {
        Map<String, String> answer = new HashMap<String, String>();
        try {
            PreparedStatement preparedStatement = dbMetadataUtils.getConnection().prepareStatement("select tname,comments from(select relname as TNAME ,col_description(c.oid, 0) as COMMENTS from pg_class c where  relkind = 'r' and relname not like 'pg_%' and relname not like 'sql_%') as temp where comments is not null ");
            ResultSet rs = preparedStatement.executeQuery();
            while (rs.next()) {
                answer.put(rs.getString(dbMetadataUtils.convertLetterByCase("tname")), rs.getString(dbMetadataUtils.convertLetterByCase("comments")));
            }
            closeResultSet(rs);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return answer;
    }

    /**
     * 获取表字段注释
     *
     * @param config
     * @return
     * @throws java.sql.SQLException
     */
    @Override
    protected Map<String, Map<String, String>> getColumnComments(DatabaseConfig config) throws SQLException {
        Map<String, Map<String, String>> answer = new HashMap<String, Map<String, String>>();
        try {
            StringBuilder sqlBuilder = new StringBuilder("select tname,cname,comments from( ");
            sqlBuilder.append("SELECT col_description(a.attrelid,a.attnum) as comments,a.attname as cname,c.relname as tname FROM pg_class as c,pg_attribute as a where a.attrelid = c.oid and a.attnum>0 and c.relname not like 'pg_%' and c.relname not like 'sql_%') as temp where comments is not null ");
            PreparedStatement preparedStatement = dbMetadataUtils.getConnection().prepareStatement(sqlBuilder.toString());
            ResultSet rs = preparedStatement.executeQuery();
            while (rs.next()) {
                String tname = rs.getString(dbMetadataUtils.convertLetterByCase("tname"));
                if (!answer.containsKey(tname)) {
                    answer.put(tname, new HashMap<String, String>());
                }
                answer.get(tname).put(rs.getString(dbMetadataUtils.convertLetterByCase("cname")), rs.getString(dbMetadataUtils.convertLetterByCase("comments")));
            }
            closeResultSet(rs);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return answer;
    }
}


package com.github.abel533.service;

import com.github.abel533.component.DBData;
import com.github.abel533.database.IntrospectedColumn;
import com.github.abel533.database.IntrospectedTable;
import com.github.abel533.database.MatchType;
import com.github.abel533.utils.I18n;
import com.github.abel533.utils.StringUtils;

import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@SuppressWarnings("serial")
public class ToolsService {

    private List<IntrospectedTable> EMPTY = new ArrayList<IntrospectedTable>(0);

    private Object[][] EMPTY_DATA = new Object[][]{};

    private String[] tableNames = I18n.key("dbTableModel.columns").split(",");
    private DefaultTableModel dbTableModel = new DefaultTableModel(EMPTY_DATA, tableNames) {
        @Override
        public boolean isCellEditable(int row, int column) {
            return false;
        }
    };
    private String[] fieldNames = I18n.key("dbFieldModel.columns").split(",");
    private DefaultTableModel dbFieldModel = new DefaultTableModel(EMPTY_DATA, fieldNames) {
        @Override
        public boolean isCellEditable(int row, int column) {
            return false;
        }

        @Override
        public Class<?> getColumnClass(int columnIndex) {
            //tablename,name,type(length),remarks,pk,nullable,defaultValue
            switch (columnIndex) {
                case 0:
                case 1:
                case 2:
                case 3:
                    return String.class;
                case 4:
                case 5:
                    return Boolean.class;
                default:
                    return Object.class;
            }
        }
    };

    public DefaultTableModel getDbTableModel() {
        return dbTableModel;
    }

    public DefaultTableModel getDbFieldModel() {
        return dbFieldModel;
    }

    /**
     * 处理标题列的居中
     *
     * @param dbField
     */
    public void prcessDbFieldHeader(JTable dbField) {
        dbField.getColumn(fieldNames[4]).setPreferredWidth(40);
        dbField.getColumn(fieldNames[4]).setMinWidth(40);
        dbField.getColumn(fieldNames[4]).setMaxWidth(40);
        dbField.getColumn(fieldNames[5]).setPreferredWidth(40);
        dbField.getColumn(fieldNames[5]).setMinWidth(40);
        dbField.getColumn(fieldNames[5]).setMaxWidth(40);
    }

    public synchronized void loadTableFields(final String tableName) {
        loadColumns(DBData.getTableColumns(tableName), true);
    }

    public void loadTableColumnsByRowIndex(int row) {
        String tableName = dbTableModel.getValueAt(row, 0).toString();
        loadTableFields(tableName);
    }

    public String getChooseTableTextByRowIndex(int row) {
        StringBuilder chooseBuilder = new StringBuilder();
        for (int i = 0; i < dbTableModel.getColumnCount(); i++) {
            chooseBuilder.append(StringUtils.getNotNull(dbTableModel.getValueAt(row, i))).append(" - ");
        }
        return chooseBuilder.substring(0, chooseBuilder.length() - 3);
    }

    public String getChooseFieldTextByRowIndex(int row) {
        StringBuilder chooseBuilder = new StringBuilder();
        for (int i = 0; i < dbFieldModel.getColumnCount(); i++) {
            chooseBuilder.append(StringUtils.getNotNull(dbFieldModel.getValueAt(row, i))).append(" - ");
        }
        return chooseBuilder.substring(0, chooseBuilder.length() - 3);
    }

    public String getTableNameByRowIndex(int row) {
        return dbTableModel.getValueAt(row, 0).toString();
    }

    public String getFieldNameByRowIndex(int row) {
        return dbFieldModel.getValueAt(row, 1).toString();
    }

    public synchronized void search(String searchText, String searchComment, MatchType matchType, boolean table, boolean caseSensitive) {
        if (table) {
            loadTables(DBData.selectTables(searchText, searchComment, matchType, caseSensitive), 0);
        } else {
            List<IntrospectedColumn> columns = DBData.selectColumns(searchText, searchComment, matchType, caseSensitive);
            if (columns == null) {
                loadTables(DBData.getAllTables(), 0);
            } else {
                List<IntrospectedTable> tables = new ArrayList<IntrospectedTable>();
                Map<String, Integer> tableMap = new HashMap<String, Integer>();
                for (IntrospectedColumn column : columns) {
                    if (tableMap.containsKey(column.getTableName())) {
                        tableMap.put(column.getTableName(), tableMap.get(column.getTableName()) + 1);
                    } else {
                        tables.add(DBData.getTable(column.getTableName()));
                        tableMap.put(column.getTableName(), 1);
                    }
                }
                loadTables(tables, -1);
                loadColumns(columns, false);
            }
        }
    }

    public synchronized void loadAll() {
        loadTables(DBData.getAllTables(), 0);
    }

    public synchronized void clearAll() {
        loadTables(EMPTY, -1);
    }

    public synchronized void loadTables(final List<IntrospectedTable> tables, final int selectIndex) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                while (dbTableModel.getRowCount() > 0) {
                    dbTableModel.removeRow(0);
                }
                for (IntrospectedTable table : tables) {
                    dbTableModel.addRow(new Object[]{
                            table.getName(),
                            table.getRemarks()
                    });
                }
                if (tables.size() == 0) {
                    while (dbFieldModel.getRowCount() > 0) {
                        dbFieldModel.removeRow(0);
                    }
                } else if (tables.size() > 0 && selectIndex > -1) {
                    loadTableFields(tables.get(selectIndex).getName());
                }
            }
        });
    }

    public synchronized void loadColumns(final List<IntrospectedColumn> columns, final boolean tableName) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                while (dbFieldModel.getRowCount() > 0) {
                    dbFieldModel.removeRow(0);
                }
                for (int i = 0; i < columns.size(); i++) {
                    //tablename,name,type(length),remarks,pk,nullable,defaultValue
                    String tName = columns.get(i).getTableName();
                    if (tableName && i > 0) {
                        tName = "";
                    }
                    StringBuilder typeBuilder = new StringBuilder(columns.get(i).getType());
                    if (!columns.get(i).isJDBCDateColumn() && !columns.get(i).isJDBCTimeColumn()) {
                        typeBuilder.append("(");
                        typeBuilder.append(columns.get(i).getLength());
                        if (columns.get(i).getScale() > 0) {
                            typeBuilder.append(",").append(columns.get(i).getScale());
                        }
                        typeBuilder.append(")");
                    }
                    dbFieldModel.addRow(new Object[]{
                            tName,
                            columns.get(i).getName(),
                            typeBuilder.toString(),
                            columns.get(i).getRemarks(),
                            columns.get(i).isPk(),
                            columns.get(i).isNullable(),
                            columns.get(i).getDefaultValue()
                    });
                }
            }
        });
    }

}


package com.github.abel533.component;

import com.github.abel533.database.*;
import com.github.abel533.utils.DBMetadataUtils;
import com.github.abel533.utils.StringUtils;

import javax.swing.*;
import java.sql.SQLException;
import java.util.*;

/**
 * 数据库表数据
 *
 * @author liuzh
 * @since 2015-03-19
 */
public class DBData {
    private static DBServer dbServer;
    private static DBMetadataUtils dbMetadataUtils;
    private static SimpleDataSource dataSource;
    //登录系统的时候操作下面两项
    public static DBServerList dbServerList;
    public static List<String> dbServers;

    //选择配置后
    public static DatabaseConfig config;
    public static List<String> catalogs;
    public static List<String> schemas;
    private static List<IntrospectedTable> tables;
    //columnMap - 方便点击表的时候显示字段
    private static Map<String, IntrospectedTable> tableMap;
    private static Map<String, List<IntrospectedColumn>> columnMap;

    public static void init(DBServer server) {
        dbServer = server;
        dataSource = new SimpleDataSource(server.getDialect(), server.getUrl(), server.getUser(), server.getRealPwd());
        dbMetadataUtils = new DBMetadataUtils(dataSource);
        try {
            initConfig();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    public static boolean testConnection() {
        return dbMetadataUtils.testConnection();
    }

    public static String convertLetterByCase(String value) {
        return dbMetadataUtils.convertLetterByCase(value);
    }

    /**
     * 初始化默认的Config
     *
     * @throws SQLException
     */
    private static void initConfig() throws SQLException {
        DBData.catalogs = dbMetadataUtils.getCatalogs();
        DBData.schemas = dbMetadataUtils.getSchemas();
        DBData.config = dbMetadataUtils.getDefaultConfig();
    }

    public static void initTables() throws SQLException {
        if (DBData.config != null) {
            DBData.setTables(dbMetadataUtils.introspectTables(DBData.config));
        }
    }

    public static DefaultComboBoxModel<String> getCatalogModel() {
        return new DefaultComboBoxModel<String>(catalogs.toArray(new String[]{}));
    }

    public static DefaultComboBoxModel<String> getSchemaModel() {
        return new DefaultComboBoxModel<String>(schemas.toArray(new String[]{}));
    }

    public static void setTables(List<IntrospectedTable> tables) {
        if (StringUtils.isEmpty(tables)) {
            tables = new ArrayList<IntrospectedTable>(0);
        }
        sortTables(tables);
        DBData.tables = tables;
        tableMap = new HashMap<String, IntrospectedTable>();
        columnMap = new HashMap<String, List<IntrospectedColumn>>();
        if (tables != null && tables.size() > 0) {
            for (IntrospectedTable table : tables) {
                tableMap.put(table.getName(), table);
                columnMap.put(table.getName(), table.getAllColumns());
            }
        }
    }

    private static void sortTables(List<IntrospectedTable> tables) {
        if (StringUtils.isNotEmpty(tables)) {
            Collections.sort(tables, new Comparator<IntrospectedTable>() {
                @Override
                public int compare(IntrospectedTable o1, IntrospectedTable o2) {
                    return o1.getName().compareTo(o2.getName());
                }
            });
        }
    }

    private static void sortColumns(List<IntrospectedColumn> columns) {
        if (StringUtils.isNotEmpty(columns)) {
            Collections.sort(columns, new Comparator<IntrospectedColumn>() {
                @Override
                public int compare(IntrospectedColumn o1, IntrospectedColumn o2) {
                    int result = o1.getTableName().compareTo(o2.getTableName());
                    if (result == 0) {
                        result = o1.getName().compareTo(o2.getName());
                    }
                    return result;
                }
            });
        }
    }

    public static List<IntrospectedTable> getAllTables() {
        return tables;
    }

    public static IntrospectedTable getTable(String tableName) {
        return tableMap.get(tableName);
    }

    public static List<IntrospectedColumn> getTableColumns(String tableName) {
        return columnMap.get(tableName);
    }

    /**
     * 查询表
     *
     * @param searchText
     * @param searchComment
     * @param matchType
     * @param caseSensitive
     * @return
     */
    public static List<IntrospectedTable> selectTables(
            String searchText, String searchComment, MatchType matchType, boolean caseSensitive) {
        List<IntrospectedTable> answer = new ArrayList<IntrospectedTable>();
        if (tables == null || tables.size() == 0) {
            return answer;
        }
        if (StringUtils.isEmpty(searchText) && StringUtils.isEmpty(searchComment)) {
            return tables;
        }
        for (IntrospectedTable table : tables) {
            if (table.filter(searchText, searchComment, matchType, caseSensitive)) {
                answer.add(table);
            }
        }
        sortTables(answer);
        return answer;
    }

    /**
     * 查询字段
     *
     * @param searchText
     * @param searchComment
     * @param matchType
     * @param caseSensitive
     * @return
     */
    public static List<IntrospectedColumn> selectColumns(
            String searchText, String searchComment, MatchType matchType, boolean caseSensitive) {
        List<IntrospectedColumn> answer = new ArrayList<IntrospectedColumn>();
        if (tables == null || tables.size() == 0) {
            return answer;
        }
        if (StringUtils.isEmpty(searchText) && StringUtils.isEmpty(searchComment)) {
            return null;
        }
        for (IntrospectedTable table : tables) {
            for (IntrospectedColumn column : table.getAllColumns()) {
                if (column.filter(searchText, searchComment, matchType, caseSensitive)) {
                    answer.add(column);
                }
            }
        }
        sortColumns(answer);
        return answer;
    }
}


package com.github.abel533.utils;

import java.io.File;
import java.io.FilenameFilter;
import java.text.MessageFormat;
import java.util.HashSet;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.Set;

/**
 * @author liuzh
 */
public class I18n {
    private static final String SUFFIX = ".properties";
    private static ResourceBundle[] resourceBundles;
    private static Set<String> baseNameList;

    static {
        init(null);
    }

    private static void initBaseNameList() {
        File[] files = new File(Path.LANGUAGE).listFiles(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                if (name.endsWith(SUFFIX)) {
                    return true;
                }
                return false;
            }
        });
        baseNameList = new HashSet<String>();
        for (int i = 0; i < files.length; i++) {
            String fileName = files[i].getName();
            int index = fileName.indexOf('_');
            if (index == -1) {
                baseNameList.add("language/" + fileName.substring(0, fileName.lastIndexOf('.')));
            } else {
                baseNameList.add("language/" + fileName.substring(0, index));
            }
        }
    }

    /**
     * 初始化 - 可以设置语言
     *
     * @param locale
     */
    public static void init(Locale locale) {
        if (locale != null) {
            Locale.setDefault(locale);
        }
        if (baseNameList == null || baseNameList.size() == 0) {
            initBaseNameList();
        }
        resourceBundles = new ResourceBundle[baseNameList.size()];
        int i = 0;
        for (String baseName : baseNameList) {
            resourceBundles[i] = ResourceBundle.getBundle(baseName);
            i++;
        }
    }

    public static String key(String key) {
        for (int i = 0; i < resourceBundles.length; i++) {
            if (resourceBundles[i].containsKey(key)) {
                return resourceBundles[i].getString(key);
            }
        }
        throw new RuntimeException("key:" + key + " not exists!");
    }

    /**
     * 可配参数，如：查找{0}字段
     *
     * @param key
     * @param args
     * @return
     */
    public static String key(String key, Object... args) {
        return MessageFormat.format(key(key), args);
    }
}


package com.github.abel533.database;

import com.github.abel533.utils.StringUtils;

public class IntrospectedBase {
    protected String name;
    protected String remarks;

    /**
     * 根据条件进行过滤
     *
     * @param searchText
     * @param searchComment
     * @param matchType
     * @param caseSensitive
     * @return
     */
    public boolean filter(String searchText, String searchComment, MatchType matchType, boolean caseSensitive) {
        if (StringUtils.isNotEmpty(searchText)) {
            if (matchType == MatchType.EQUALS) {
                if (caseSensitive) {
                    if (!getName().equals(searchText)) {
                        return false;
                    }
                } else {
                    if (!getName().equalsIgnoreCase(searchText)) {
                        return false;
                    }
                }
            } else {
                if (caseSensitive) {
                    if (getName().indexOf(searchText) == -1) {
                        return false;
                    }
                } else {
                    if (getName().toUpperCase().indexOf(searchText.toUpperCase()) == -1) {
                        return false;
                    }
                }
            }
        }
        if (StringUtils.isNotEmpty(searchComment)) {
            if (matchType == MatchType.EQUALS) {
                if (caseSensitive) {
                    if (getRemarks() == null || !getRemarks().equals(searchComment)) {
                        return false;
                    }
                } else {
                    if (getRemarks() == null || !getRemarks().equalsIgnoreCase(searchComment)) {
                        return false;
                    }
                }
            } else {
                if (caseSensitive) {
                    if (getRemarks() == null || getRemarks().indexOf(searchComment) == -1) {
                        return false;
                    }
                } else {
                    if (getRemarks() == null || getRemarks().indexOf(searchComment) == -1) {
                        return false;
                    }
                }
            }
        }
        return true;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getRemarks() {
        return remarks;
    }

    public void setRemarks(String remarks) {
        this.remarks = remarks;
    }
}


package com.github.abel533.database.introspector;

import com.github.abel533.database.DatabaseConfig;
import com.github.abel533.database.FullyQualifiedJavaType;
import com.github.abel533.database.IntrospectedColumn;
import com.github.abel533.database.IntrospectedTable;
import com.github.abel533.utils.DBMetadataUtils;
import com.github.abel533.utils.JavaBeansUtil;

import java.math.BigDecimal;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.*;

public class DatabaseIntrospector {

    protected static final Map<Integer, JdbcTypeInformation> typeMap;

    static {
        typeMap = new HashMap<Integer, JdbcTypeInformation>();

        typeMap.put(Types.ARRAY, new JdbcTypeInformation("ARRAY",
                new FullyQualifiedJavaType(Object.class.getName())));
        typeMap.put(Types.BIGINT, new JdbcTypeInformation("BIGINT",
                new FullyQualifiedJavaType(Long.class.getName())));
        typeMap.put(Types.BINARY, new JdbcTypeInformation("BINARY",
                new FullyQualifiedJavaType("byte[]")));
        typeMap.put(Types.BIT, new JdbcTypeInformation("BIT",
                new FullyQualifiedJavaType(Boolean.class.getName())));
        typeMap.put(Types.BLOB, new JdbcTypeInformation("BLOB",
                new FullyQualifiedJavaType("byte[]")));
        typeMap.put(Types.BOOLEAN, new JdbcTypeInformation("BOOLEAN",
                new FullyQualifiedJavaType(Boolean.class.getName())));
        typeMap.put(Types.CHAR, new JdbcTypeInformation("CHAR",
                new FullyQualifiedJavaType(String.class.getName())));
        typeMap.put(Types.CLOB, new JdbcTypeInformation("CLOB",
                new FullyQualifiedJavaType(String.class.getName())));
        typeMap.put(Types.DATALINK, new JdbcTypeInformation("DATALINK",
                new FullyQualifiedJavaType(Object.class.getName())));
        typeMap.put(Types.DATE, new JdbcTypeInformation("DATE",
                new FullyQualifiedJavaType(Date.class.getName())));
        typeMap.put(Types.DISTINCT, new JdbcTypeInformation("DISTINCT",
                new FullyQualifiedJavaType(Object.class.getName())));
        typeMap.put(Types.DOUBLE, new JdbcTypeInformation("DOUBLE",
                new FullyQualifiedJavaType(Double.class.getName())));
        typeMap.put(Types.FLOAT, new JdbcTypeInformation("FLOAT",
                new FullyQualifiedJavaType(Double.class.getName())));
        typeMap.put(Types.INTEGER, new JdbcTypeInformation("INTEGER",
                new FullyQualifiedJavaType(Integer.class.getName())));
        typeMap.put(Types.JAVA_OBJECT, new JdbcTypeInformation("JAVA_OBJECT",
                new FullyQualifiedJavaType(Object.class.getName())));
        typeMap.put(Types.LONGNVARCHAR, new JdbcTypeInformation("LONGNVARCHAR",
                new FullyQualifiedJavaType(String.class.getName())));
        typeMap.put(Types.LONGVARBINARY, new JdbcTypeInformation(
                "LONGVARBINARY",
                new FullyQualifiedJavaType("byte[]")));
        typeMap.put(Types.LONGVARCHAR, new JdbcTypeInformation("LONGVARCHAR",
                new FullyQualifiedJavaType(String.class.getName())));
        typeMap.put(Types.NCHAR, new JdbcTypeInformation("NCHAR",
                new FullyQualifiedJavaType(String.class.getName())));
        typeMap.put(Types.NCLOB, new JdbcTypeInformation("NCLOB",
                new FullyQualifiedJavaType(String.class.getName())));
        typeMap.put(Types.NVARCHAR, new JdbcTypeInformation("NVARCHAR",
                new FullyQualifiedJavaType(String.class.getName())));
        typeMap.put(Types.NULL, new JdbcTypeInformation("NULL",
                new FullyQualifiedJavaType(Object.class.getName())));
        typeMap.put(Types.OTHER, new JdbcTypeInformation("OTHER",
                new FullyQualifiedJavaType(Object.class.getName())));
        typeMap.put(Types.REAL, new JdbcTypeInformation("REAL",
                new FullyQualifiedJavaType(Float.class.getName())));
        typeMap.put(Types.REF, new JdbcTypeInformation("REF",
                new FullyQualifiedJavaType(Object.class.getName())));
        typeMap.put(Types.SMALLINT, new JdbcTypeInformation("SMALLINT",
                new FullyQualifiedJavaType(Short.class.getName())));
        typeMap.put(Types.STRUCT, new JdbcTypeInformation("STRUCT",
                new FullyQualifiedJavaType(Object.class.getName())));
        typeMap.put(Types.TIME, new JdbcTypeInformation("TIME",
                new FullyQualifiedJavaType(Date.class.getName())));
        typeMap.put(Types.TIMESTAMP, new JdbcTypeInformation("TIMESTAMP",
                new FullyQualifiedJavaType(Date.class.getName())));
        typeMap.put(Types.TINYINT, new JdbcTypeInformation("TINYINT",
                new FullyQualifiedJavaType(Byte.class.getName())));
        typeMap.put(Types.VARBINARY, new JdbcTypeInformation("VARBINARY",
                new FullyQualifiedJavaType("byte[]")));
        typeMap.put(Types.VARCHAR, new JdbcTypeInformation("VARCHAR",
                new FullyQualifiedJavaType(String.class.getName())));
    }

    protected DBMetadataUtils dbMetadataUtils;
    protected boolean forceBigDecimals;
    protected boolean useCamelCase;

    public DatabaseIntrospector(DBMetadataUtils dbMetadataUtils) {
        this(dbMetadataUtils, false, true);
    }

    public DatabaseIntrospector(DBMetadataUtils dbMetadataUtils, boolean forceBigDecimals, boolean useCamelCase) {
        this.dbMetadataUtils = dbMetadataUtils;
        this.forceBigDecimals = forceBigDecimals;
        this.useCamelCase = useCamelCase;
    }

    public FullyQualifiedJavaType calculateJavaType(IntrospectedColumn introspectedColumn) {
        FullyQualifiedJavaType answer;
        JdbcTypeInformation jdbcTypeInformation = typeMap.get(introspectedColumn.getJdbcType());
        if (jdbcTypeInformation == null) {
            switch (introspectedColumn.getJdbcType()) {
                case Types.DECIMAL:
                case Types.NUMERIC:
                    if (introspectedColumn.getScale() > 0
                            || introspectedColumn.getLength() > 18
                            || forceBigDecimals
                            ) {
                        answer = new FullyQualifiedJavaType(BigDecimal.class
                                .getName());
                    } else if (introspectedColumn.getLength() > 9) {
                        answer = new FullyQualifiedJavaType(Long.class.getName());
                    } else if (introspectedColumn.getLength() > 4) {
                        answer = new FullyQualifiedJavaType(Integer.class.getName());
                    } else {
                        answer = new FullyQualifiedJavaType(Short.class.getName());
                    }
                    break;

                default:
                    answer = null;
                    break;
            }
        } else {
            answer = jdbcTypeInformation.getFullyQualifiedJavaType();
        }

        return answer;
    }

    public String calculateJdbcTypeName(IntrospectedColumn introspectedColumn) {
        String answer;
        JdbcTypeInformation jdbcTypeInformation = typeMap
                .get(introspectedColumn.getJdbcType());

        if (jdbcTypeInformation == null) {
            switch (introspectedColumn.getJdbcType()) {
                case Types.DECIMAL:
                    answer = "DECIMAL";
                    break;
                case Types.NUMERIC:
                    answer = "NUMERIC";
                    break;
                default:
                    answer = null;
                    break;
            }
        } else {
            answer = jdbcTypeInformation.getJdbcTypeName();
        }

        return answer;
    }

    public List<String> getCatalogs() throws SQLException {
        ResultSet rs = dbMetadataUtils.getDatabaseMetaData().getCatalogs();
        List<String> catalogs = new ArrayList<String>();
        while (rs.next()) {
            catalogs.add(rs.getString(1));
        }
        closeResultSet(rs);
        return catalogs;
    }

    public List<String> getSchemas() throws SQLException {
        ResultSet rs = dbMetadataUtils.getDatabaseMetaData().getSchemas();
        List<String> schemas = new ArrayList<String>();
        while (rs.next()) {
            schemas.add(rs.getString(1));
        }
        closeResultSet(rs);
        return schemas;
    }

    public List<String> getTableTypes() throws SQLException {
        ResultSet rs = dbMetadataUtils.getDatabaseMetaData().getTableTypes();
        List<String> tableType = new ArrayList<String>();
        while (rs.next()) {
            tableType.add(rs.getString(1));
        }
        closeResultSet(rs);
        return tableType;
    }

    /**
     * 计算主键
     *
     * @param config
     * @param introspectedTable
     */
    protected void calculatePrimaryKey(DatabaseConfig config,
                                       IntrospectedTable introspectedTable) {
        ResultSet rs = null;
        try {
            rs = dbMetadataUtils.getDatabaseMetaData().getPrimaryKeys(
                    config.getCatalog(),
                    config.getSchemaPattern(),
                    introspectedTable.getName());
        } catch (SQLException e) {
            closeResultSet(rs);
            return;
        }

        try {
            Map<Short, String> keyColumns = new TreeMap<Short, String>();
            while (rs.next()) {
                //主键列名
                String columnName = rs.getString("COLUMN_NAME");
                //主键顺序
                short keySeq = rs.getShort("KEY_SEQ");
                keyColumns.put(keySeq, columnName);
            }

            for (String columnName : keyColumns.values()) {
                introspectedTable.addPrimaryKeyColumn(columnName);
            }
        } catch (SQLException e) {
        } finally {
            closeResultSet(rs);
        }
    }

    /**
     * 关闭ResultSet
     *
     * @param rs
     */
    protected void closeResultSet(ResultSet rs) {
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
            }
        }
    }

    /**
     * 获取表信息
     *
     * @param config
     * @return
     * @throws SQLException
     */
    public List<IntrospectedTable> introspectTables(DatabaseConfig config)
            throws SQLException {
        if (config.hasProcess()) {
            config.getDatabaseProcess().processStart();
        }
        List<IntrospectedTable> introspectedTables = null;
        try {
            DatabaseConfig localConfig = getLocalDatabaseConfig(config);
            Map<IntrospectedTable, List<IntrospectedColumn>> columns = getColumns(localConfig);

            if (columns.isEmpty()) {
                introspectedTables = new ArrayList<IntrospectedTable>(0);
            } else {
                introspectedTables = calculateIntrospectedTables(localConfig, columns);
                Iterator<IntrospectedTable> iter = introspectedTables.iterator();
                while (iter.hasNext()) {
                    IntrospectedTable introspectedTable = iter.next();
                    //去掉没有字段的表
                    if (!introspectedTable.hasAnyColumns()) {
                        iter.remove();
                    }
                }
            }
        } finally {
            if (config.hasProcess()) {
                config.getDatabaseProcess().processComplete(introspectedTables);
            }
        }
        return introspectedTables;
    }

    /**
     * 根据数据库转换配置
     *
     * @param config
     * @return
     * @throws SQLException
     */
    protected DatabaseConfig getLocalDatabaseConfig(DatabaseConfig config) throws SQLException {
        String localCatalog;
        String localSchema;
        String localTableName;
        if (dbMetadataUtils.getDatabaseMetaData().storesLowerCaseIdentifiers()) {
            localCatalog = config.getCatalog() == null ? null : config.getCatalog()
                    .toLowerCase();
            localSchema = config.getSchemaPattern() == null ? null : config.getSchemaPattern()
                    .toLowerCase();
            localTableName = config.getTableNamePattern() == null ? null : config
                    .getTableNamePattern().toLowerCase();
        } else if (dbMetadataUtils.getDatabaseMetaData().storesUpperCaseIdentifiers()) {
            localCatalog = config.getCatalog() == null ? null : config.getCatalog()
                    .toUpperCase();
            localSchema = config.getSchemaPattern() == null ? null : config.getSchemaPattern()
                    .toUpperCase();
            localTableName = config.getTableNamePattern() == null ? null : config
                    .getTableNamePattern().toUpperCase();
        } else {
            localCatalog = config.getCatalog();
            localSchema = config.getSchemaPattern();
            localTableName = config.getTableNamePattern();
        }
        DatabaseConfig newConfig = new DatabaseConfig(localCatalog, localSchema, localTableName);
        newConfig.setDatabaseProcess(config.getDatabaseProcess());
        return newConfig;
    }

    /**
     * 获取全部的表和字段
     *
     * @param config
     * @return
     * @throws SQLException
     */
    protected Map<IntrospectedTable, List<IntrospectedColumn>> getColumns(DatabaseConfig config) throws SQLException {
        Map<IntrospectedTable, List<IntrospectedColumn>> answer = new HashMap<IntrospectedTable, List<IntrospectedColumn>>();

        ResultSet rs = dbMetadataUtils.getDatabaseMetaData().getColumns(
                config.getCatalog(),
                config.getSchemaPattern(),
                config.getTableNamePattern(),
                null);
        while (rs.next()) {
            IntrospectedColumn column = new IntrospectedColumn();
            column.setJdbcType(rs.getInt("DATA_TYPE"));
            column.setType(rs.getString("TYPE_NAME"));
            column.setLength(rs.getInt("COLUMN_SIZE"));
            column.setName(rs.getString("COLUMN_NAME"));
            column.setNullable(rs.getInt("NULLABLE") == DatabaseMetaData.columnNullable);
            column.setScale(rs.getInt("DECIMAL_DIGITS"));
            column.setRemarks(rs.getString("REMARKS"));
            column.setDefaultValue(rs.getString("COLUMN_DEF"));

            IntrospectedTable table = new IntrospectedTable(
                    rs.getString("TABLE_CAT"),
                    rs.getString("TABLE_SCHEM"),
                    rs.getString("TABLE_NAME"));

            List<IntrospectedColumn> columns = answer.get(table);
            if (columns == null) {
                columns = new ArrayList<IntrospectedColumn>();
                answer.put(table, columns);
                if (config.hasProcess()) {
                    config.getDatabaseProcess().processTable(table);
                }
            }
            if (config.hasProcess()) {
                config.getDatabaseProcess().processColumn(table, column);
            }
            columns.add(column);
        }
        closeResultSet(rs);
        return answer;
    }

    /**
     * 处理表
     *
     * @param config
     * @param columns
     * @return
     * @throws SQLException
     */
    protected List<IntrospectedTable> calculateIntrospectedTables(
            DatabaseConfig config,
            Map<IntrospectedTable, List<IntrospectedColumn>> columns) throws SQLException {
        List<IntrospectedTable> answer = new ArrayList<IntrospectedTable>();
        //获取表注释信息
        Map<String, String> tableCommentsMap = getTableComments(config);
        Map<String, Map<String, String>> tableColumnCommentsMap = getColumnComments(config);
        for (Map.Entry<IntrospectedTable, List<IntrospectedColumn>> entry : columns
                .entrySet()) {
            IntrospectedTable table = entry.getKey();
            if (tableCommentsMap != null && tableCommentsMap.containsKey(table.getName())) {
                table.setRemarks(tableCommentsMap.get(table.getName()));
            }
            Map<String, String> columnCommentsMap = null;
            if (tableColumnCommentsMap != null && tableColumnCommentsMap.containsKey(table.getName())) {
                columnCommentsMap = tableColumnCommentsMap.get(table.getName());
            }
            for (IntrospectedColumn introspectedColumn : entry.getValue()) {
                FullyQualifiedJavaType fullyQualifiedJavaType = calculateJavaType(introspectedColumn);
                if (fullyQualifiedJavaType != null) {
                    introspectedColumn.setFullyQualifiedJavaType(fullyQualifiedJavaType);
                    introspectedColumn.setJdbcTypeName(calculateJdbcTypeName(introspectedColumn));
                }
                //转换为驼峰形式
                if (useCamelCase) {
                    introspectedColumn.setJavaProperty(JavaBeansUtil.getCamelCaseString(introspectedColumn.getName(), false));
                } else {
                    introspectedColumn.setJavaProperty(JavaBeansUtil.getValidPropertyName(introspectedColumn.getName()));
                }
                //处理注释
                if (columnCommentsMap != null && columnCommentsMap.containsKey(introspectedColumn.getName())) {
                    introspectedColumn.setRemarks(columnCommentsMap.get(introspectedColumn.getName()));
                }
                table.addColumn(introspectedColumn);
            }
            calculatePrimaryKey(config, table);
            answer.add(table);
        }
        return answer;
    }

    /**
     * 获取表名和注释映射
     *
     * @param config
     * @return
     * @throws SQLException
     */
    protected Map<String, String> getTableComments(DatabaseConfig config) throws SQLException {
        ResultSet rs = dbMetadataUtils.getDatabaseMetaData().getTables(config.getCatalog(), config.getSchemaPattern(), config.getTableNamePattern(), null);
        Map<String, String> answer = new HashMap<String, String>();
        while (rs.next()) {
            answer.put(rs.getString("TABLE_NAME"), rs.getString("REMARKS"));
        }
        closeResultSet(rs);
        return answer;
    }

    /**
     * 获取表名和列名-注释映射
     *
     * @param config
     * @return
     * @throws SQLException
     */
    protected Map<String, Map<String, String>> getColumnComments(DatabaseConfig config) throws SQLException {
        return null;
    }

    public static class JdbcTypeInformation {
        private String jdbcTypeName;

        private FullyQualifiedJavaType fullyQualifiedJavaType;

        public JdbcTypeInformation(String jdbcTypeName,
                                   FullyQualifiedJavaType fullyQualifiedJavaType) {
            this.jdbcTypeName = jdbcTypeName;
            this.fullyQualifiedJavaType = fullyQualifiedJavaType;
        }

        public String getJdbcTypeName() {
            return jdbcTypeName;
        }

        public FullyQualifiedJavaType getFullyQualifiedJavaType() {
            return fullyQualifiedJavaType;
        }
    }
}


package com.github.abel533.utils;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;
import java.security.Security;

public class EncrypDES {

    private static String defaultKey = "abel533l";
    // SecretKey 负责保存对称密钥
    private static SecretKey deskey;

    static {
        Security.addProvider(new com.sun.crypto.provider.SunJCE());
        // 生成密钥
        deskey = new SecretKeySpec(defaultKey.getBytes(), "DES");
        // 生成Cipher对象,指定其支持的DES算法
    }

    private static Cipher getCipher() {
        try {
            Cipher c = Cipher.getInstance("DES");
            return c;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 加密
     *
     * @param str
     * @return
     * @throws Exception
     */
    public static String Encrytor(String str) throws Exception {
        // 根据密钥，对Cipher对象进行初始化，ENCRYPT_MODE表示加密模式
        Cipher c = getCipher();
        c.init(Cipher.ENCRYPT_MODE, deskey);
        byte[] src = str.getBytes();
        // 加密，结果保存进cipherByte
        byte[] cipherByte = c.doFinal(src);
        return EncrypDES.byteArr2HexStr(cipherByte);
    }

    /**
     * 解密
     *
     * @param str
     * @return
     * @throws Exception
     */
    public static String Decryptor(String str) throws Exception {
        byte[] buff = EncrypDES.hexStr2ByteArr(str);
        // 根据密钥，对Cipher对象进行初始化，DECRYPT_MODE表示加密模式
        Cipher c = getCipher();
        c.init(Cipher.DECRYPT_MODE, deskey);
        byte[] cipherByte = c.doFinal(buff);
        return new String(cipherByte);
    }

    /**
     * 将byte数组转换为表示16进制值的字符串， 如：byte[]{8,18}转换为：0813， 和public static byte[]
     * hexStr2ByteArr(String strIn) 互为可逆的转换过程
     *
     * @param arrB 需要转换的byte数组
     * @return 转换后的字符串
     * @throws Exception 本方法不处理任何异常，所有异常全部抛出
     */
    public static String byteArr2HexStr(byte[] arrB) throws Exception {
        int iLen = arrB.length;
        // 每个byte用两个字符才能表示，所以字符串的长度是数组长度的两倍
        StringBuffer sb = new StringBuffer(iLen * 2);
        for (int i = 0; i < iLen; i++) {
            int intTmp = arrB[i];
            // 把负数转换为正数
            while (intTmp < 0) {
                intTmp = intTmp + 256;
            }
            // 小于0F的数需要在前面补0
            if (intTmp < 16) {
                sb.append("0");
            }
            sb.append(Integer.toString(intTmp, 16));
        }
        return sb.toString();
    }

    /**
     * 将表示16进制值的字符串转换为byte数组， 和public static String byteArr2HexStr(byte[] arrB)
     * 互为可逆的转换过程
     *
     * @param strIn 需要转换的字符串
     * @return 转换后的byte数组
     * @throws Exception 本方法不处理任何异常，所有异常全部抛出
     */
    public static byte[] hexStr2ByteArr(String strIn) throws Exception {
        byte[] arrB = strIn.getBytes();
        int iLen = arrB.length;

        // 两个字符表示一个字节，所以字节数组长度是字符串长度除以2
        byte[] arrOut = new byte[iLen / 2];
        for (int i = 0; i < iLen; i = i + 2) {
            String strTmp = new String(arrB, i, 2);
            arrOut[i / 2] = (byte) Integer.parseInt(strTmp, 16);
        }
        return arrOut;
    }

    /**
     * MD5加密
     *
     * @param info
     * @return
     * @throws Exception
     */
    public static String Md5(String info) throws Exception {
        // 根据MD5算法生成MessageDigest对象
        MessageDigest md5 = MessageDigest.getInstance("MD5");
        byte[] srcBytes = info.getBytes();
        // 使用srcBytes更新摘要
        md5.update(srcBytes);
        // 完成哈希计算，得到result
        byte[] resultBytes = md5.digest();
        return byteArr2HexStr(resultBytes);
    }
}


/*
 * Copyright 2011 Lincoln Baxter, III
 *
 * 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.
 */
package org.ocpsoft.rewrite.servlet.config;

import java.util.LinkedHashSet;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;

import org.ocpsoft.common.util.Assert;
import org.ocpsoft.rewrite.config.Condition;
import org.ocpsoft.rewrite.config.ConfigurationRuleParameterBuilder;
import org.ocpsoft.rewrite.context.EvaluationContext;
import org.ocpsoft.rewrite.param.ConfigurableParameter;
import org.ocpsoft.rewrite.param.Parameter;
import org.ocpsoft.rewrite.param.ParameterStore;
import org.ocpsoft.rewrite.param.Parameterized;
import org.ocpsoft.rewrite.param.ParameterizedPattern;
import org.ocpsoft.rewrite.param.ParameterizedPatternParser;
import org.ocpsoft.rewrite.param.RegexConstraint;
import org.ocpsoft.rewrite.param.RegexParameterizedPatternParser;
import org.ocpsoft.rewrite.servlet.config.bind.RequestBinding;
import org.ocpsoft.rewrite.servlet.http.event.HttpOutboundServletRewrite;
import org.ocpsoft.rewrite.servlet.http.event.HttpServletRewrite;
import org.ocpsoft.rewrite.servlet.spi.RequestParameterProvider;
import org.ocpsoft.urlbuilder.Address;
import org.ocpsoft.urlbuilder.AddressBuilder;

/**
 * A {@link Condition} that inspects the value of {@link HttpServletRewrite#getAddress()} path and query string.
 *
 * @author Lincoln Baxter, III
 */
public abstract class PathAndQuery extends HttpCondition implements Parameterized
{
   private final ParameterizedPatternParser expression;
   private boolean withRequestBinding = false;
   private String captureIn;

   private PathAndQuery(final String pattern)
   {
      Assert.notNull(pattern, "Pattern must not be null.");
      this.expression = new RegexParameterizedPatternParser(".+", pattern);
   }

   /**
    * Create a {@link Condition} that compares the current {@link Address#getPathAndQuery()} to the given pattern.
    *

    * The given pattern may be parameterized:
    *

    *
    *    /example?{param}=foo

    *    /example/{param1}?bar={param2}&p=q

    *    ...
    *
    *

    *
    * @param pattern {@link ParameterizedPattern} matching the path and query.
    *
    * @see ConfigurationRuleParameterBuilder#where(String)
    */
   public static PathAndQuery matches(final String pattern)
   {
      return new PathAndQuery(pattern) {
         @Override
         public String toString()
         {
            return "PathAndQuery.matches(\"" + pattern + "\")";
         }
      };
   }

   /**
    * Capture the entire path portion of the {@link Address} into the given {@link Parameter}.
    *
    * @param param the name of the {@link Parameter} to which the entire path portion of the {@link Address} will be
    *           bound.
    */
   public static PathAndQuery captureIn(final String param)
   {
      PathAndQuery path = new PathAndQuery("{" + param + "}") {
         @Override
         public String toString()
         {
            return "PathAndQuery.captureIn(\"" + param + "\")";
         }
      };
      path.captureIn = param;
      return path;
   }

   /**
    * Bind each path {@link Parameter} to the corresponding request parameter by name.
    *

    *
    * @see {@link ConfigurationRuleParameterBuilder#where(String)} {@link HttpServletRequest#getParameterMap()}
    *      {@link RequestParameterProvider}
    */
   public PathAndQuery withRequestBinding()
   {
      withRequestBinding = true;
      return this;
   }

   @Override
   public boolean evaluateHttp(final HttpServletRewrite event, final EvaluationContext context)
   {
      String url = null;

      if (event instanceof HttpOutboundServletRewrite)
      {
         url = ((HttpOutboundServletRewrite) event).getOutboundAddress().getPathAndQuery();
         if (url == null)
            /*
             * e.g an external url like http://ocpsoft.org (without trailing slash) or an anchor link have a null path
             */
            return false;
      }
      else
         url = AddressBuilder.begin()
                 .pathDecoded(event.getInboundAddress().getPath())
                 .queryLiteral(event.getInboundAddress().getQuery())
                 .build().toString();

      String contextPath = event.getContextPath();
      if (!contextPath.equals("/") && url.startsWith(contextPath))
         url = url.substring(contextPath.length());

      return (expression.parse(url).submit(event, context));
   }

   /**
    * Get the underlying {@link ParameterizedPattern} for this {@link PathAndQuery}
    */
   public ParameterizedPatternParser getExpression()
   {
      return expression;
   }

   @Override
   public Set<String> getRequiredParameterNames()
   {
      Set<String> result = new LinkedHashSet<String>();
      if (captureIn != null)
         result.add(captureIn);
      else
         result.addAll(expression.getRequiredParameterNames());
      return result;
   }

   @Override
   public void setParameterStore(ParameterStore store)
   {
      if (captureIn != null)
      {
         Parameter<?> parameter = store.get(captureIn);
         if (parameter instanceof ConfigurableParameter<?>)
            ((ConfigurableParameter<?>) parameter).constrainedBy(new RegexConstraint(".*"));
      }
      if (withRequestBinding)
      {
         for (String param : getRequiredParameterNames())
         {
            Parameter<?> parameter = store.get(param);
            if (parameter instanceof ConfigurableParameter<?>)
               ((ConfigurableParameter<?>) parameter).bindsTo(RequestBinding.parameter(param));
         }
         withRequestBinding = true;
      }

      expression.setParameterStore(store);
   }
}


package org.ocpsoft.rewrite.faces.config;

import javax.faces.context.FacesContext;
import javax.faces.event.PhaseId;

import org.ocpsoft.common.pattern.Weighted;
import org.ocpsoft.common.services.ServiceLoader;
import org.ocpsoft.logging.Logger;
import org.ocpsoft.rewrite.bind.Retrieval;
import org.ocpsoft.rewrite.bind.Submission;
import org.ocpsoft.rewrite.config.Invoke;
import org.ocpsoft.rewrite.config.Operation;
import org.ocpsoft.rewrite.context.EvaluationContext;
import org.ocpsoft.rewrite.exception.RewriteException;
import org.ocpsoft.rewrite.servlet.event.BaseRewrite.ServletRewriteFlow;
import org.ocpsoft.rewrite.servlet.event.ServletRewrite;
import org.ocpsoft.rewrite.servlet.event.SubflowTask;
import org.ocpsoft.rewrite.servlet.http.event.HttpInboundServletRewrite;
import org.ocpsoft.rewrite.servlet.http.event.HttpServletRewrite;
import org.ocpsoft.rewrite.spi.InvocationResultHandler;

/**
 * An {@link Operation} that invokes an action before or after a given JavaServer Faces {@link PhaseId}. Has a
 * {@link Weighted#priority()} of 0.
 *
 * @author Lincoln Baxter, III
 */
public abstract class PhaseAction extends PhaseOperation<PhaseAction>
{
   private static final Logger log = Logger.getLogger(Invoke.class);
   private final Submission submission;
   private final Retrieval retrieval;

   /**
    * Create an {@link Operation} that invokes the given {@link Retrieval} and processes {@link InvocationResultHandler}
    * instances on the result value (if any).
    *

    * By default, this action is invoked after {@link PhaseId#RESTORE_VIEW}
    */
   public static PhaseAction retrieveFrom(final Retrieval retrieval)
   {
      return new PhaseAction(null, retrieval) {

         @Override
         public String toString()
         {
            return "PhaseAction.retrieveFrom(" + retrieval + ")";
         }

      }.after(PhaseId.RESTORE_VIEW);
   }

   /**
    * Create an {@link Operation} that invokes
    * {@link Submission#submit(org.ocpsoft.rewrite.event.Rewrite, EvaluationContext, Object)}, and uses the result of
    * the given {@link Retrieval#retrieve(org.ocpsoft.rewrite.event.Rewrite, EvaluationContext)} as the value for this
    * submission. Process {@link InvocationResultHandler} instances on the result value (if any).
    *

    * By default, this action is invoked after {@link PhaseId#RESTORE_VIEW}
    */
   public static PhaseAction submitTo(final Submission to, final Retrieval from)
   {
      return new PhaseAction(to, from) {

         @Override
         public String toString()
         {
            return "PhaseAction.submitTo(" + to + ", " + from + ")";
         }

      }.after(PhaseId.RESTORE_VIEW);
   }

   @Override
   public int priority()
   {
      return 0;
   }

   private PhaseAction(final Submission submission, final Retrieval retrieval)
   {
      this.submission = submission;
      this.retrieval = retrieval;
   }

   @Override
   @SuppressWarnings("unchecked")
   public void performOperation(final HttpServletRewrite event, final EvaluationContext context)
   {
      SubflowTask.perform(event, context, ServletRewriteFlow.UN_HANDLED, new SubflowTask() {

         @Override
         public void performInSubflow(ServletRewrite<?, ?> event, EvaluationContext context)
         {
            Object result = null;
            if (retrieval != null)
            {
               result = retrieval.retrieve(event, context);
               log.debug("Invoked binding [" + submission + "] returned value [" + result + "]");
            }
            if (submission != null)
            {
               result = submission.submit(event, context, result);
               log.debug("Invoked binding [" + retrieval + "] returned value [" + result + "]");
            }
            if (retrieval == null && submission == null)
            {
               log.warn("No binding specified for Invocation.");
            }

            if (result instanceof Operation)
            {
               ((Operation) result).perform(event, context);
            }
            else if (result != null)
            {
               ServiceLoader<InvocationResultHandler> providers = ServiceLoader.load(InvocationResultHandler.class);
               if (!providers.iterator().hasNext())
               {
                  log.debug("No instances of [" + InvocationResultHandler.class.getName()
                           + "] were registered to handing binding invocation result [" + result + "]");
               }

               for (InvocationResultHandler handler : providers) {
                  handler.handle(event, context, result);
               }
            }

            if (result != null)
            {
               try {
                  if (event.getFlow().is(ServletRewriteFlow.ABORT_REQUEST))
                  {
                     FacesContext facesContext = FacesContext.getCurrentInstance();
                     if (event.getFlow().is(ServletRewriteFlow.FORWARD))
                     {
                        String dispatchResource = ((HttpInboundServletRewrite) event).getDispatchResource();
                        facesContext.getExternalContext().dispatch(dispatchResource);
                     }
                     facesContext.responseComplete();
                  }
                  else if (event.getFlow().is(ServletRewriteFlow.INCLUDE))
                  {
                     throw new IllegalStateException(
                              "Cannot issue INCLUDE directive within JSF lifecycle. Not supported.");
                  }
               }
               catch (Exception e) {
                  throw new RewriteException("", e);
               }
            }

         }
      });
   }

}


package org.ocpsoft.rewrite.faces;

import java.util.Collections;
import java.util.List;

import javax.servlet.http.HttpServletRequest;

import org.ocpsoft.common.pattern.WeightedComparator;
import org.ocpsoft.common.services.ServiceLoader;
import org.ocpsoft.common.util.Iterators;
import org.ocpsoft.rewrite.servlet.DispatcherType;
import org.ocpsoft.rewrite.servlet.http.HttpRewriteLifecycleListener;
import org.ocpsoft.rewrite.servlet.http.event.HttpServletRewrite;
import org.ocpsoft.rewrite.servlet.spi.DispatcherTypeProvider;

/**
 * @author Lincoln Baxter, III
 *
 */
public class FacesRewriteLifecycleListener extends HttpRewriteLifecycleListener
{
   private static final String ORIGINAL_URL = FacesRewriteLifecycleListener.class.getName()
            + "_originalRequestURL";

   private volatile List<DispatcherTypeProvider> dispatcherTypeProviders = null;

   @Override
   public void beforeInboundLifecycle(final HttpServletRewrite event)
   {
      if (DispatcherType.REQUEST.equals(getDispatcherType(event)))
      {
         event.getRequest().setAttribute(ORIGINAL_URL, event.getAddress().getPathAndQuery());
      }
   }

   /**
    * Determines the {@link DispatcherType} of the current request using the {@link DispatcherTypeProvider} SPI.
    */
   private DispatcherType getDispatcherType(final HttpServletRewrite event)
   {
      for (DispatcherTypeProvider provider : getDispatcherTypeProviders()) {
         DispatcherType dispatcherType = provider.getDispatcherType(event.getRequest(), event.getServletContext());
         if (dispatcherType != null) {
            return dispatcherType;
         }
      }
      throw new IllegalStateException("Unable to determine dispatcher type of current request");
   }

   /**
    * Returns the list of {@link DispatcherTypeProvider} implementations.
    */
   private List<DispatcherTypeProvider> getDispatcherTypeProviders()
   {
      List<DispatcherTypeProvider> result = dispatcherTypeProviders;
      if (result == null) {
         synchronized(this) {
            result = dispatcherTypeProviders;
            if (result == null) {
               result = Iterators.asList(ServiceLoader.loadTypesafe(DispatcherTypeProvider.class).iterator());
               Collections.sort(result, new WeightedComparator());
               dispatcherTypeProviders = result;
            }
         }
      }
      return result;
   }

   public static String getOriginalRequestURL(final HttpServletRequest request)
   {
      return (String) request.getAttribute(ORIGINAL_URL);
   }

   @Override
   public void beforeInboundRewrite(final HttpServletRewrite event)
   {}

   @Override
   public void afterInboundRewrite(final HttpServletRewrite event)
   {}

   @Override
   public void beforeOutboundRewrite(final HttpServletRewrite event)
   {}

   @Override
   public void afterOutboundRewrite(final HttpServletRewrite event)
   {}

   @Override
   public void afterInboundLifecycle(final HttpServletRewrite event)
   {}

   @Override
   public int priority()
   {
      return 0;
   }

}


/*
 * Copyright 2011 Lincoln Baxter, III
 *
 * 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.
 */
package org.ocpsoft.rewrite.util;

import java.util.Arrays;
import java.util.Stack;

import org.ocpsoft.common.util.Assert;

/**
 * @author Lincoln Baxter, III
 *
 */
public abstract class ParseTools
{
   private static final char ESCAPE_CHAR = '\\';

   /**
    * Return the index of the terminating character in the group, excluding markers.
    */
   public static CapturingGroup balancedCapture(final char[] chars, final int startPos, final int endPos,
            final CaptureType type)
   {
      Assert.notNull(chars, "Character input array must not be null.");
      Assert.assertTrue(startPos >= 0, "Start position must be greater than zero.");
      Assert.assertTrue(startPos < (chars.length - 1),
               "Start position must be at least one character before the array upper bound.");
      Assert.assertTrue(endPos > 0, "End position must be greater than zero.");
      Assert.assertTrue(endPos < chars.length, "End position must be less than or equal to the array upper bound.");
      Assert.assertTrue(startPos < endPos, "Start position must be less than end position.");

      Assert.assertTrue(chars[startPos] == type.getBegin(), "Character at starting position was [" + chars[startPos]
               + "] but expected [" + type.getBegin() + "]");

      if (isEscaped(chars, startPos))
      {
         throw new IllegalArgumentException(
                  "Character at starting position is escaped, and cannot be used in capturing a group.");
      }

      char begin = type.getBegin();
      char end = type.getEnd();

      int cursor = startPos + 1;
      Stack<Integer> beginPositions = new Stack<Integer>();
      beginPositions.push(cursor);
      while ((cursor <= endPos) && (beginPositions.size() > 0))
      {
         char character = chars[cursor];
         if (character == end)
         {
            if (!isEscaped(chars, cursor))
            {
               beginPositions.pop();
            }
         }
         else if (character == begin)
         {
            if (!isEscaped(chars, cursor))
            {
               beginPositions.push(cursor);
            }
         }

         if (beginPositions.size() == 0)
         {
            break;
         }
         cursor++;
      }

      if (beginPositions.size() > 0)
      {
         throw new IllegalArgumentException(
                  "Unclosed capturing group at index [" + beginPositions.peek() + "] of [" + new String(chars) + "]");
      }

      return new CapturingGroup(chars, startPos, cursor);
   }

   /**
    * Return true if the character at the given cursor is escaped; otherwise, return false.
    */
   public static boolean isEscaped(final char[] chars, final int cursor)
   {
      Assert.notNull(chars, "Character input array must not be null.");
      Assert.assertTrue(cursor >= 0, "Start position must be greater than zero.");
      Assert.assertTrue(cursor < (chars.length),
               "Start position must be within the array upper bound.");

      if ((cursor > 0) && (chars[cursor - 1] == ESCAPE_CHAR))
      {
         if ((cursor == 1) || ((cursor > 1) && (chars[cursor - 2] != ESCAPE_CHAR)))
         {
            return true;
         }
      }
      return false;
   }

   public static class CapturingGroup
   {
      private final char[] chars;
      private final int start;
      private final int end;

      public CapturingGroup(final char[] chars, final int start, final int end)
      {
         this.chars = chars;
         this.start = start;
         this.end = end;
      }

      public int getStart()
      {
         return start;
      }

      public int getEnd()
      {
         return end;
      }

      public char[] getCaptured()
      {
         return Arrays.copyOfRange(chars, start + 1, end);
      }

      @Override
      public String toString()
      {
         return "CapturingGroup [start=" + start + ", end=" + end + "]";
      }
   }

   public enum CaptureType
   {
      BRACE('{', '}'), BRACKET('[', ']'), PAREN('(', ')'), REGEX('/', '/');

      private char begin;
      private char end;

      private CaptureType(final char begin, final char end)
      {
         this.begin = begin;
         this.end = end;
      }

      public char getBegin()
      {
         return begin;
      }

      public char getEnd()
      {
         return end;
      }
   }
}


/*
 * Copyright 2011 Lincoln Baxter, III
 *
 * 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.
 */
package org.ocpsoft.rewrite.config;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.ocpsoft.common.util.Assert;
import org.ocpsoft.common.util.Strings;
import org.ocpsoft.rewrite.context.EvaluationContext;
import org.ocpsoft.rewrite.event.Rewrite;

/**
 * Evaluates all provided {@link Condition} instances. If all provided conditions return true, this
 * condition returns true. If any provided conditions return false, this condition returns
 * false.
 *
 * @author Lincoln Baxter, III
 */
public final class And extends DefaultConditionBuilder implements CompositeCondition
{
   private final Condition[] conditions;

   private And(final Condition... conditions)
   {
      this.conditions = conditions;
   }

   /**
    * Creates a new {@link And} condition. If all provided {@link Condition} instances return true, this
    * condition returns true. If any provided conditions return false, this condition returns
    * false.
    *
    * @param conditions the array of conditions to be evaluated
    */
   public static And all(final Condition... conditions)
   {
      Assert.notNull(conditions, "At least one condition is required.");
      Assert.assertTrue(conditions.length > 0, "At least one condition is required.");
      return new And(flattenConditions(Arrays.asList(conditions)).toArray(new Condition[] {}));
   }

   private static List<Condition> flattenConditions(List<Condition> conditions)
   {
      List<Condition> result = new ArrayList<Condition>();
      for (Condition condition : conditions) {
         if (condition instanceof And)
         {
            result.addAll(flattenConditions(((And) condition).getConditions()));
         }
         else
         {
            result.add(condition);
         }
      }
      return result;
   }

   @Override
   public boolean evaluate(final Rewrite event, final EvaluationContext context)
   {
      boolean result = true;
      for (int i = 0; i < conditions.length; i++) {
         if (!conditions[i].evaluate(event, context))
         {
            result = false;
            break;
         }
      }
      return result;
   }

   @Override
   public List<Condition> getConditions()
   {
      return Arrays.asList(conditions);
   }

   @Override
   public String toString()
   {
      return "And.all(" + Strings.join(getConditions(), ", ") + ")";
   }
}


/*
 * Copyright 2010 Lincoln Baxter, III
 *
 * 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.
 */
package org.ocpsoft.rewrite.servlet.util;

import java.util.List;

import org.ocpsoft.urlbuilder.AddressBuilder;

/**
 * @deprecated Use {@link AddressBuilder} instead. May be removed in subsequent releases.
 * @author Lincoln Baxter, III
 */
@Deprecated
public class Metadata
{
   private String encoding = "UTF-8";
   private boolean trailingSlash = false;
   private boolean leadingSlash = false;

   /**
    * Return a copy of this Metadata
    */
   public Metadata copy()
   {
      Metadata result = new Metadata();
      result.setEncoding(encoding);
      result.setTrailingSlash(trailingSlash);
      result.setLeadingSlash(leadingSlash);
      return result;
   }

   public String buildURLFromSegments(final List<String> segments)
   {
      StringBuilder result = new StringBuilder();

      if (hasLeadingSlash())
      {
         result.append("/");
      }

      /*
       * Normal loop for performance reasons
       */
      for (int i = 0; i < segments.size(); i++) {
         String segment = segments.get(i);
         result.append(segment);
         if (i < (segments.size() - 1))
         {
            result.append("/");
         }
      }

      if (hasTrailingSlash() && (!segments.isEmpty() || (result.indexOf("/") < 0)))
      {
         result.append("/");
      }
      return result.toString();
   }

   /*
    * Getters & Setters
    */

   public String getEncoding()
   {
      return encoding;
   }

   public void setEncoding(final String encoding)
   {
      this.encoding = encoding;
   }

   public boolean hasTrailingSlash()
   {
      return trailingSlash;
   }

   public void setTrailingSlash(final boolean trailingSlash)
   {
      this.trailingSlash = trailingSlash;
   }

   public boolean hasLeadingSlash()
   {
      return leadingSlash;
   }

   public void setLeadingSlash(final boolean leadingSlash)
   {
      this.leadingSlash = leadingSlash;
   }
}


/*
 * Copyright 2011 Lincoln Baxter, III
 *
 * 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.
 */
package org.ocpsoft.rewrite.servlet.config;

import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;

import javax.servlet.http.HttpServletRequest;

import org.ocpsoft.rewrite.context.EvaluationContext;
import org.ocpsoft.rewrite.servlet.http.event.HttpServletRewrite;

/**
 * Responsible for performing actions on the current {@link HttpServletRequest}
 *
 * @author Christian Kaltepoth
 */
public abstract class Request extends HttpOperation
{

   /**
    * Creates an {@link HttpOperation} that calls {@link HttpServletRequest#setCharacterEncoding(String)} on the current
    * request.
    *
    * @param encoding The encoding to apply
    */
   public static Request setCharacterEncoding(final String encoding)
   {
      return new Request() {
         @Override
         public void performHttp(HttpServletRewrite event, EvaluationContext context)
         {
            try {
               event.getRequest().setCharacterEncoding(encoding);
            }
            catch (UnsupportedEncodingException e) {
               throw new IllegalArgumentException("Failed to set encoding " + encoding, e);
            }
         }

         @Override
         public String toString()
         {
            return "Request.setCharacterEncoding(\"" + encoding + "\")";
         }
      };
   }

   /**
    * Creates an {@link HttpOperation} that calls {@link HttpServletRequest#setCharacterEncoding(String)} on the current
    * request.
    *
    * @param charset The character set to apply
    */
   public static Request setCharacterEncoding(final Charset charset)
   {
      return setCharacterEncoding(charset.name());
   }

   /**
    * Creates an {@link HttpOperation} that calls {@link HttpServletRequest#setAttribute(String, Object)} on the current
    * request.
    *
    * @param name The name of the request attribute
    * @param value The value to set
    */
   public static Request setAttribute(final String name, final Object value)
   {
      return new Request() {
         @Override
         public void performHttp(HttpServletRewrite event, EvaluationContext context)
         {
            event.getRequest().setAttribute(name, value);
         }

         @Override
         public String toString()
         {
            return "Request.setAttribute(\"" + name + "\", \"" + value + "\")";
         }
      };
   }

}


/*
 * Copyright 2011 Lincoln Baxter, III
 *
 * 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.
 */
package org.ocpsoft.rewrite.faces.config;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

import javax.faces.event.PhaseId;

import org.ocpsoft.rewrite.bind.Binding;
import org.ocpsoft.rewrite.bind.Submission;
import org.ocpsoft.rewrite.config.Operation;
import org.ocpsoft.rewrite.context.EvaluationContext;
import org.ocpsoft.rewrite.event.Rewrite;
import org.ocpsoft.rewrite.param.Converter;
import org.ocpsoft.rewrite.param.Validator;
import org.ocpsoft.rewrite.servlet.config.HttpOperation;
import org.ocpsoft.rewrite.servlet.config.SendStatus;
import org.ocpsoft.rewrite.servlet.http.event.HttpServletRewrite;
import org.ocpsoft.rewrite.util.ValueHolderUtil;

/**
 * An {@link Operation} that wraps & holds a {@link Submission} until before or after a given JavaServer Faces
 * {@link PhaseId}. Validation and conversion of the given {@link Submission} are also deferred to within the Faces
 * lifecycle.
 *
 * @author Lincoln Baxter, III
 * @author Fabien Marsaud
 */
public abstract class PhaseBinding implements Binding
{
   private final Set<PhaseId> beforePhases = new HashSet<PhaseId>();
   private final Set<PhaseId> afterPhases = new HashSet<PhaseId>();

   private final Submission deferred;

   private Validator<?> validator;
   private Converter<?> converter;
   private Operation operation = SendStatus.error(404);

   private PhaseBinding(Submission binding)
   {
      this.deferred = binding;
   }

   /**
    * Create a {@link Binding} to process the given {@link Submission} during the Faces life-cycle.
    */
   public static PhaseBinding to(final Submission submission)
   {
      return new PhaseBinding(submission) {
         @Override
         public String toString()
         {
            return "PhaseBinding.to(" + submission + ")";
         }
      };
   }

   /**
    * Perform this {@link PhaseBinding} before the given phases (Except {@link PhaseId#RESTORE_VIEW}). The deferred
    * {@link Submission} will be performed once for each {@link PhaseId} provided.
    */
   public PhaseBinding before(final PhaseId... phases)
   {
      if (phases != null)
         this.beforePhases.addAll(Arrays.asList(phases));
      return this;
   }

   /**
    * Perform this {@link PhaseBinding} after the given phases (Except {@link PhaseId#RENDER_RESPONSE}). The deferred
    * {@link Submission} will be performed once for each {@link PhaseId} provided.
    */
   public PhaseBinding after(final PhaseId... phases)
   {
      if (phases != null)
         this.afterPhases.addAll(Arrays.asList(phases));
      return this;
   }

   /**
    * Validate the {@link Binding} value using the given {@link Validator}.
    */
   public PhaseBinding validatedBy(Validator<?> validator)
   {
      this.validator = validator;
      return this;
   }

   /**
    * Convert the {@link Binding} value using the given {@link Converter}.
    */
   public PhaseBinding convertedBy(Converter<?> converter)
   {
      this.converter = converter;
      return this;
   }

   /**
    * On validation failure, perform the given {@link Operation}; defaults to {@link SendStatus#code(int)} error code
    * 404 unless otherwise specified.
    */
   public PhaseBinding onValidationFailure(Operation operation)
   {
      this.operation = operation;
      return this;
   }

   @Override
   public Object retrieve(Rewrite event, EvaluationContext context)
   {
      throw new IllegalStateException("PhaseBinding does not support retrieval.");
   }

   @Override
   public Object submit(Rewrite event, EvaluationContext context, Object value)
   {

      /**
       * Must occur before queued {@link PhaseAction} instances during the same phase.
       */
      PhaseOperation.enqueue(new PhaseBindingOperation(value), -5)
               .before(beforePhases.toArray(new PhaseId[] {}))
               .after(afterPhases.toArray(new PhaseId[] {}))
               .perform(event, context);

      return null;
   }

   @Override
   public boolean supportsRetrieval()
   {
      return false;
   }

   @Override
   public boolean supportsSubmission()
   {
      return true;
   }

   private class PhaseBindingOperation extends HttpOperation
   {
      private Object value;

      public PhaseBindingOperation(Object value)
      {
         this.value = value;
      }

      @Override
      public void performHttp(HttpServletRewrite event, EvaluationContext context)
      {

         Object converted = value;

         if (converter != null)
            converted = ValueHolderUtil.convert(event, context, converter, value);

         if (validator != null && !ValueHolderUtil.validates(event, context, validator, converted))
         {
            if (operation != null)
               operation.perform(event, context);
         }
         else
         {
            deferred.submit(event, context, converted);
         }
      }

   }

}