diff options
author | James Moger <james.moger@gmail.com> | 2013-03-08 21:14:50 -0500 |
---|---|---|
committer | James Moger <james.moger@gmail.com> | 2013-03-08 21:14:50 -0500 |
commit | d8915c7da130b8a6de6f2c911effe0e10dbe4d12 (patch) | |
tree | dec81ecb015b1e1102f019fd0d3d3ac5bb5492d0 /src/main/java/com/iciql/util | |
parent | 926634baaccf8f19f30fa179298ca7edebfeb58d (diff) | |
download | iciql-d8915c7da130b8a6de6f2c911effe0e10dbe4d12.tar.gz iciql-d8915c7da130b8a6de6f2c911effe0e10dbe4d12.zip |
Conform to Apache standard directory layout
Diffstat (limited to 'src/main/java/com/iciql/util')
-rw-r--r-- | src/main/java/com/iciql/util/GenerateModels.java | 193 | ||||
-rw-r--r-- | src/main/java/com/iciql/util/IciqlLogger.java | 214 | ||||
-rw-r--r-- | src/main/java/com/iciql/util/JdbcUtils.java | 254 | ||||
-rw-r--r-- | src/main/java/com/iciql/util/Slf4jIciqlListener.java | 92 | ||||
-rw-r--r-- | src/main/java/com/iciql/util/StatementBuilder.java | 166 | ||||
-rw-r--r-- | src/main/java/com/iciql/util/StringUtils.java | 382 | ||||
-rw-r--r-- | src/main/java/com/iciql/util/Utils.java | 459 | ||||
-rw-r--r-- | src/main/java/com/iciql/util/WeakIdentityHashMap.java | 243 | ||||
-rw-r--r-- | src/main/java/com/iciql/util/package.html | 25 |
9 files changed, 2028 insertions, 0 deletions
diff --git a/src/main/java/com/iciql/util/GenerateModels.java b/src/main/java/com/iciql/util/GenerateModels.java new file mode 100644 index 0000000..eac9f6c --- /dev/null +++ b/src/main/java/com/iciql/util/GenerateModels.java @@ -0,0 +1,193 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 James Moger. + * + * 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 com.iciql.util; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.Writer; +import java.sql.SQLException; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.iciql.Db; +import com.iciql.DbInspector; + +/** + * Generates iciql models. + */ +public class GenerateModels { + + /** + * The output stream where this tool writes to. + */ + protected PrintStream out = System.out; + + public static void main(String... args) { + GenerateModels tool = new GenerateModels(); + try { + tool.runTool(args); + } catch (SQLException e) { + tool.out.print("Error: "); + tool.out.println(e.getMessage()); + tool.out.println(); + tool.showUsage(); + } + } + + public void runTool(String... args) throws SQLException { + String url = null; + String user = "sa"; + String password = ""; + String schema = null; + String table = null; + String packageName = ""; + String folder = null; + boolean annotateSchema = true; + boolean trimStrings = false; + for (int i = 0; args != null && i < args.length; i++) { + String arg = args[i]; + if (arg.equals("-url")) { + url = args[++i]; + } else if (arg.equals("-user")) { + user = args[++i]; + } else if (arg.equals("-password")) { + password = args[++i]; + } else if (arg.equals("-schema")) { + schema = args[++i]; + } else if (arg.equals("-table")) { + table = args[++i]; + } else if (arg.equals("-package")) { + packageName = args[++i]; + } else if (arg.equals("-folder")) { + folder = args[++i]; + } else if (arg.equals("-annotateSchema")) { + try { + annotateSchema = Boolean.parseBoolean(args[++i]); + } catch (Throwable t) { + throw new SQLException("Can not parse -annotateSchema value"); + } + } else if (arg.equals("-trimStrings")) { + try { + trimStrings = Boolean.parseBoolean(args[++i]); + } catch (Throwable t) { + throw new SQLException("Can not parse -trimStrings value"); + } + } else { + throwUnsupportedOption(arg); + } + } + if (url == null) { + throw new SQLException("URL not set"); + } + execute(url, user, password, schema, table, packageName, folder, annotateSchema, trimStrings); + } + + /** + * Generates models from the database. + * + * @param url + * the database URL + * @param user + * the user name + * @param password + * the password + * @param schema + * the schema to read from. null for all schemas. + * @param table + * the table to model. null for all tables within schema. + * @param packageName + * the package name of the model classes. + * @param folder + * destination folder for model classes (package path not + * included) + * @param annotateSchema + * includes the schema in the table model annotations + * @param trimStrings + * automatically trim strings that exceed maxLength + */ + public static void execute(String url, String user, String password, String schema, String table, + String packageName, String folder, boolean annotateSchema, boolean trimStrings) + throws SQLException { + try { + Db db; + if (password == null) { + db = Db.open(url, user, (String) null); + } else { + db = Db.open(url, user, password); + } + DbInspector inspector = new DbInspector(db); + List<String> models = inspector.generateModel(schema, table, packageName, annotateSchema, + trimStrings); + File parentFile; + if (StringUtils.isNullOrEmpty(folder)) { + parentFile = new File(System.getProperty("user.dir")); + } else { + parentFile = new File(folder); + } + parentFile.mkdirs(); + Pattern p = Pattern.compile("class ([a-zA-Z0-9]+)"); + for (String model : models) { + Matcher m = p.matcher(model); + if (m.find()) { + String className = m.group().substring("class".length()).trim(); + File classFile = new File(parentFile, className + ".java"); + Writer o = new FileWriter(classFile, false); + PrintWriter writer = new PrintWriter(new BufferedWriter(o)); + writer.write(model); + writer.close(); + System.out.println("Generated " + classFile.getAbsolutePath()); + } + } + } catch (IOException io) { + throw new SQLException("could not generate model", io); + } + } + + /** + * Throw a SQLException saying this command line option is not supported. + * + * @param option + * the unsupported option + * @return this method never returns normally + */ + protected SQLException throwUnsupportedOption(String option) throws SQLException { + showUsage(); + throw new SQLException("Unsupported option: " + option); + } + + protected void showUsage() { + out.println("GenerateModels"); + out.println("Usage:"); + out.println(); + out.println("(*) -url jdbc:h2:~test"); + out.println(" -user <string>"); + out.println(" -password <string>"); + out.println(" -schema <string>"); + out.println(" -table <string>"); + out.println(" -package <string>"); + out.println(" -folder <string>"); + out.println(" -annotateSchema <boolean>"); + out.println(" -trimStrings <boolean>"); + } + +} diff --git a/src/main/java/com/iciql/util/IciqlLogger.java b/src/main/java/com/iciql/util/IciqlLogger.java new file mode 100644 index 0000000..d8005bb --- /dev/null +++ b/src/main/java/com/iciql/util/IciqlLogger.java @@ -0,0 +1,214 @@ +/* + * Copyright 2011 James Moger. + * + * 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 com.iciql.util; + +import java.text.DecimalFormat; +import java.text.MessageFormat; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicLong; + +import com.iciql.IciqlException; + +/** + * Utility class to optionally log generated statements to IciqlListeners.<br> + * Statement logging is disabled by default. + * <p> + * This class also tracks the counts for generated statements by major type. + * + */ +public class IciqlLogger { + + /** + * Enumeration of the different statement types that are logged. + */ + public enum StatementType { + STAT, TOTAL, CREATE, INSERT, UPDATE, MERGE, DELETE, SELECT, DROP, WARN; + } + + /** + * Interface that defines an iciql listener. + */ + public interface IciqlListener { + void logIciql(StatementType type, String statement); + } + + private static final ExecutorService EXEC = Executors.newSingleThreadExecutor(); + private static final Set<IciqlListener> LISTENERS = Utils.newHashSet(); + private static final IciqlListener CONSOLE = new IciqlListener() { + + @Override + public void logIciql(StatementType type, String message) { + System.out.println(message); + } + }; + + private static final AtomicLong SELECT_COUNT = new AtomicLong(); + private static final AtomicLong CREATE_COUNT = new AtomicLong(); + private static final AtomicLong INSERT_COUNT = new AtomicLong(); + private static final AtomicLong UPDATE_COUNT = new AtomicLong(); + private static final AtomicLong MERGE_COUNT = new AtomicLong(); + private static final AtomicLong DELETE_COUNT = new AtomicLong(); + private static final AtomicLong DROP_COUNT = new AtomicLong(); + private static final AtomicLong WARN_COUNT = new AtomicLong(); + + /** + * Activates the Console Logger. + */ + public static void activateConsoleLogger() { + registerListener(CONSOLE); + } + + /** + * Deactivates the Console Logger. + */ + public static void deactivateConsoleLogger() { + unregisterListener(CONSOLE); + } + + /** + * Registers a listener with the relay. + * + * @param listener + */ + public static void registerListener(IciqlListener listener) { + LISTENERS.add(listener); + } + + /** + * Unregisters a listener with the relay. + * + * @param listener + */ + public static void unregisterListener(IciqlListener listener) { + if (!LISTENERS.remove(listener)) { + throw new IciqlException("Failed to remove iciql listener {0}", listener); + } + } + + public static void create(String statement) { + CREATE_COUNT.incrementAndGet(); + logStatement(StatementType.CREATE, statement); + } + + public static void insert(String statement) { + INSERT_COUNT.incrementAndGet(); + logStatement(StatementType.INSERT, statement); + } + + public static void update(String statement) { + UPDATE_COUNT.incrementAndGet(); + logStatement(StatementType.UPDATE, statement); + } + + public static void merge(String statement) { + MERGE_COUNT.incrementAndGet(); + logStatement(StatementType.MERGE, statement); + } + + public static void delete(String statement) { + DELETE_COUNT.incrementAndGet(); + logStatement(StatementType.DELETE, statement); + } + + public static void select(String statement) { + SELECT_COUNT.incrementAndGet(); + logStatement(StatementType.SELECT, statement); + } + + public static void drop(String statement) { + DROP_COUNT.incrementAndGet(); + logStatement(StatementType.DROP, statement); + } + + public static void warn(String message, Object... args) { + WARN_COUNT.incrementAndGet(); + logStatement(StatementType.WARN, args.length > 0 ? MessageFormat.format(message, args) : message); + } + + private static void logStatement(final StatementType type, final String statement) { + for (final IciqlListener listener : LISTENERS) { + EXEC.execute(new Runnable() { + public void run() { + listener.logIciql(type, statement); + } + }); + } + } + + public static long getCreateCount() { + return CREATE_COUNT.longValue(); + } + + public static long getInsertCount() { + return INSERT_COUNT.longValue(); + } + + public static long getUpdateCount() { + return UPDATE_COUNT.longValue(); + } + + public static long getMergeCount() { + return MERGE_COUNT.longValue(); + } + + public static long getDeleteCount() { + return DELETE_COUNT.longValue(); + } + + public static long getSelectCount() { + return SELECT_COUNT.longValue(); + } + + public static long getDropCount() { + return DROP_COUNT.longValue(); + } + + public static long getWarnCount() { + return WARN_COUNT.longValue(); + } + + public static long getTotalCount() { + return getCreateCount() + getInsertCount() + getUpdateCount() + getDeleteCount() + getMergeCount() + + getSelectCount() + getDropCount(); + } + + public static void logStats() { + logStatement(StatementType.STAT, "iciql Runtime Statistics"); + logStatement(StatementType.STAT, "========================"); + logStat(StatementType.WARN, getWarnCount()); + logStatement(StatementType.STAT, "========================"); + logStat(StatementType.CREATE, getCreateCount()); + logStat(StatementType.INSERT, getInsertCount()); + logStat(StatementType.UPDATE, getUpdateCount()); + logStat(StatementType.MERGE, getMergeCount()); + logStat(StatementType.DELETE, getDeleteCount()); + logStat(StatementType.SELECT, getSelectCount()); + logStat(StatementType.DROP, getDropCount()); + logStatement(StatementType.STAT, "========================"); + logStat(StatementType.TOTAL, getTotalCount()); + } + + private static void logStat(StatementType type, long value) { + if (value > 0) { + DecimalFormat df = new DecimalFormat("###,###,###,###"); + logStatement(StatementType.STAT, + StringUtils.pad(type.name(), 6, " ", true) + " = " + df.format(value)); + } + } +}
\ No newline at end of file diff --git a/src/main/java/com/iciql/util/JdbcUtils.java b/src/main/java/com/iciql/util/JdbcUtils.java new file mode 100644 index 0000000..4a4a2b6 --- /dev/null +++ b/src/main/java/com/iciql/util/JdbcUtils.java @@ -0,0 +1,254 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 James Moger. + * + * 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 com.iciql.util; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Properties; + +import javax.naming.Context; +import javax.sql.DataSource; +import javax.sql.XAConnection; + +/** + * This is a utility class with JDBC helper functions. + */ +public class JdbcUtils { + + private static final String[] DRIVERS = { "h2:", "org.h2.Driver", "Cache:", + "com.intersys.jdbc.CacheDriver", "daffodilDB://", "in.co.daffodil.db.rmi.RmiDaffodilDBDriver", + "daffodil", "in.co.daffodil.db.jdbc.DaffodilDBDriver", "db2:", "COM.ibm.db2.jdbc.net.DB2Driver", + "derby:net:", "org.apache.derby.jdbc.ClientDriver", "derby://", + "org.apache.derby.jdbc.ClientDriver", "derby:", "org.apache.derby.jdbc.EmbeddedDriver", + "FrontBase:", "com.frontbase.jdbc.FBJDriver", "firebirdsql:", "org.firebirdsql.jdbc.FBDriver", + "hsqldb:", "org.hsqldb.jdbcDriver", "informix-sqli:", "com.informix.jdbc.IfxDriver", "jtds:", + "net.sourceforge.jtds.jdbc.Driver", "microsoft:", "com.microsoft.jdbc.sqlserver.SQLServerDriver", + "mimer:", "com.mimer.jdbc.Driver", "mysql:", "com.mysql.jdbc.Driver", "odbc:", + "sun.jdbc.odbc.JdbcOdbcDriver", "oracle:", "oracle.jdbc.driver.OracleDriver", "pervasive:", + "com.pervasive.jdbc.v2.Driver", "pointbase:micro:", "com.pointbase.me.jdbc.jdbcDriver", + "pointbase:", "com.pointbase.jdbc.jdbcUniversalDriver", "postgresql:", "org.postgresql.Driver", + "sybase:", "com.sybase.jdbc3.jdbc.SybDriver", "sqlserver:", + "com.microsoft.sqlserver.jdbc.SQLServerDriver", "teradata:", "com.ncr.teradata.TeraDriver", }; + + private JdbcUtils() { + // utility class + } + + /** + * Close a statement without throwing an exception. + * + * @param stat + * the statement or null + */ + public static void closeSilently(Statement stat) { + if (stat != null) { + try { + stat.close(); + } catch (SQLException e) { + // ignore + } + } + } + + /** + * Close a connection without throwing an exception. + * + * @param conn + * the connection or null + */ + public static void closeSilently(Connection conn) { + if (conn != null) { + try { + conn.close(); + } catch (SQLException e) { + // ignore + } + } + } + + /** + * Close a result set without throwing an exception. + * + * @param rs + * the result set or null + */ + public static void closeSilently(ResultSet rs) { + closeSilently(rs, false); + } + + /** + * Close a result set, and optionally its statement without throwing an + * exception. + * + * @param rs + * the result set or null + */ + public static void closeSilently(ResultSet rs, boolean closeStatement) { + if (rs != null) { + Statement stat = null; + if (closeStatement) { + try { + stat = rs.getStatement(); + } catch (SQLException e) { + // ignore + } + } + try { + rs.close(); + } catch (SQLException e) { + // ignore + } + closeSilently(stat); + } + } + + /** + * Close an XA connection set without throwing an exception. + * + * @param conn + * the XA connection or null + */ + public static void closeSilently(XAConnection conn) { + if (conn != null) { + try { + conn.close(); + } catch (SQLException e) { + // ignore + } + } + } + + /** + * Open a new database connection with the given settings. + * + * @param driver + * the driver class name + * @param url + * the database URL + * @param user + * the user name + * @param password + * the password + * @return the database connection + */ + public static Connection getConnection(String driver, String url, String user, String password) + throws SQLException { + Properties prop = new Properties(); + if (user != null) { + prop.setProperty("user", user); + } + if (password != null) { + prop.setProperty("password", password); + } + return getConnection(driver, url, prop); + } + + /** + * Escape table or schema patterns used for DatabaseMetaData functions. + * + * @param pattern + * the pattern + * @return the escaped pattern + */ + public static String escapeMetaDataPattern(String pattern) { + if (pattern == null || pattern.length() == 0) { + return pattern; + } + return StringUtils.replaceAll(pattern, "\\", "\\\\"); + } + + /** + * Open a new database connection with the given settings. + * + * @param driver + * the driver class name + * @param url + * the database URL + * @param prop + * the properties containing at least the user name and password + * @return the database connection + */ + public static Connection getConnection(String driver, String url, Properties prop) throws SQLException { + if (StringUtils.isNullOrEmpty(driver)) { + JdbcUtils.load(url); + } else { + Class<?> d = Utils.loadClass(driver); + if (java.sql.Driver.class.isAssignableFrom(d)) { + return DriverManager.getConnection(url, prop); + } else if (javax.naming.Context.class.isAssignableFrom(d)) { + // JNDI context + try { + Context context = (Context) d.newInstance(); + DataSource ds = (DataSource) context.lookup(url); + String user = prop.getProperty("user"); + String password = prop.getProperty("password"); + if (StringUtils.isNullOrEmpty(user) && StringUtils.isNullOrEmpty(password)) { + return ds.getConnection(); + } + return ds.getConnection(user, password); + } catch (SQLException e) { + throw e; + } catch (Exception e) { + throw new SQLException("Failed to get connection for " + url, e); + } + } else { + // Don't know, but maybe it loaded a JDBC Driver + return DriverManager.getConnection(url, prop); + } + } + return DriverManager.getConnection(url, prop); + } + + /** + * Get the driver class name for the given URL, or null if the URL is + * unknown. + * + * @param url + * the database URL + * @return the driver class name + */ + public static String getDriver(String url) { + if (url.startsWith("jdbc:")) { + url = url.substring("jdbc:".length()); + for (int i = 0; i < DRIVERS.length; i += 2) { + String prefix = DRIVERS[i]; + if (url.startsWith(prefix)) { + return DRIVERS[i + 1]; + } + } + } + return null; + } + + /** + * Load the driver class for the given URL, if the database URL is known. + * + * @param url + * the database URL + */ + public static void load(String url) { + String driver = getDriver(url); + if (driver != null) { + Utils.loadClass(driver); + } + } + +} diff --git a/src/main/java/com/iciql/util/Slf4jIciqlListener.java b/src/main/java/com/iciql/util/Slf4jIciqlListener.java new file mode 100644 index 0000000..ded393f --- /dev/null +++ b/src/main/java/com/iciql/util/Slf4jIciqlListener.java @@ -0,0 +1,92 @@ +/*
+ * Copyright 2011 James Moger.
+ *
+ * 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 com.iciql.util;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.iciql.Iciql;
+import com.iciql.util.IciqlLogger.IciqlListener;
+import com.iciql.util.IciqlLogger.StatementType;
+
+/**
+ * Slf4jIciqlListener interfaces the IciqlLogger to the SLF4J logging framework.
+ */
+public class Slf4jIciqlListener implements IciqlListener {
+
+ private Logger logger = LoggerFactory.getLogger(Iciql.class);
+
+ /**
+ * Enumeration representing the SLF4J log levels.
+ */
+ public enum Level {
+ ERROR, WARN, INFO, DEBUG, TRACE, OFF;
+ }
+
+ private final Level defaultLevel;
+
+ private final Map<StatementType, Level> levels;
+
+ public Slf4jIciqlListener() {
+ this(Level.TRACE);
+ }
+
+ public Slf4jIciqlListener(Level defaultLevel) {
+ this.defaultLevel = defaultLevel;
+ levels = new HashMap<StatementType, Level>();
+ for (StatementType type : StatementType.values()) {
+ levels.put(type, defaultLevel);
+ }
+ }
+
+ /**
+ * Sets the logging level for a particular statement type.
+ *
+ * @param type
+ * @param level
+ */
+ public void setLevel(StatementType type, Level level) {
+ levels.put(type, defaultLevel);
+ }
+
+ @Override
+ public void logIciql(StatementType type, String statement) {
+ Level level = levels.get(type);
+ switch (level) {
+ case ERROR:
+ logger.error(statement);
+ break;
+ case WARN:
+ logger.warn(statement);
+ break;
+ case INFO:
+ logger.info(statement);
+ break;
+ case DEBUG:
+ logger.debug(statement);
+ break;
+ case TRACE:
+ logger.trace(statement);
+ break;
+ case OFF:
+ break;
+ }
+ }
+}
diff --git a/src/main/java/com/iciql/util/StatementBuilder.java b/src/main/java/com/iciql/util/StatementBuilder.java new file mode 100644 index 0000000..47e8054 --- /dev/null +++ b/src/main/java/com/iciql/util/StatementBuilder.java @@ -0,0 +1,166 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 James Moger. + * + * 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 com.iciql.util; + +/** + * A utility class to build a statement. In addition to the methods supported by + * StringBuilder, it allows to add a text only in the second iteration. This + * simplified constructs such as: + * + * <pre> + * StringBuilder buff = new StringBuilder(); + * for (int i = 0; i < args.length; i++) { + * if (i > 0) { + * buff.append(", "); + * } + * buff.append(args[i]); + * } + * </pre> + * + * to + * + * <pre> + * StatementBuilder buff = new StatementBuilder(); + * for (String s : args) { + * buff.appendExceptFirst(", "); + * buff.append(a); + * } + * </pre> + */ +public class StatementBuilder { + + private final StringBuilder builder = new StringBuilder(); + private int index; + + /** + * Create a new builder. + */ + public StatementBuilder() { + // nothing to do + } + + /** + * Create a new builder. + * + * @param string + * the initial string + */ + public StatementBuilder(String string) { + builder.append(string); + } + + /** + * Append a text. + * + * @param s + * the text to append + * @return itself + */ + public StatementBuilder append(String s) { + builder.append(s); + return this; + } + + /** + * Append a character. + * + * @param c + * the character to append + * @return itself + */ + public StatementBuilder append(char c) { + builder.append(c); + return this; + } + + /** + * Append a number. + * + * @param x + * the number to append + * @return itself + */ + public StatementBuilder append(long x) { + builder.append(x); + return this; + } + + /** + * Returns the current value of the loop counter. + * + * @return the loop counter + */ + public int getCount() { + return index; + } + + /** + * Reset the loop counter. + * + * @return itself + */ + public StatementBuilder resetCount() { + index = 0; + return this; + } + + /** + * Append a text, but only if appendExceptFirst was never called. + * + * @param s + * the text to append + */ + public void appendOnlyFirst(String s) { + if (index == 0) { + builder.append(s); + } + } + + /** + * Append a text, except when this method is called the first time. + * + * @param s + * the text to append + */ + public void appendExceptFirst(String s) { + if (index++ > 0) { + builder.append(s); + } + } + + public void append(StatementBuilder sb) { + builder.append(sb); + } + + public void insert(int offset, char c) { + builder.insert(offset, c); + } + + public String toString() { + return builder.toString(); + } + + /** + * Get the length. + * + * @return the length + */ + public int length() { + return builder.length(); + } +} diff --git a/src/main/java/com/iciql/util/StringUtils.java b/src/main/java/com/iciql/util/StringUtils.java new file mode 100644 index 0000000..dd3f180 --- /dev/null +++ b/src/main/java/com/iciql/util/StringUtils.java @@ -0,0 +1,382 @@ +/* + * Copyright 2004-2011 H2 Group. + * Copyright 2011 James Moger. + * + * 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 com.iciql.util; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; + +/** + * Common string utilities. + * + */ +public class StringUtils { + + /** + * Replace all occurrences of the before string with the after string. + * + * @param s + * the string + * @param before + * the old text + * @param after + * the new text + * @return the string with the before string replaced + */ + public static String replaceAll(String s, String before, String after) { + int next = s.indexOf(before); + if (next < 0) { + return s; + } + StringBuilder buff = new StringBuilder(s.length() - before.length() + after.length()); + int index = 0; + while (true) { + buff.append(s.substring(index, next)).append(after); + index = next + before.length(); + next = s.indexOf(before, index); + if (next < 0) { + buff.append(s.substring(index)); + break; + } + } + return buff.toString(); + } + + /** + * Check if a String is null or empty (the length is null). + * + * @param s + * the string to check + * @return true if it is null or empty + */ + public static boolean isNullOrEmpty(String s) { + return s == null || s.length() == 0; + } + + /** + * Convert a string to a Java literal using the correct escape sequences. + * The literal is not enclosed in double quotes. The result can be used in + * properties files or in Java source code. + * + * @param s + * the text to convert + * @return the Java representation + */ + public static String javaEncode(String s) { + int length = s.length(); + StringBuilder buff = new StringBuilder(length); + for (int i = 0; i < length; i++) { + char c = s.charAt(i); + switch (c) { + // case '\b': + // // BS backspace + // // not supported in properties files + // buff.append("\\b"); + // break; + case '\t': + // HT horizontal tab + buff.append("\\t"); + break; + case '\n': + // LF linefeed + buff.append("\\n"); + break; + case '\f': + // FF form feed + buff.append("\\f"); + break; + case '\r': + // CR carriage return + buff.append("\\r"); + break; + case '"': + // double quote + buff.append("\\\""); + break; + case '\\': + // backslash + buff.append("\\\\"); + break; + default: + int ch = c & 0xffff; + if (ch >= ' ' && (ch < 0x80)) { + buff.append(c); + // not supported in properties files + // } else if(ch < 0xff) { + // buff.append("\\"); + // // make sure it's three characters (0x200 is octal 1000) + // buff.append(Integer.toOctalString(0x200 | + // ch).substring(1)); + } else { + buff.append("\\u"); + // make sure it's four characters + buff.append(Integer.toHexString(0x10000 | ch).substring(1)); + } + } + } + return buff.toString(); + } + + /** + * Pad a string. This method is used for the SQL function RPAD and LPAD. + * + * @param string + * the original string + * @param n + * the target length + * @param padding + * the padding string + * @param right + * true if the padding should be appended at the end + * @return the padded string + */ + public static String pad(String string, int n, String padding, boolean right) { + if (n < 0) { + n = 0; + } + if (n < string.length()) { + return string.substring(0, n); + } else if (n == string.length()) { + return string; + } + char paddingChar; + if (padding == null || padding.length() == 0) { + paddingChar = ' '; + } else { + paddingChar = padding.charAt(0); + } + StringBuilder buff = new StringBuilder(n); + n -= string.length(); + if (right) { + buff.append(string); + } + for (int i = 0; i < n; i++) { + buff.append(paddingChar); + } + if (!right) { + buff.append(string); + } + return buff.toString(); + } + + /** + * Convert a string to a SQL literal. Null is converted to NULL. The text is + * enclosed in single quotes. If there are any special characters, the + * method STRINGDECODE is used. + * + * @param s + * the text to convert. + * @return the SQL literal + */ + public static String quoteStringSQL(String s) { + if (s == null) { + return "NULL"; + } + int length = s.length(); + StringBuilder buff = new StringBuilder(length + 2); + buff.append('\''); + for (int i = 0; i < length; i++) { + char c = s.charAt(i); + if (c == '\'') { + buff.append(c); + } else if (c < ' ' || c > 127) { + // need to start from the beginning because maybe there was a \ + // that was not quoted + return "STRINGDECODE(" + quoteStringSQL(javaEncode(s)) + ")"; + } + buff.append(c); + } + buff.append('\''); + return buff.toString(); + } + + /** + * Split a string into an array of strings using the given separator. A null + * string will result in a null array, and an empty string in a zero element + * array. + * + * @param s + * the string to split + * @param separatorChar + * the separator character + * @param trim + * whether each element should be trimmed + * @return the array list + */ + public static String[] arraySplit(String s, char separatorChar, boolean trim) { + if (s == null) { + return null; + } + int length = s.length(); + if (length == 0) { + return new String[0]; + } + ArrayList<String> list = Utils.newArrayList(); + StringBuilder buff = new StringBuilder(length); + for (int i = 0; i < length; i++) { + char c = s.charAt(i); + if (c == separatorChar) { + String e = buff.toString(); + list.add(trim ? e.trim() : e); + buff.setLength(0); + } else if (c == '\\' && i < length - 1) { + buff.append(s.charAt(++i)); + } else { + buff.append(c); + } + } + String e = buff.toString(); + list.add(trim ? e.trim() : e); + String[] array = new String[list.size()]; + list.toArray(array); + return array; + } + + /** + * Calculates the SHA1 of the string. + * + * @param text + * @return sha1 of the string + */ + public static String calculateSHA1(String text) { + try { + byte[] bytes = text.getBytes("iso-8859-1"); + return calculateSHA1(bytes); + } catch (UnsupportedEncodingException u) { + throw new RuntimeException(u); + } + } + + /** + * Calculates the SHA1 of the byte array. + * + * @param bytes + * @return sha1 of the byte array + */ + public static String calculateSHA1(byte[] bytes) { + try { + MessageDigest md = MessageDigest.getInstance("SHA-1"); + md.update(bytes, 0, bytes.length); + byte[] digest = md.digest(); + StringBuilder sb = new StringBuilder(digest.length * 2); + for (int i = 0; i < digest.length; i++) { + if (((int) digest[i] & 0xff) < 0x10) { + sb.append('0'); + } + sb.append(Integer.toHexString((int) digest[i] & 0xff)); + } + return sb.toString(); + } catch (NoSuchAlgorithmException t) { + throw new RuntimeException(t); + } + } + + /** + * Counts the occurrences of char c in the given string. + * + * @param c + * the character to count + * @param value + * the source string + * @return the count of c in value + */ + public static int count(char c, String value) { + int count = 0; + for (char cv : value.toCharArray()) { + if (cv == c) { + count++; + } + } + return count; + } + + /** + * Prepare text for html presentation. Replace sensitive characters with + * html entities. + * + * @param inStr + * @param changeSpace + * @return plain text escaped for html + */ + public static String escapeForHtml(String inStr, boolean changeSpace) { + StringBuffer retStr = new StringBuffer(); + int i = 0; + while (i < inStr.length()) { + if (inStr.charAt(i) == '&') { + retStr.append("&"); + } else if (inStr.charAt(i) == '<') { + retStr.append("<"); + } else if (inStr.charAt(i) == '>') { + retStr.append(">"); + } else if (inStr.charAt(i) == '\"') { + retStr.append("""); + } else if (changeSpace && inStr.charAt(i) == ' ') { + retStr.append(" "); + } else if (changeSpace && inStr.charAt(i) == '\t') { + retStr.append(" "); + } else { + retStr.append(inStr.charAt(i)); + } + i++; + } + return retStr.toString(); + } + + /** + * Replaces carriage returns and line feeds with html line breaks. + * + * @param string + * @return plain text with html line breaks + */ + public static String breakLinesForHtml(String string) { + return string.replace("\r\n", "<br/>").replace("\r", "<br/>").replace("\n", "<br/>"); + } + + /** + * Returns the string content of the specified file. + * + * @param file + * @param lineEnding + * @return the string content of the file + */ + public static String readContent(File file, String lineEnding) { + StringBuilder sb = new StringBuilder(); + try { + InputStreamReader is = new InputStreamReader(new FileInputStream(file), Charset.forName("UTF-8")); + BufferedReader reader = new BufferedReader(is); + String line = null; + while ((line = reader.readLine()) != null) { + sb.append(line); + if (lineEnding != null) { + sb.append(lineEnding); + } + } + reader.close(); + } catch (Throwable t) { + System.err.println("Failed to read content of " + file.getAbsolutePath()); + t.printStackTrace(); + } + return sb.toString(); + } +} diff --git a/src/main/java/com/iciql/util/Utils.java b/src/main/java/com/iciql/util/Utils.java new file mode 100644 index 0000000..77110b8 --- /dev/null +++ b/src/main/java/com/iciql/util/Utils.java @@ -0,0 +1,459 @@ +/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ *
+ * 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 com.iciql.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.io.StringWriter;
+import java.lang.reflect.Constructor;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.sql.Blob;
+import java.sql.Clob;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+import com.iciql.Iciql.EnumId;
+import com.iciql.Iciql.EnumType;
+import com.iciql.IciqlException;
+
+/**
+ * Generic utility methods.
+ */
+public class Utils {
+
+ public static final AtomicLong COUNTER = new AtomicLong(0);
+
+ public static final AtomicInteger AS_COUNTER = new AtomicInteger(0);
+
+ private static final boolean MAKE_ACCESSIBLE = true;
+
+ private static final int BUFFER_BLOCK_SIZE = 4 * 1024;
+
+ public static synchronized int nextAsCount() {
+ // prevent negative values and use a threadsafe counter
+ int count = AS_COUNTER.incrementAndGet();
+ if (count == Integer.MAX_VALUE) {
+ count = 0;
+ AS_COUNTER.set(count);
+ }
+ return count;
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <X> Class<X> getClass(X x) {
+ return (Class<X>) x.getClass();
+ }
+
+ public static Class<?> loadClass(String className) {
+ try {
+ return Class.forName(className);
+ } catch (Exception e) {
+ throw new IciqlException(e);
+ }
+ }
+
+ public static <T> ArrayList<T> newArrayList() {
+ return new ArrayList<T>();
+ }
+
+ public static <T> ArrayList<T> newArrayList(Collection<T> c) {
+ return new ArrayList<T>(c);
+ }
+
+ public static <T> HashSet<T> newHashSet() {
+ return new HashSet<T>();
+ }
+
+ public static <T> HashSet<T> newHashSet(Collection<T> list) {
+ return new HashSet<T>(list);
+ }
+
+ public static <A, B> HashMap<A, B> newHashMap() {
+ return new HashMap<A, B>();
+ }
+
+ public static <A, B> Map<A, B> newSynchronizedHashMap() {
+ HashMap<A, B> map = newHashMap();
+ return Collections.synchronizedMap(map);
+ }
+
+ public static <A, B> IdentityHashMap<A, B> newIdentityHashMap() {
+ return new IdentityHashMap<A, B>();
+ }
+
+ public static <T> ThreadLocal<T> newThreadLocal(final Class<? extends T> clazz) {
+ return new ThreadLocal<T>() {
+ @SuppressWarnings("rawtypes")
+ @Override
+ protected T initialValue() {
+ try {
+ return clazz.newInstance();
+ } catch (Exception e) {
+ if (MAKE_ACCESSIBLE) {
+ Constructor[] constructors = clazz.getDeclaredConstructors();
+ // try 0 length constructors
+ for (Constructor c : constructors) {
+ if (c.getParameterTypes().length == 0) {
+ c.setAccessible(true);
+ try {
+ return clazz.newInstance();
+ } catch (Exception e2) {
+ // ignore
+ }
+ }
+ }
+ }
+ throw new IciqlException(e,
+ "Missing default constructor? Exception trying to instantiate {0}: {1}",
+ clazz.getName(), e.getMessage());
+ }
+ }
+ };
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public static <T> T newObject(Class<T> clazz) {
+ // must create new instances
+ if (clazz == int.class || clazz == Integer.class) {
+ return (T) new Integer((int) COUNTER.getAndIncrement());
+ } else if (clazz == String.class) {
+ return (T) ("" + COUNTER.getAndIncrement());
+ } else if (clazz == long.class || clazz == Long.class) {
+ return (T) new Long(COUNTER.getAndIncrement());
+ } else if (clazz == short.class || clazz == Short.class) {
+ return (T) new Short((short) COUNTER.getAndIncrement());
+ } else if (clazz == byte.class || clazz == Byte.class) {
+ return (T) new Byte((byte) COUNTER.getAndIncrement());
+ } else if (clazz == float.class || clazz == Float.class) {
+ return (T) new Float(COUNTER.getAndIncrement());
+ } else if (clazz == double.class || clazz == Double.class) {
+ return (T) new Double(COUNTER.getAndIncrement());
+ } else if (clazz == boolean.class || clazz == Boolean.class) {
+ COUNTER.getAndIncrement();
+ return (T) new Boolean(false);
+ } else if (clazz == BigDecimal.class) {
+ return (T) new BigDecimal(COUNTER.getAndIncrement());
+ } else if (clazz == BigInteger.class) {
+ return (T) new BigInteger("" + COUNTER.getAndIncrement());
+ } else if (clazz == java.sql.Date.class) {
+ return (T) new java.sql.Date(COUNTER.getAndIncrement());
+ } else if (clazz == java.sql.Time.class) {
+ return (T) new java.sql.Time(COUNTER.getAndIncrement());
+ } else if (clazz == java.sql.Timestamp.class) {
+ return (T) new java.sql.Timestamp(COUNTER.getAndIncrement());
+ } else if (clazz == java.util.Date.class) {
+ return (T) new java.util.Date(COUNTER.getAndIncrement());
+ } else if (clazz == byte[].class) {
+ COUNTER.getAndIncrement();
+ return (T) new byte[0];
+ } else if (clazz.isEnum()) {
+ COUNTER.getAndIncrement();
+ // enums can not be instantiated reflectively
+ // return first constant as reference
+ return clazz.getEnumConstants()[0];
+ } else if (clazz == java.util.UUID.class) {
+ COUNTER.getAndIncrement();
+ return (T) UUID.randomUUID();
+ }
+ try {
+ return clazz.newInstance();
+ } catch (Exception e) {
+ if (MAKE_ACCESSIBLE) {
+ Constructor[] constructors = clazz.getDeclaredConstructors();
+ // try 0 length constructors
+ for (Constructor c : constructors) {
+ if (c.getParameterTypes().length == 0) {
+ c.setAccessible(true);
+ try {
+ return clazz.newInstance();
+ } catch (Exception e2) {
+ // ignore
+ }
+ }
+ }
+ // try 1 length constructors
+ for (Constructor c : constructors) {
+ if (c.getParameterTypes().length == 1) {
+ c.setAccessible(true);
+ try {
+ return (T) c.newInstance(new Object[1]);
+ } catch (Exception e2) {
+ // ignore
+ }
+ }
+ }
+ }
+ throw new IciqlException(e,
+ "Missing default constructor?! Exception trying to instantiate {0}: {1}",
+ clazz.getName(), e.getMessage());
+ }
+ }
+
+ public static <T> boolean isSimpleType(Class<T> clazz) {
+ if (Number.class.isAssignableFrom(clazz)) {
+ return true;
+ } else if (clazz == String.class) {
+ return true;
+ }
+ return false;
+ }
+
+ public static Object convert(Object o, Class<?> targetType) {
+ if (o == null) {
+ return null;
+ }
+ Class<?> currentType = o.getClass();
+ if (targetType.isAssignableFrom(currentType)) {
+ return o;
+ }
+
+ // convert from CLOB/TEXT/VARCHAR to String
+ if (targetType == String.class) {
+ if (Clob.class.isAssignableFrom(currentType)) {
+ Clob c = (Clob) o;
+ try {
+ Reader r = c.getCharacterStream();
+ return readStringAndClose(r, -1);
+ } catch (Exception e) {
+ throw new IciqlException(e, "error converting CLOB to String: ", e.toString());
+ }
+ }
+ return o.toString();
+ }
+
+ if (Boolean.class.isAssignableFrom(targetType) || boolean.class.isAssignableFrom(targetType)) {
+ // convert from number to boolean
+ if (Number.class.isAssignableFrom(currentType)) {
+ Number n = (Number) o;
+ return n.intValue() > 0;
+ }
+ // convert from string to boolean
+ if (String.class.isAssignableFrom(currentType)) {
+ String s = o.toString().toLowerCase();
+ float f = 0f;
+ try {
+ f = Float.parseFloat(s);
+ } catch (Exception e) {
+ }
+ return f > 0 || s.equals("true") || s.equals("yes") || s.equals("y") || s.equals("on");
+ }
+ }
+
+ // convert from boolean to number
+ if (Boolean.class.isAssignableFrom(currentType)) {
+ Boolean b = (Boolean) o;
+ if (Number.class.isAssignableFrom(targetType)) {
+ return b ? 1 : 0;
+ }
+ if (boolean.class.isAssignableFrom(targetType)) {
+ return b.booleanValue();
+ }
+ }
+
+ // convert from number to number
+ if (Number.class.isAssignableFrom(currentType)) {
+ Number n = (Number) o;
+ if (targetType == byte.class || targetType == Byte.class) {
+ return n.byteValue();
+ } else if (targetType == short.class || targetType == Short.class) {
+ return n.shortValue();
+ } else if (targetType == int.class || targetType == Integer.class) {
+ return n.intValue();
+ } else if (targetType == long.class || targetType == Long.class) {
+ return n.longValue();
+ } else if (targetType == double.class || targetType == Double.class) {
+ return n.doubleValue();
+ } else if (targetType == float.class || targetType == Float.class) {
+ return n.floatValue();
+ }
+ }
+
+ // convert from BLOB
+ if (targetType == byte[].class) {
+ if (Blob.class.isAssignableFrom(currentType)) {
+ Blob b = (Blob) o;
+ try {
+ InputStream is = b.getBinaryStream();
+ return readBlobAndClose(is, -1);
+ } catch (Exception e) {
+ throw new IciqlException(e, "error converting BLOB to byte[]: ", e.toString());
+ }
+ }
+ }
+ throw new IciqlException("Can not convert the value {0} from {1} to {2}", o, currentType, targetType);
+ }
+
+ public static Object convertEnum(Enum<?> o, EnumType type) {
+ if (o == null) {
+ return null;
+ }
+ switch (type) {
+ case ORDINAL:
+ return o.ordinal();
+ case ENUMID:
+ if (!EnumId.class.isAssignableFrom(o.getClass())) {
+ throw new IciqlException("Can not convert the enum {0} using ENUMID", o);
+ }
+ EnumId enumid = (EnumId) o;
+ return enumid.enumId();
+ case NAME:
+ default:
+ return o.name();
+ }
+ }
+
+ public static Object convertEnum(Object o, Class<?> targetType, EnumType type) {
+ if (o == null) {
+ return null;
+ }
+ Class<?> currentType = o.getClass();
+ if (targetType.isAssignableFrom(currentType)) {
+ return o;
+ }
+ // convert from VARCHAR/TEXT/INT to Enum
+ Enum<?>[] values = (Enum[]) targetType.getEnumConstants();
+ if (Clob.class.isAssignableFrom(currentType)) {
+ // TEXT/CLOB field
+ Clob c = (Clob) o;
+ String name = null;
+ try {
+ Reader r = c.getCharacterStream();
+ name = readStringAndClose(r, -1);
+ } catch (Exception e) {
+ throw new IciqlException(e, "error converting CLOB to String: ", e.toString());
+ }
+
+ // find name match
+ for (Enum<?> value : values) {
+ if (value.name().equalsIgnoreCase(name)) {
+ return value;
+ }
+ }
+ } else if (String.class.isAssignableFrom(currentType)) {
+ // VARCHAR field
+ String name = (String) o;
+ for (Enum<?> value : values) {
+ if (value.name().equalsIgnoreCase(name)) {
+ return value;
+ }
+ }
+ } else if (Number.class.isAssignableFrom(currentType)) {
+ // INT field
+ int n = ((Number) o).intValue();
+ if (type.equals(EnumType.ORDINAL)) {
+ // ORDINAL mapping
+ for (Enum<?> value : values) {
+ if (value.ordinal() == n) {
+ return value;
+ }
+ }
+ } else if (type.equals(EnumType.ENUMID)) {
+ if (!EnumId.class.isAssignableFrom(targetType)) {
+ throw new IciqlException("Can not convert the value {0} from {1} to {2} using ENUMID", o,
+ currentType, targetType);
+ }
+ // ENUMID mapping
+ for (Enum<?> value : values) {
+ EnumId enumid = (EnumId) value;
+ if (enumid.enumId() == n) {
+ return value;
+ }
+ }
+ }
+ }
+ throw new IciqlException("Can not convert the value {0} from {1} to {2}", o, currentType, targetType);
+ }
+
+ /**
+ * Read a number of characters from a reader and close it.
+ *
+ * @param in
+ * the reader
+ * @param length
+ * the maximum number of characters to read, or -1 to read until
+ * the end of file
+ * @return the string read
+ */
+ public static String readStringAndClose(Reader in, int length) throws IOException {
+ try {
+ if (length <= 0) {
+ length = Integer.MAX_VALUE;
+ }
+ int block = Math.min(BUFFER_BLOCK_SIZE, length);
+ StringWriter out = new StringWriter(length == Integer.MAX_VALUE ? block : length);
+ char[] buff = new char[block];
+ while (length > 0) {
+ int len = Math.min(block, length);
+ len = in.read(buff, 0, len);
+ if (len < 0) {
+ break;
+ }
+ out.write(buff, 0, len);
+ length -= len;
+ }
+ return out.toString();
+ } finally {
+ in.close();
+ }
+ }
+
+ /**
+ * Read a number of bytes from a stream and close it.
+ *
+ * @param in
+ * the stream
+ * @param length
+ * the maximum number of bytes to read, or -1 to read until the
+ * end of file
+ * @return the string read
+ */
+ public static byte[] readBlobAndClose(InputStream in, int length) throws IOException {
+ try {
+ if (length <= 0) {
+ length = Integer.MAX_VALUE;
+ }
+ int block = Math.min(BUFFER_BLOCK_SIZE, length);
+ ByteArrayOutputStream out = new ByteArrayOutputStream(length == Integer.MAX_VALUE ? block
+ : length);
+ byte[] buff = new byte[block];
+ while (length > 0) {
+ int len = Math.min(block, length);
+ len = in.read(buff, 0, len);
+ if (len < 0) {
+ break;
+ }
+ out.write(buff, 0, len);
+ length -= len;
+ }
+ return out.toByteArray();
+ } finally {
+ in.close();
+ }
+ }
+}
diff --git a/src/main/java/com/iciql/util/WeakIdentityHashMap.java b/src/main/java/com/iciql/util/WeakIdentityHashMap.java new file mode 100644 index 0000000..bc03cd0 --- /dev/null +++ b/src/main/java/com/iciql/util/WeakIdentityHashMap.java @@ -0,0 +1,243 @@ +/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ *
+ * 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 com.iciql.util;
+
+import java.lang.ref.WeakReference;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+import com.iciql.IciqlException;
+
+/**
+ * This hash map uses weak references, so that elements that are no longer
+ * referenced elsewhere can be garbage collected. It also uses object identity
+ * to compare keys. The garbage collection happens when trying to add new data,
+ * or when resizing.
+ *
+ * @param <K>
+ * the keys
+ * @param <V>
+ * the value
+ */
+
+public class WeakIdentityHashMap<K, V> implements Map<K, V> {
+
+ private static final int MAX_LOAD = 90;
+ private static final WeakReference<Object> DELETED_KEY = new WeakReference<Object>(null);
+ private int mask, len, size, deletedCount, level;
+ private int maxSize, minSize, maxDeleted;
+ private WeakReference<K>[] keys;
+ private V[] values;
+
+ public WeakIdentityHashMap() {
+ reset(2);
+ }
+
+ public int size() {
+ return size;
+ }
+
+ private void checkSizePut() {
+ if (deletedCount > size) {
+ rehash(level);
+ }
+ if (size + deletedCount >= maxSize) {
+ rehash(level + 1);
+ }
+ }
+
+ private void checkSizeRemove() {
+ if (size < minSize && level > 0) {
+ rehash(level - 1);
+ } else if (deletedCount > maxDeleted) {
+ rehash(level);
+ }
+ }
+
+ private int getIndex(Object key) {
+ return System.identityHashCode(key) & mask;
+ }
+
+ @SuppressWarnings("unchecked")
+ private void reset(int newLevel) {
+ minSize = size * 3 / 4;
+ size = 0;
+ level = newLevel;
+ len = 2 << level;
+ mask = len - 1;
+ maxSize = (int) (len * MAX_LOAD / 100L);
+ deletedCount = 0;
+ maxDeleted = 20 + len / 2;
+ keys = new WeakReference[len];
+ values = (V[]) new Object[len];
+ }
+
+ public V put(K key, V value) {
+ checkSizePut();
+ int index = getIndex(key);
+ int plus = 1;
+ int deleted = -1;
+ do {
+ WeakReference<K> k = keys[index];
+ if (k == null) {
+ // found an empty record
+ if (deleted >= 0) {
+ index = deleted;
+ deletedCount--;
+ }
+ size++;
+ keys[index] = new WeakReference<K>(key);
+ values[index] = value;
+ return null;
+ } else if (k == DELETED_KEY) {
+ if (deleted < 0) {
+ // found the first deleted record
+ deleted = index;
+ }
+ } else {
+ Object r = k.get();
+ if (r == null) {
+ delete(index);
+ } else if (r == key) {
+ // update existing
+ V old = values[index];
+ values[index] = value;
+ return old;
+ }
+ }
+ index = (index + plus++) & mask;
+ } while (plus <= len);
+ throw new IciqlException("Hashmap is full");
+ }
+
+ public V remove(Object key) {
+ checkSizeRemove();
+ int index = getIndex(key);
+ int plus = 1;
+ do {
+ WeakReference<K> k = keys[index];
+ if (k == null) {
+ // found an empty record
+ return null;
+ } else if (k == DELETED_KEY) {
+ // continue
+ } else {
+ Object r = k.get();
+ if (r == null) {
+ delete(index);
+ } else if (r == key) {
+ // found the record
+ V old = values[index];
+ delete(index);
+ return old;
+ }
+ }
+ index = (index + plus++) & mask;
+ k = keys[index];
+ } while (plus <= len);
+ // not found
+ return null;
+ }
+
+ @SuppressWarnings("unchecked")
+ private void delete(int index) {
+ keys[index] = (WeakReference<K>) DELETED_KEY;
+ values[index] = null;
+ deletedCount++;
+ size--;
+ }
+
+ private void rehash(int newLevel) {
+ WeakReference<K>[] oldKeys = keys;
+ V[] oldValues = values;
+ reset(newLevel);
+ for (int i = 0; i < oldKeys.length; i++) {
+ WeakReference<K> k = oldKeys[i];
+ if (k != null && k != DELETED_KEY) {
+ K key = k.get();
+ if (key != null) {
+ put(key, oldValues[i]);
+ }
+ }
+ }
+ }
+
+ public V get(Object key) {
+ int index = getIndex(key);
+ int plus = 1;
+ do {
+ WeakReference<K> k = keys[index];
+ if (k == null) {
+ return null;
+ } else if (k == DELETED_KEY) {
+ // continue
+ } else {
+ Object r = k.get();
+ if (r == null) {
+ delete(index);
+ } else if (r == key) {
+ return values[index];
+ }
+ }
+ index = (index + plus++) & mask;
+ } while (plus <= len);
+ return null;
+ }
+
+ public void clear() {
+ reset(2);
+ }
+
+ public boolean containsKey(Object key) {
+ return get(key) != null;
+ }
+
+ public boolean containsValue(Object value) {
+ if (value == null) {
+ return false;
+ }
+ for (V item : values) {
+ if (value.equals(item)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public Set<java.util.Map.Entry<K, V>> entrySet() {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean isEmpty() {
+ return size == 0;
+ }
+
+ public Set<K> keySet() {
+ throw new UnsupportedOperationException();
+ }
+
+ public void putAll(Map<? extends K, ? extends V> m) {
+ throw new UnsupportedOperationException();
+ }
+
+ public Collection<V> values() {
+ throw new UnsupportedOperationException();
+ }
+
+}
diff --git a/src/main/java/com/iciql/util/package.html b/src/main/java/com/iciql/util/package.html new file mode 100644 index 0000000..3d24dee --- /dev/null +++ b/src/main/java/com/iciql/util/package.html @@ -0,0 +1,25 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<!--
+ Copyright 2004-2011 H2 Group.
+ Copyright 2011 James Moger.
+
+ 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.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
+<head><meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
+<title>Javadoc package documentation</title>
+</head>
+<body>
+Utility classes for iciql.
+</body>
+</html>
\ No newline at end of file |