diff --git a/src/main/java/com/avaje/ebean/text/PathProperties.java b/src/main/java/com/avaje/ebean/text/PathProperties.java index 7ce4bcb25..ad0d96f6a 100644 --- a/src/main/java/com/avaje/ebean/text/PathProperties.java +++ b/src/main/java/com/avaje/ebean/text/PathProperties.java @@ -1,251 +1,251 @@ -package com.avaje.ebean.text; - -import java.util.Collection; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; -import java.util.Map.Entry; - -import com.avaje.ebean.Query; - -/** - * This is a Tree like structure of paths and properties that can be used for - * defining which parts of an object graph to render in JSON or XML, and can - * also be used to define which parts to select and fetch for an ORM query. - *
- * It provides a way of parsing a string representation of nested path - * properties and applying that to both what to fetch (ORM query) and what to - * render (JAX-RS JSON / XML). - *
- */ -public class PathProperties { - - private final Map- * For example, you may want to create a copy to add extra properties to a - * path so that they are fetching in a ORM query but perhaps not rendered by - * default. That is, use a PathProperties for JSON or XML rendering, but - * create a copy, add some extra properties and then use that copy to define - * an ORM query. - *
- */ - public PathProperties copy() { - return new PathProperties(this); - } - - /** - * Return true if there are no paths defined. - */ - public boolean isEmpty() { - return pathMap.isEmpty(); - } - - public String toString() { - return pathMap.toString(); - } - - /** - * Return true if the path is defined and has properties. - */ - public boolean hasPath(String path) { - Props props = pathMap.get(path); - return props != null && !props.isEmpty(); - } - - /** - * Get the properties for a given path. - */ - public Set+ * It provides a way of parsing a string representation of nested path + * properties and applying that to both what to fetch (ORM query) and what to + * render (JAX-RS JSON / XML). + *
+ */ +public class PathProperties { + + private final Map+ * For example, you may want to create a copy to add extra properties to a + * path so that they are fetching in a ORM query but perhaps not rendered by + * default. That is, use a PathProperties for JSON or XML rendering, but + * create a copy, add some extra properties and then use that copy to define + * an ORM query. + *
+ */ + public PathProperties copy() { + return new PathProperties(this); + } + + /** + * Return true if there are no paths defined. + */ + public boolean isEmpty() { + return pathMap.isEmpty(); + } + + public String toString() { + return pathMap.toString(); + } + + /** + * Return true if the path is defined and has properties. + */ + public boolean hasPath(String path) { + Props props = pathMap.get(path); + return props != null && !props.isEmpty(); + } + + /** + * Get the properties for a given path. + */ + public Set- * Basic interface to support CSV, JSON and XML processing. - *
- * - * @author rbygrave - */ -public interface StringFormatter { - - /** - * Convert an Object value into a String value. - */ - String format(Object value); -} +package com.avaje.ebean.text; + +/** + * Convert an Object value into a String value. + *+ * Basic interface to support CSV, JSON and XML processing. + *
+ * + * @author rbygrave + */ +public interface StringFormatter { + + /** + * Convert an Object value into a String value. + */ + String format(Object value); +} diff --git a/src/main/java/com/avaje/ebean/text/StringParser.java b/src/main/java/com/avaje/ebean/text/StringParser.java index d790736b8..c03371310 100644 --- a/src/main/java/com/avaje/ebean/text/StringParser.java +++ b/src/main/java/com/avaje/ebean/text/StringParser.java @@ -1,17 +1,17 @@ -package com.avaje.ebean.text; - -/** - * Convert a String value into an Object value. - *- * Basic interface to support CSV, JSON and XML processing. - *
- * - * @author rbygrave - */ -public interface StringParser { - - /** - * Convert a String value into an Object value. - */ - Object parse(String value); -} +package com.avaje.ebean.text; + +/** + * Convert a String value into an Object value. + *+ * Basic interface to support CSV, JSON and XML processing. + *
+ * + * @author rbygrave + */ +public interface StringParser { + + /** + * Convert a String value into an Object value. + */ + Object parse(String value); +} diff --git a/src/main/java/com/avaje/ebean/text/TextException.java b/src/main/java/com/avaje/ebean/text/TextException.java index 68a65649e..ac41a3c07 100644 --- a/src/main/java/com/avaje/ebean/text/TextException.java +++ b/src/main/java/com/avaje/ebean/text/TextException.java @@ -1,32 +1,32 @@ -package com.avaje.ebean.text; - -/** - * An exception occurred typically in processing CSV, JSON or XML. - * - * @author rbygrave - */ -public class TextException extends RuntimeException { - - private static final long serialVersionUID = 1601310159486033148L; - - /** - * Construct with an error message. - */ - public TextException(String msg) { - super(msg); - } - - /** - * Construct with a message and cause. - */ - public TextException(String msg, Exception e) { - super(msg, e); - } - - /** - * Construct with a cause. - */ - public TextException(Exception e) { - super(e); - } -} +package com.avaje.ebean.text; + +/** + * An exception occurred typically in processing CSV, JSON or XML. + * + * @author rbygrave + */ +public class TextException extends RuntimeException { + + private static final long serialVersionUID = 1601310159486033148L; + + /** + * Construct with an error message. + */ + public TextException(String msg) { + super(msg); + } + + /** + * Construct with a message and cause. + */ + public TextException(String msg, Exception e) { + super(msg, e); + } + + /** + * Construct with a cause. + */ + public TextException(Exception e) { + super(e); + } +} diff --git a/src/main/java/com/avaje/ebean/text/TimeStringParser.java b/src/main/java/com/avaje/ebean/text/TimeStringParser.java index cb212ccaa..349b99575 100644 --- a/src/main/java/com/avaje/ebean/text/TimeStringParser.java +++ b/src/main/java/com/avaje/ebean/text/TimeStringParser.java @@ -1,56 +1,56 @@ -package com.avaje.ebean.text; - -import java.sql.Time; - -/** - * Parser for TIME types that supports both HH:mm:ss and HH:mm. - * - * @author rbygrave - */ -public final class TimeStringParser implements StringParser { - - private static final TimeStringParser SHARED = new TimeStringParser(); - - /** - * Return a shared instance as this is thread safe. - */ - public static TimeStringParser get() { - return SHARED; - } - - /** - * Parse the String supporting both HH:mm:ss and HH:mm formats. - */ - @SuppressWarnings("deprecation") - public Object parse(String value) { - if (value == null || value.trim().length() == 0) { - return null; - } - - String s = value.trim(); - int minute; - int second; - int firstColon = s.indexOf(':'); - int secondColon = s.indexOf(':', firstColon + 1); - - if (firstColon == -1) { - throw new java.lang.IllegalArgumentException("No ':' in value [" + s + "]"); - } - try { - int hour = Integer.parseInt(s.substring(0, firstColon)); - if (secondColon == -1) { - minute = Integer.parseInt(s.substring(firstColon + 1, s.length())); - second = 0; - } else { - minute = Integer.parseInt(s.substring(firstColon + 1, secondColon)); - second = Integer.parseInt(s.substring(secondColon + 1)); - } - - return new Time(hour, minute, second); - - } catch (NumberFormatException e) { - throw new java.lang.IllegalArgumentException("Number format Error parsing time [" + s + "] " - + e.getMessage(), e); - } - } +package com.avaje.ebean.text; + +import java.sql.Time; + +/** + * Parser for TIME types that supports both HH:mm:ss and HH:mm. + * + * @author rbygrave + */ +public final class TimeStringParser implements StringParser { + + private static final TimeStringParser SHARED = new TimeStringParser(); + + /** + * Return a shared instance as this is thread safe. + */ + public static TimeStringParser get() { + return SHARED; + } + + /** + * Parse the String supporting both HH:mm:ss and HH:mm formats. + */ + @SuppressWarnings("deprecation") + public Object parse(String value) { + if (value == null || value.trim().length() == 0) { + return null; + } + + String s = value.trim(); + int minute; + int second; + int firstColon = s.indexOf(':'); + int secondColon = s.indexOf(':', firstColon + 1); + + if (firstColon == -1) { + throw new java.lang.IllegalArgumentException("No ':' in value [" + s + "]"); + } + try { + int hour = Integer.parseInt(s.substring(0, firstColon)); + if (secondColon == -1) { + minute = Integer.parseInt(s.substring(firstColon + 1, s.length())); + second = 0; + } else { + minute = Integer.parseInt(s.substring(firstColon + 1, secondColon)); + second = Integer.parseInt(s.substring(secondColon + 1)); + } + + return new Time(hour, minute, second); + + } catch (NumberFormatException e) { + throw new java.lang.IllegalArgumentException("Number format Error parsing time [" + s + "] " + + e.getMessage(), e); + } + } } \ No newline at end of file diff --git a/src/main/java/com/avaje/ebean/text/csv/CsvCallback.java b/src/main/java/com/avaje/ebean/text/csv/CsvCallback.java index 09608fe40..b550be252 100644 --- a/src/main/java/com/avaje/ebean/text/csv/CsvCallback.java +++ b/src/main/java/com/avaje/ebean/text/csv/CsvCallback.java @@ -1,89 +1,89 @@ -package com.avaje.ebean.text.csv; - -import com.avaje.ebean.EbeanServer; - -/** - * Provides callback methods for customisation of CSV processing. - *- * You can provide your own CsvCallback implementation to customise the CSV - * processing. It is expected that the DefaultCsvCallback provides a good base - * class that you can extend. - *
- * - * @author rbygrave - * - * @param- * Typically the callback will create a transaction, set batch mode, batch - * size etc. - *
- */ - void begin(EbeanServer server); - - /** - * Read the header row. - *- * This is only called if {@link CsvReader#setHasHeader(boolean,boolean)} has - * been set to true. - *
- * - * @param line - * the header line content. - */ - void readHeader(String[] line); - - /** - * Check that the row should be processed - return true to process the row or - * false to ignore the row. Gives ability to handle bad data... empty rows etc - * and ignore it rather than fail. - */ - boolean processLine(int row, String[] line); - - /** - * Called for each bean after it has been loaded from the CSV content. - *- * This allows you to process the bean however you like. - *
- *- * When you use a CsvCallback the CsvReader *WILL NOT* create a transaction - * and will not save the bean for you. You have complete control and must do - * these things yourself (if that is want you want). - *
- * - * @param row - * the index of the content being processed - * @param line - * the content that has been used to load the bean - * @param bean - * the entity bean after it has been loaded from the csv content - */ - void processBean(int row, String[] line, T bean); - - /** - * The processing has ended successfully. - *- * Typically the callback will commit the transaction. - *
- */ - void end(int row); - - /** - * The processing has ended due to an error. - *- * This gives the callback the opportunity to rollback the transaction if one - * was created. - *
- * - * @param row - * the row that the error has occured on - * @param e - * the error that occured - */ - void endWithError(int row, Exception e); - -} +package com.avaje.ebean.text.csv; + +import com.avaje.ebean.EbeanServer; + +/** + * Provides callback methods for customisation of CSV processing. + *+ * You can provide your own CsvCallback implementation to customise the CSV + * processing. It is expected that the DefaultCsvCallback provides a good base + * class that you can extend. + *
+ * + * @author rbygrave + * + * @param+ * Typically the callback will create a transaction, set batch mode, batch + * size etc. + *
+ */ + void begin(EbeanServer server); + + /** + * Read the header row. + *+ * This is only called if {@link CsvReader#setHasHeader(boolean,boolean)} has + * been set to true. + *
+ * + * @param line + * the header line content. + */ + void readHeader(String[] line); + + /** + * Check that the row should be processed - return true to process the row or + * false to ignore the row. Gives ability to handle bad data... empty rows etc + * and ignore it rather than fail. + */ + boolean processLine(int row, String[] line); + + /** + * Called for each bean after it has been loaded from the CSV content. + *+ * This allows you to process the bean however you like. + *
+ *+ * When you use a CsvCallback the CsvReader *WILL NOT* create a transaction + * and will not save the bean for you. You have complete control and must do + * these things yourself (if that is want you want). + *
+ * + * @param row + * the index of the content being processed + * @param line + * the content that has been used to load the bean + * @param bean + * the entity bean after it has been loaded from the csv content + */ + void processBean(int row, String[] line, T bean); + + /** + * The processing has ended successfully. + *+ * Typically the callback will commit the transaction. + *
+ */ + void end(int row); + + /** + * The processing has ended due to an error. + *+ * This gives the callback the opportunity to rollback the transaction if one + * was created. + *
+ * + * @param row + * the row that the error has occured on + * @param e + * the error that occured + */ + void endWithError(int row, Exception e); + +} diff --git a/src/main/java/com/avaje/ebean/text/csv/CsvReader.java b/src/main/java/com/avaje/ebean/text/csv/CsvReader.java index c8b45011e..76ce0f2ef 100644 --- a/src/main/java/com/avaje/ebean/text/csv/CsvReader.java +++ b/src/main/java/com/avaje/ebean/text/csv/CsvReader.java @@ -1,173 +1,173 @@ -package com.avaje.ebean.text.csv; - -import java.io.Reader; -import java.util.Locale; - -import com.avaje.ebean.text.StringParser; - -/** - * Reads CSV data turning it into object graphs that you can be saved (inserted) - * or processed yourself. - * - *- * This first example doesn't use a {@link CsvCallback} and this means it will - * automatically create a transaction, save the customers and commit the - * transaction when successful. - *
- * - *
- * try {
- * File f = new File("src/test/resources/test1.csv");
- *
- * FileReader reader = new FileReader(f);
- *
- * CsvReader<Customer> csvReader = Ebean.createCsvReader(Customer.class);
- *
- * csvReader.setPersistBatchSize(20);
- *
- * csvReader.addProperty("status");
- * // ignore the next property
- * csvReader.addIgnore();
- * csvReader.addProperty("name");
- * csvReader.addDateTime("anniversary", "dd-MMM-yyyy");
- * csvReader.addProperty("billingAddress.line1");
- * csvReader.addProperty("billingAddress.city");
- *
- * csvReader.process(reader);
- *
- * } catch (Exception e) {
- * throw new RuntimeException(e);
- * }
- *
- *
- * @param - * By default this is set to 20 and setting this to 1 will disable the use of - * JDBC statement batching. - *
- */ - void setPersistBatchSize(int persistBatchSize); - - /** - * Set to true if there is a header row that should be ignored. - *- * If addPropertiesFromHeader is true then all the properties are added using - * the default time,date and timestamp formats. - *
- * If you have a mix of dateTime formats you can not use this method and must - * add the properties yourself. - *
- */ - void setHasHeader(boolean hasHeader, boolean addPropertiesFromHeader); - - /** - * Same as setHasHeader(true,true); - *- * This will use a header to define all the properties to load using the - * default formats for time, date and datetime types. - *
- */ - void setAddPropertiesFromHeader(); - - /** - * Same as setHasHeader(true, false); - *- * This indicates that there is a header but that it should be ignored. - *
- */ - void setIgnoreHeader(); - - /** - * Set the frequency with which a INFO message will be logged showing the - * progress of the processing. You might set this to 1000 or 10000 etc. - *- * If this is not set then no INFO messages will be logged. - *
- */ - void setLogInfoFrequency(int logInfoFrequency); - - /** - * Ignore the next column of data. - */ - void addIgnore(); - - /** - * Define the property which will be loaded from the next column of data. - *- * This takes into account the data type of the property and handles the - * String to object conversion automatically. - *
- */ - void addProperty(String propertyName); - - /** - * Define the next property and use a custom StringParser to convert the - * string content into the appropriate type for the property. - */ - void addProperty(String propertyName, StringParser parser); - - /** - * Add a property with a custom Date/Time/Timestamp format using the default - * Locale. This will convert the string into the appropriate java type for the - * given property (Date, Calendar, SQL Date, Time, Timestamp, JODA etc). - */ - void addDateTime(String propertyName, String dateTimeFormat); - - /** - * Add a property with a custom Date/Time/Timestamp format. This will convert - * the string into the appropriate java type for the given property (Date, - * Calendar, SQL Date, Time, Timestamp, JODA etc). - */ - void addDateTime(String propertyName, String dateTimeFormat, Locale locale); - - /** - * Automatically create a transaction if required to process all the CSV - * content from the reader. - *- * This will check for a current transaction. If there is no current - * transaction then one is started and will commit (or rollback) at the end of - * processing. This will also set the persistBatchSize on the transaction. - *
- */ - void process(Reader reader) throws Exception; - - /** - * Process the CSV content passing the bean to the CsvCallback after each row. - *- * This provides you with the ability to modify and process the bean. - *
- *- * When using a CsvCallback the reader WILL NOT create a transaction or save - * the bean(s) for you. If you want to insert the processed beans you must - * create your own transaction and save the bean(s) yourself. - *
- */ - void process(Reader reader, CsvCallback+ * This first example doesn't use a {@link CsvCallback} and this means it will + * automatically create a transaction, save the customers and commit the + * transaction when successful. + *
+ * + *
+ * try {
+ * File f = new File("src/test/resources/test1.csv");
+ *
+ * FileReader reader = new FileReader(f);
+ *
+ * CsvReader<Customer> csvReader = Ebean.createCsvReader(Customer.class);
+ *
+ * csvReader.setPersistBatchSize(20);
+ *
+ * csvReader.addProperty("status");
+ * // ignore the next property
+ * csvReader.addIgnore();
+ * csvReader.addProperty("name");
+ * csvReader.addDateTime("anniversary", "dd-MMM-yyyy");
+ * csvReader.addProperty("billingAddress.line1");
+ * csvReader.addProperty("billingAddress.city");
+ *
+ * csvReader.process(reader);
+ *
+ * } catch (Exception e) {
+ * throw new RuntimeException(e);
+ * }
+ *
+ *
+ * @param + * By default this is set to 20 and setting this to 1 will disable the use of + * JDBC statement batching. + *
+ */ + void setPersistBatchSize(int persistBatchSize); + + /** + * Set to true if there is a header row that should be ignored. + *+ * If addPropertiesFromHeader is true then all the properties are added using + * the default time,date and timestamp formats. + *
+ * If you have a mix of dateTime formats you can not use this method and must + * add the properties yourself. + *
+ */ + void setHasHeader(boolean hasHeader, boolean addPropertiesFromHeader); + + /** + * Same as setHasHeader(true,true); + *+ * This will use a header to define all the properties to load using the + * default formats for time, date and datetime types. + *
+ */ + void setAddPropertiesFromHeader(); + + /** + * Same as setHasHeader(true, false); + *+ * This indicates that there is a header but that it should be ignored. + *
+ */ + void setIgnoreHeader(); + + /** + * Set the frequency with which a INFO message will be logged showing the + * progress of the processing. You might set this to 1000 or 10000 etc. + *+ * If this is not set then no INFO messages will be logged. + *
+ */ + void setLogInfoFrequency(int logInfoFrequency); + + /** + * Ignore the next column of data. + */ + void addIgnore(); + + /** + * Define the property which will be loaded from the next column of data. + *+ * This takes into account the data type of the property and handles the + * String to object conversion automatically. + *
+ */ + void addProperty(String propertyName); + + /** + * Define the next property and use a custom StringParser to convert the + * string content into the appropriate type for the property. + */ + void addProperty(String propertyName, StringParser parser); + + /** + * Add a property with a custom Date/Time/Timestamp format using the default + * Locale. This will convert the string into the appropriate java type for the + * given property (Date, Calendar, SQL Date, Time, Timestamp, JODA etc). + */ + void addDateTime(String propertyName, String dateTimeFormat); + + /** + * Add a property with a custom Date/Time/Timestamp format. This will convert + * the string into the appropriate java type for the given property (Date, + * Calendar, SQL Date, Time, Timestamp, JODA etc). + */ + void addDateTime(String propertyName, String dateTimeFormat, Locale locale); + + /** + * Automatically create a transaction if required to process all the CSV + * content from the reader. + *+ * This will check for a current transaction. If there is no current + * transaction then one is started and will commit (or rollback) at the end of + * processing. This will also set the persistBatchSize on the transaction. + *
+ */ + void process(Reader reader) throws Exception; + + /** + * Process the CSV content passing the bean to the CsvCallback after each row. + *+ * This provides you with the ability to modify and process the bean. + *
+ *+ * When using a CsvCallback the reader WILL NOT create a transaction or save + * the bean(s) for you. If you want to insert the processed beans you must + * create your own transaction and save the bean(s) yourself. + *
+ */ + void process(Reader reader, CsvCallback- * This handles transaction creation (if no current transaction existed) and - * transaction commit or rollback on error. - *
- *- * For customising the processing you can extend this object and override the - * appropriate methods. - *
- * - * @author rob - * - * @param- * This is only called if {@link CsvReader#setHasHeader(boolean,boolean)} is - * set to true. - *
- *- * By default this does nothing (effectively ignoring the heading). - *
- */ - public void readHeader(String[] line) { - - } - - /** - * Validate that the content is valid and return false if the row should be - * ignored. - *- * By default this just returns true. - *
- *- * Override this to add custom validation logic returning false if you want - * the row to be ignored. For example, if all the content is empty return - * false to ignore the row (rather than having the processing fail with some - * error). - *
- */ - public boolean processLine(int row, String[] line) { - return true; - } - - /** - * Will save the bean. - *- * Override this method to customise the bean (set additional properties etc) - * or to control the saving of other related beans (when you can't/don't want - * to use Cascade.PERSIST etc). - *
- */ - public void processBean(int row, String[] line, T bean) { - - // assumes single bean or Cascade.PERSIST will save any - // related beans (e.g. customer -> customer.billingAddress - server.save(bean, transaction); - - if (logInfoFrequency > 0 && (row % logInfoFrequency == 0)) { - logger.info("processed " + row + " rows"); - } - } - - /** - * Commit the transaction if one was created. - */ - public void end(int row) { - - commitTransactionIfCreated(); - - exeTime = System.currentTimeMillis() - startTime; - logger.info("Csv finished, rows[" + row + "] exeMillis[" + exeTime + "]"); - } - - /** - * Rollback the transaction if one was created. - */ - public void endWithError(int row, Exception e) { - rollbackTransactionIfCreated(e); - } - - /** - * Create a transaction if one is not already active and set its batch mode - * and batch size. - */ - protected void initTransactionIfRequired() { - - transaction = server.currentTransaction(); - if (transaction == null || !transaction.isActive()) { - - transaction = server.beginTransaction(); - createdTransaction = true; - if (persistBatchSize > 1) { - logger.info("Creating transaction, batchSize[" + persistBatchSize + "]"); - transaction.setBatchMode(true); - transaction.setBatchSize(persistBatchSize); - - } else { - // explicitly turn off JDBC batching in case - // is has been turned on globally - transaction.setBatchMode(false); - logger.info("Creating transaction with no JDBC batching"); - } - } - } - - /** - * If we created a transaction commit it. We have successfully processed all - * the rows. - */ - protected void commitTransactionIfCreated() { - if (createdTransaction) { - transaction.commit(); - logger.info("Committed transaction"); - } - } - - /** - * Rollback the transaction if we where not successful in processing all the - * rows. - */ - protected void rollbackTransactionIfCreated(Throwable e) { - if (createdTransaction) { - transaction.rollback(e); - logger.info("Rolled back transaction"); - } - } - -} +package com.avaje.ebean.text.csv; + +import com.avaje.ebean.EbeanServer; +import com.avaje.ebean.Transaction; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Provides the default implementation of CsvCallback. + *+ * This handles transaction creation (if no current transaction existed) and + * transaction commit or rollback on error. + *
+ *+ * For customising the processing you can extend this object and override the + * appropriate methods. + *
+ * + * @author rob + * + * @param+ * This is only called if {@link CsvReader#setHasHeader(boolean,boolean)} is + * set to true. + *
+ *+ * By default this does nothing (effectively ignoring the heading). + *
+ */ + public void readHeader(String[] line) { + + } + + /** + * Validate that the content is valid and return false if the row should be + * ignored. + *+ * By default this just returns true. + *
+ *+ * Override this to add custom validation logic returning false if you want + * the row to be ignored. For example, if all the content is empty return + * false to ignore the row (rather than having the processing fail with some + * error). + *
+ */ + public boolean processLine(int row, String[] line) { + return true; + } + + /** + * Will save the bean. + *+ * Override this method to customise the bean (set additional properties etc) + * or to control the saving of other related beans (when you can't/don't want + * to use Cascade.PERSIST etc). + *
+ */ + public void processBean(int row, String[] line, T bean) { + + // assumes single bean or Cascade.PERSIST will save any + // related beans (e.g. customer -> customer.billingAddress + server.save(bean, transaction); + + if (logInfoFrequency > 0 && (row % logInfoFrequency == 0)) { + logger.info("processed " + row + " rows"); + } + } + + /** + * Commit the transaction if one was created. + */ + public void end(int row) { + + commitTransactionIfCreated(); + + exeTime = System.currentTimeMillis() - startTime; + logger.info("Csv finished, rows[" + row + "] exeMillis[" + exeTime + "]"); + } + + /** + * Rollback the transaction if one was created. + */ + public void endWithError(int row, Exception e) { + rollbackTransactionIfCreated(e); + } + + /** + * Create a transaction if one is not already active and set its batch mode + * and batch size. + */ + protected void initTransactionIfRequired() { + + transaction = server.currentTransaction(); + if (transaction == null || !transaction.isActive()) { + + transaction = server.beginTransaction(); + createdTransaction = true; + if (persistBatchSize > 1) { + logger.info("Creating transaction, batchSize[" + persistBatchSize + "]"); + transaction.setBatchMode(true); + transaction.setBatchSize(persistBatchSize); + + } else { + // explicitly turn off JDBC batching in case + // is has been turned on globally + transaction.setBatchMode(false); + logger.info("Creating transaction with no JDBC batching"); + } + } + } + + /** + * If we created a transaction commit it. We have successfully processed all + * the rows. + */ + protected void commitTransactionIfCreated() { + if (createdTransaction) { + transaction.commit(); + logger.info("Committed transaction"); + } + } + + /** + * Rollback the transaction if we where not successful in processing all the + * rows. + */ + protected void rollbackTransactionIfCreated(Throwable e) { + if (createdTransaction) { + transaction.rollback(e); + logger.info("Rolled back transaction"); + } + } + +} diff --git a/src/main/java/com/avaje/ebean/text/json/JsonContext.java b/src/main/java/com/avaje/ebean/text/json/JsonContext.java index 8c32a2234..5d42d8949 100644 --- a/src/main/java/com/avaje/ebean/text/json/JsonContext.java +++ b/src/main/java/com/avaje/ebean/text/json/JsonContext.java @@ -1,166 +1,166 @@ -package com.avaje.ebean.text.json; - -import com.avaje.ebean.text.PathProperties; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonParser; - -import java.io.Reader; -import java.io.Writer; -import java.lang.reflect.Type; -import java.util.List; - -/** - * Converts objects to and from JSON format. - */ -public interface JsonContext { - - /** - * Convert json string input into a Bean of a specific type. - * - * @throws JsonIOException When IOException occurs - */ -- * You can explicitly state which properties to include in the JSON output for - * the root level and each path. - *
- * @deprecated - */ -public class JsonWriteOptions { - - protected PathProperties pathProperties; - - /** - * Parse and return a PathProperties from nested string format like - * (a,b,c(d,e),f(g)) where "c" is a path containing "d" and "e" and "f" is a - * path containing "g" and the root path contains "a","b","c" and "f". - * - * @see com.avaje.ebean.text.PathProperties#parse(String) - */ - public static JsonWriteOptions parsePath(String pathProperties) { - - return pathProperties(PathProperties.parse(pathProperties)); - } - - /** - * Construct JsonWriteOptions with the given pathProperties. - */ - public static JsonWriteOptions pathProperties(PathProperties pathProperties) { - JsonWriteOptions o = new JsonWriteOptions(); - o.setPathProperties(pathProperties); - return o; - } - - /** - * Set the Map of properties to include by path. - */ - public void setPathProperties(PathProperties pathProperties) { - this.pathProperties = pathProperties; - } - - /** - * Return the properties to include by path. - */ - public PathProperties getPathProperties() { - return pathProperties; - } - -} +package com.avaje.ebean.text.json; + +import com.avaje.ebean.text.PathProperties; + +/** + * Deprecated in favour of just using PathProperties. + * + * Provides options for customising the JSON write process. + *+ * You can explicitly state which properties to include in the JSON output for + * the root level and each path. + *
+ * @deprecated + */ +public class JsonWriteOptions { + + protected PathProperties pathProperties; + + /** + * Parse and return a PathProperties from nested string format like + * (a,b,c(d,e),f(g)) where "c" is a path containing "d" and "e" and "f" is a + * path containing "g" and the root path contains "a","b","c" and "f". + * + * @see com.avaje.ebean.text.PathProperties#parse(String) + */ + public static JsonWriteOptions parsePath(String pathProperties) { + + return pathProperties(PathProperties.parse(pathProperties)); + } + + /** + * Construct JsonWriteOptions with the given pathProperties. + */ + public static JsonWriteOptions pathProperties(PathProperties pathProperties) { + JsonWriteOptions o = new JsonWriteOptions(); + o.setPathProperties(pathProperties); + return o; + } + + /** + * Set the Map of properties to include by path. + */ + public void setPathProperties(PathProperties pathProperties) { + this.pathProperties = pathProperties; + } + + /** + * Return the properties to include by path. + */ + public PathProperties getPathProperties() { + return pathProperties; + } + +} diff --git a/src/main/java/com/avaje/ebean/util/ClassUtil.java b/src/main/java/com/avaje/ebean/util/ClassUtil.java index 60ce5e08f..13e361e1b 100644 --- a/src/main/java/com/avaje/ebean/util/ClassUtil.java +++ b/src/main/java/com/avaje/ebean/util/ClassUtil.java @@ -1,61 +1,61 @@ -package com.avaje.ebean.util; - -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.Arrays; - -/** - * Helper to find classes taking into account the context class loader. - */ -public class ClassUtil { - - /** - * Return a new instance of the class using the default constructor. - */ - public static Object newInstance(String className) { - try { - Class> cls = Class.forName(className); - return cls.newInstance(); - } catch (Exception e) { - String msg = "Error constructing " + className; - throw new IllegalArgumentException(msg, e); - } - } - - - /** - * Returns the raw type for the 2nd generic parameter for a subclass. - */ - public static Class> getSecondArgumentType(Class> subclass) { - Type[] typeArguments = getSuperclassTypeParameter(subclass); - if (typeArguments.length != 2) { - throw new IllegalArgumentException("Expected type with 2 generic argument types but got " - + typeArguments.length + " - " + Arrays.toString(typeArguments)); - } - - return getRawType(typeArguments[1]); - } - - static Type[] getSuperclassTypeParameter(Class> subclass) { - Type superclass = subclass.getGenericSuperclass(); - if (superclass instanceof Class) { - throw new RuntimeException("Missing generics type parameters on subclass " + subclass); - } - return ((ParameterizedType) superclass).getActualTypeArguments(); - } - - private static Class> getRawType(Type type) { - - if (type instanceof Class>) { - return (Class>) type; - - } else if (type instanceof ParameterizedType) { - ParameterizedType parameterizedType = (ParameterizedType) type; - Type rawType = parameterizedType.getRawType(); - if (rawType instanceof Class>) { - return (Class>) rawType; - } - } - throw new RuntimeException("Unable to obtain raw class type from " + type); - } -} +package com.avaje.ebean.util; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Arrays; + +/** + * Helper to find classes taking into account the context class loader. + */ +public class ClassUtil { + + /** + * Return a new instance of the class using the default constructor. + */ + public static Object newInstance(String className) { + try { + Class> cls = Class.forName(className); + return cls.newInstance(); + } catch (Exception e) { + String msg = "Error constructing " + className; + throw new IllegalArgumentException(msg, e); + } + } + + + /** + * Returns the raw type for the 2nd generic parameter for a subclass. + */ + public static Class> getSecondArgumentType(Class> subclass) { + Type[] typeArguments = getSuperclassTypeParameter(subclass); + if (typeArguments.length != 2) { + throw new IllegalArgumentException("Expected type with 2 generic argument types but got " + + typeArguments.length + " - " + Arrays.toString(typeArguments)); + } + + return getRawType(typeArguments[1]); + } + + static Type[] getSuperclassTypeParameter(Class> subclass) { + Type superclass = subclass.getGenericSuperclass(); + if (superclass instanceof Class) { + throw new RuntimeException("Missing generics type parameters on subclass " + subclass); + } + return ((ParameterizedType) superclass).getActualTypeArguments(); + } + + private static Class> getRawType(Type type) { + + if (type instanceof Class>) { + return (Class>) type; + + } else if (type instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) type; + Type rawType = parameterizedType.getRawType(); + if (rawType instanceof Class>) { + return (Class>) rawType; + } + } + throw new RuntimeException("Unable to obtain raw class type from " + type); + } +} diff --git a/src/main/java/com/avaje/ebean/util/StringHelper.java b/src/main/java/com/avaje/ebean/util/StringHelper.java index e7cdf1b97..c8c4e7276 100644 --- a/src/main/java/com/avaje/ebean/util/StringHelper.java +++ b/src/main/java/com/avaje/ebean/util/StringHelper.java @@ -1,600 +1,600 @@ -package com.avaje.ebean.util; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; - -/** - * Utility String class that supports String manipulation functions. - */ -public class StringHelper { - - private static final char SINGLE_QUOTE = '\''; - - private static final char DOUBLE_QUOTE = '"'; - - /** - * parses a String of the form name1='value1' name2='value2'. Note that you - * can use either single or double quotes for any particular name value pair - * and the end quote must match the begin quote. - */ - public static HashMap
- * e.g. "alpha,beta,,theta"
- * With keepEmpties true, this results in a String[] of size 4 with the third
- * one having a String of 0 length. With keepEmpties false, this results in a
- * String[] of size 3.
- *
- *
- *
- * e.g. ",alpha,beta,,theta,"
- * With keepEmpties true, this results in a String[] of size 6 with the
- * 1st,4th and 6th one having a String of 0 length. With keepEmpties false,
- * this results in a String[] of size 3.
- *
- * If leftBound can't be found this returns null. - *
- *- * This rightBound can't be found then this throws a - * StringIndexOutOfBoundsException. - *
- * - * @param str - * the base string that we will search for the bounded string. - * @param leftBound - * the left bound of the string. - * @param rightBound - * the right bound of the string. - */ - public static String getBoundedString(String str, String leftBound, String rightBound) - throws RuntimeException { - - if (str == null) { - throw new RuntimeException("string to parse is null?"); - } - int startPos = str.indexOf(leftBound); - if (startPos > -1) { - startPos = startPos + leftBound.length(); - int endPos = str.indexOf(rightBound, startPos); - // dp(str+" start:"+startPos+" end:"+endPos); - if (endPos == -1) { - throw new RuntimeException("Can't find rightBound: " + rightBound); - } - return str.substring(startPos, endPos); - } else { - // if no leftBound can be found.. return null... could be in a - // search n parse type loop? - // this keeps "no tag"==null different from "tag not formed - // properly"==StringParsingException - return null; - } - } - - /** - * Takes the String bounded by leftBound & rightBound, and replaces it with - * replaceString. Actually removes the left and right bound strings aswell. - */ - public static String setBoundedString(String str, String leftBound, String rightBound, - String replaceString) { - - int startPos = str.indexOf(leftBound); - if (startPos > -1) { - // startPos = startPos; - int endPos = str.indexOf(rightBound, startPos + leftBound.length()); - if (endPos > -1) { - String toReplace = str.substring(startPos, endPos + 1); - return replaceString(str, toReplace, replaceString); - } else { - return str; - } - } else { - return str; - } - } - - // public static String replaceString(String str, String oldSub, String - // newSub) { - // - // if (str == null) { - // return null; - // } - // StringBuilder newSB = new StringBuilder(str.length()+20); - // int iPos = 0; - // int iPrevPos = 0; - // - // while (true) { - // iPos = str.indexOf(oldSub, iPrevPos); - // if (iPos > -1) { - // // found - // newSB.append(str.substring(iPrevPos, iPos)); - // newSB.append(newSub); - // iPrevPos = iPos + oldSub.length(); - // } else { - // // not found - // newSB.append(str.substring(iPrevPos)); - // break; - // } - // } - // - // return newSB.toString(); - // } - - /** - * This method takes a String and will replace all occurrences of the match - * String with that of the replace String. - * - * @param source - * the source string - * @param match - * the string used to find a match - * @param replace - * the string used to replace match with - * @return the source string after the search and replace - */ - public static String replaceString(String source, String match, String replace) { - if (source == null) { - return null; - } - if (replace == null) { - return source; - } - if (match == null) { - throw new NullPointerException("match is null?"); - } - if (match.equals(replace)) { - return source; - } - return replaceString(source, match, replace, 30, 0, source.length()); - } - - /** - * Additionally specify the additionalSize to add to the buffer. This will - * make the buffer bigger so that it doesn't have to grow when replacement - * occurs. - */ - public static String replaceString(String source, String match, String replace, - int additionalSize, int startPos, int endPos) { - - if (source == null) { - return source; - } - - char match0 = match.charAt(0); - - int matchLength = match.length(); - - if (matchLength == 1 && replace.length() == 1) { - char replace0 = replace.charAt(0); - return source.replace(match0, replace0); - } - if (matchLength >= replace.length()) { - additionalSize = 0; - } - - int sourceLength = source.length(); - int lastMatch = endPos - matchLength; - - StringBuilder sb = new StringBuilder(sourceLength + additionalSize); - - if (startPos > 0) { - sb.append(source.substring(0, startPos)); - } - - char sourceChar; - boolean isMatch; - int sourceMatchPos; - - for (int i = startPos; i < sourceLength; i++) { - sourceChar = source.charAt(i); - if (i > lastMatch || sourceChar != match0) { - sb.append(sourceChar); - - } else { - // check to see if this is a match - isMatch = true; - sourceMatchPos = i; - - // check each following character... - for (int j = 1; j < matchLength; j++) { - sourceMatchPos++; - if (source.charAt(sourceMatchPos) != match.charAt(j)) { - isMatch = false; - break; - } - } - if (isMatch) { - i = i + matchLength - 1; - sb.append(replace); - } else { - // was not a match - sb.append(sourceChar); - } - } - } - - return sb.toString(); - } - - /** - * A search and replace with multiple matching strings. - *- * Useful when converting CRNL CR and NL all to a BR tag for example. - *
- * - *
- *
- * String[] multi = { "\r\n", "\r", "\n" };
- * content = StringHelper.replaceStringMulti(content, multi, "<br/>");
- *
- *
- */
- public static String replaceStringMulti(String source, String[] match, String replace) {
- return replaceStringMulti(source, match, replace, 30, 0, source.length());
- }
-
- /**
- * Additionally specify an additional size estimate for the buffer plus start
- * and end positions.
- * - * The start and end positions can limit the search and replace. Otherwise - * these default to startPos = 0 and endPos = source.length(). - *
- */ - public static String replaceStringMulti(String source, String[] match, String replace, - int additionalSize, int startPos, int endPos) { - - int shortestMatch = match[0].length(); - - char[] match0 = new char[match.length]; - for (int i = 0; i < match0.length; i++) { - match0[i] = match[i].charAt(0); - if (match[i].length() < shortestMatch) { - shortestMatch = match[i].length(); - } - } - - StringBuilder sb = new StringBuilder(source.length() + additionalSize); - - char sourceChar; - - int len = source.length(); - int lastMatch = endPos - shortestMatch; - - if (startPos > 0) { - sb.append(source.substring(0, startPos)); - } - - int matchCount = 0; - - for (int i = startPos; i < len; i++) { - sourceChar = source.charAt(i); - if (i > lastMatch) { - sb.append(sourceChar); - } else { - matchCount = 0; - for (int k = 0; k < match0.length; k++) { - if (matchCount == 0 && sourceChar == match0[k]) { - if (match[k].length() + i <= len) { - - ++matchCount; - int j = 1; - for (; j < match[k].length(); j++) { - if (source.charAt(i + j) != match[k].charAt(j)) { - --matchCount; - break; - } - } - if (matchCount > 0) { - i = i + j - 1; - sb.append(replace); - break; - } - } - } - } - if (matchCount == 0) { - sb.append(sourceChar); - } - } - } - - return sb.toString(); - } - - /** - * This method takes a String as an argument and removes all occurrences of - * the supplied Char. It returns the resulting String. - */ - public static String removeChar(String s, char chr) { - - StringBuilder sb = new StringBuilder(s.length()); - - for (int i = 0; i < s.length(); i++) { - char c = s.charAt(i); - if (c != chr) { - sb.append(c); - } - } - - return sb.toString(); - } - - /** - * This method takes a String as an argument and removes all occurrences of - * the supplied Chars. It returns the resulting String. - */ - public static String removeChars(String s, char[] chr) { - - StringBuilder sb = new StringBuilder(s.length()); - - for (int i = 0; i < s.length(); i++) { - char c = s.charAt(i); - if (!charMatch(c, chr)) { - sb.append(c); - } - } - - return sb.toString(); - } - - private static boolean charMatch(int iChr, char[] chr) { - for (int i = 0; i < chr.length; i++) { - if (iChr == chr[i]) { - return true; - } - } - return false; - } - -} +package com.avaje.ebean.util; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +/** + * Utility String class that supports String manipulation functions. + */ +public class StringHelper { + + private static final char SINGLE_QUOTE = '\''; + + private static final char DOUBLE_QUOTE = '"'; + + /** + * parses a String of the form name1='value1' name2='value2'. Note that you + * can use either single or double quotes for any particular name value pair + * and the end quote must match the begin quote. + */ + public static HashMap
+ * e.g. "alpha,beta,,theta"
+ * With keepEmpties true, this results in a String[] of size 4 with the third
+ * one having a String of 0 length. With keepEmpties false, this results in a
+ * String[] of size 3.
+ *
+ *
+ *
+ * e.g. ",alpha,beta,,theta,"
+ * With keepEmpties true, this results in a String[] of size 6 with the
+ * 1st,4th and 6th one having a String of 0 length. With keepEmpties false,
+ * this results in a String[] of size 3.
+ *
+ * If leftBound can't be found this returns null. + *
+ *+ * This rightBound can't be found then this throws a + * StringIndexOutOfBoundsException. + *
+ * + * @param str + * the base string that we will search for the bounded string. + * @param leftBound + * the left bound of the string. + * @param rightBound + * the right bound of the string. + */ + public static String getBoundedString(String str, String leftBound, String rightBound) + throws RuntimeException { + + if (str == null) { + throw new RuntimeException("string to parse is null?"); + } + int startPos = str.indexOf(leftBound); + if (startPos > -1) { + startPos = startPos + leftBound.length(); + int endPos = str.indexOf(rightBound, startPos); + // dp(str+" start:"+startPos+" end:"+endPos); + if (endPos == -1) { + throw new RuntimeException("Can't find rightBound: " + rightBound); + } + return str.substring(startPos, endPos); + } else { + // if no leftBound can be found.. return null... could be in a + // search n parse type loop? + // this keeps "no tag"==null different from "tag not formed + // properly"==StringParsingException + return null; + } + } + + /** + * Takes the String bounded by leftBound & rightBound, and replaces it with + * replaceString. Actually removes the left and right bound strings aswell. + */ + public static String setBoundedString(String str, String leftBound, String rightBound, + String replaceString) { + + int startPos = str.indexOf(leftBound); + if (startPos > -1) { + // startPos = startPos; + int endPos = str.indexOf(rightBound, startPos + leftBound.length()); + if (endPos > -1) { + String toReplace = str.substring(startPos, endPos + 1); + return replaceString(str, toReplace, replaceString); + } else { + return str; + } + } else { + return str; + } + } + + // public static String replaceString(String str, String oldSub, String + // newSub) { + // + // if (str == null) { + // return null; + // } + // StringBuilder newSB = new StringBuilder(str.length()+20); + // int iPos = 0; + // int iPrevPos = 0; + // + // while (true) { + // iPos = str.indexOf(oldSub, iPrevPos); + // if (iPos > -1) { + // // found + // newSB.append(str.substring(iPrevPos, iPos)); + // newSB.append(newSub); + // iPrevPos = iPos + oldSub.length(); + // } else { + // // not found + // newSB.append(str.substring(iPrevPos)); + // break; + // } + // } + // + // return newSB.toString(); + // } + + /** + * This method takes a String and will replace all occurrences of the match + * String with that of the replace String. + * + * @param source + * the source string + * @param match + * the string used to find a match + * @param replace + * the string used to replace match with + * @return the source string after the search and replace + */ + public static String replaceString(String source, String match, String replace) { + if (source == null) { + return null; + } + if (replace == null) { + return source; + } + if (match == null) { + throw new NullPointerException("match is null?"); + } + if (match.equals(replace)) { + return source; + } + return replaceString(source, match, replace, 30, 0, source.length()); + } + + /** + * Additionally specify the additionalSize to add to the buffer. This will + * make the buffer bigger so that it doesn't have to grow when replacement + * occurs. + */ + public static String replaceString(String source, String match, String replace, + int additionalSize, int startPos, int endPos) { + + if (source == null) { + return source; + } + + char match0 = match.charAt(0); + + int matchLength = match.length(); + + if (matchLength == 1 && replace.length() == 1) { + char replace0 = replace.charAt(0); + return source.replace(match0, replace0); + } + if (matchLength >= replace.length()) { + additionalSize = 0; + } + + int sourceLength = source.length(); + int lastMatch = endPos - matchLength; + + StringBuilder sb = new StringBuilder(sourceLength + additionalSize); + + if (startPos > 0) { + sb.append(source.substring(0, startPos)); + } + + char sourceChar; + boolean isMatch; + int sourceMatchPos; + + for (int i = startPos; i < sourceLength; i++) { + sourceChar = source.charAt(i); + if (i > lastMatch || sourceChar != match0) { + sb.append(sourceChar); + + } else { + // check to see if this is a match + isMatch = true; + sourceMatchPos = i; + + // check each following character... + for (int j = 1; j < matchLength; j++) { + sourceMatchPos++; + if (source.charAt(sourceMatchPos) != match.charAt(j)) { + isMatch = false; + break; + } + } + if (isMatch) { + i = i + matchLength - 1; + sb.append(replace); + } else { + // was not a match + sb.append(sourceChar); + } + } + } + + return sb.toString(); + } + + /** + * A search and replace with multiple matching strings. + *+ * Useful when converting CRNL CR and NL all to a BR tag for example. + *
+ * + *
+ *
+ * String[] multi = { "\r\n", "\r", "\n" };
+ * content = StringHelper.replaceStringMulti(content, multi, "<br/>");
+ *
+ *
+ */
+ public static String replaceStringMulti(String source, String[] match, String replace) {
+ return replaceStringMulti(source, match, replace, 30, 0, source.length());
+ }
+
+ /**
+ * Additionally specify an additional size estimate for the buffer plus start
+ * and end positions.
+ * + * The start and end positions can limit the search and replace. Otherwise + * these default to startPos = 0 and endPos = source.length(). + *
+ */ + public static String replaceStringMulti(String source, String[] match, String replace, + int additionalSize, int startPos, int endPos) { + + int shortestMatch = match[0].length(); + + char[] match0 = new char[match.length]; + for (int i = 0; i < match0.length; i++) { + match0[i] = match[i].charAt(0); + if (match[i].length() < shortestMatch) { + shortestMatch = match[i].length(); + } + } + + StringBuilder sb = new StringBuilder(source.length() + additionalSize); + + char sourceChar; + + int len = source.length(); + int lastMatch = endPos - shortestMatch; + + if (startPos > 0) { + sb.append(source.substring(0, startPos)); + } + + int matchCount = 0; + + for (int i = startPos; i < len; i++) { + sourceChar = source.charAt(i); + if (i > lastMatch) { + sb.append(sourceChar); + } else { + matchCount = 0; + for (int k = 0; k < match0.length; k++) { + if (matchCount == 0 && sourceChar == match0[k]) { + if (match[k].length() + i <= len) { + + ++matchCount; + int j = 1; + for (; j < match[k].length(); j++) { + if (source.charAt(i + j) != match[k].charAt(j)) { + --matchCount; + break; + } + } + if (matchCount > 0) { + i = i + j - 1; + sb.append(replace); + break; + } + } + } + } + if (matchCount == 0) { + sb.append(sourceChar); + } + } + } + + return sb.toString(); + } + + /** + * This method takes a String as an argument and removes all occurrences of + * the supplied Char. It returns the resulting String. + */ + public static String removeChar(String s, char chr) { + + StringBuilder sb = new StringBuilder(s.length()); + + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (c != chr) { + sb.append(c); + } + } + + return sb.toString(); + } + + /** + * This method takes a String as an argument and removes all occurrences of + * the supplied Chars. It returns the resulting String. + */ + public static String removeChars(String s, char[] chr) { + + StringBuilder sb = new StringBuilder(s.length()); + + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (!charMatch(c, chr)) { + sb.append(c); + } + } + + return sb.toString(); + } + + private static boolean charMatch(int iChr, char[] chr) { + for (int i = 0; i < chr.length; i++) { + if (iChr == chr[i]) { + return true; + } + } + return false; + } + +}