diff --git a/pom.xml b/pom.xml index ad5c5a4..d5ca117 100644 --- a/pom.xml +++ b/pom.xml @@ -1,248 +1,252 @@ - - 4.0.0 - com.j256.simplecsv - simplecsv - 2.7-SNAPSHOT - jar - Simple CSV - https://256stuff.com/sources/simplecsv/ - Simple CSV Java package which helps with the reading and writing of CSV files using annotations. - - - ISC License - repo - https://opensource.org/licenses/ISC - - - - org.sonatype.oss - oss-parent - 5 - - - - - 3.4 - 4.13.2 - 2.13 - - - https://github.com/j256/simplecsv - scm:git:ssh://git@github.com/j256/simplecsv.git - scm:git:ssh://git@github.com/j256/simplecsv.git - - - - gray - Gray Watson - https://256stuff.com/gray/ - 256stuff.com - https://256stuff.com/ - - architect - developer - - -5 - - - - - disable-java8-doclint - - [1.8,) - - - -Xdoclint:none - - - - st - - - sonatype-nexus-staging - Nexus Release Repository - https://oss.sonatype.org/service/local/staging/deploy/maven2/ - - - sonatype-nexus-snapshots - Sonatype Nexus Snapshots - https://oss.sonatype.org/content/repositories/snapshots/ - - - - - - simplecsv - - - - src/main/resources - - - - - - src/test/resources - - - - - - org.apache.maven.plugins - maven-release-plugin - - forked-path - false - -Psonatype-oss-release - - - - org.apache.maven.plugins - maven-compiler-plugin - 2.5.1 - - 1.6 - 1.6 - - - - org.apache.maven.plugins - maven-dependency-plugin - 2.6 - - - org.apache.maven.plugins - maven-source-plugin - 2.2.1 - - - - jar - test-jar - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.9.1 - - - - jar - - - false - -tag inheritDoc:X ${disableDoclint} - - - - - false - - This documentation content is licensed by Gray Watson under the - <a - href="https://creativecommons.org/licenses/by-sa/3.0/" >Creative Commons Attribution-Share Alike 3.0 License. - </a> - -tag inheritDoc:X ${disableDoclint} - - - - - - org.apache.maven.plugins - maven-eclipse-plugin - 2.9 - - - org.apache.maven.plugins - maven-gpg-plugin - 1.6 - - - sign-artifacts - verify - - sign - - - - - - D3412AC1 - - - - org.jacoco - jacoco-maven-plugin - 0.8.6 - - - - prepare-agent - - - - report - test - - report - - - - - - org.apache.maven.plugins - maven-surefire-plugin - 2.19.1 - - false - - - - com.j256.testcheckpublisher - test-check-publisher-maven-plugin - 1.5 - - - - - - org.eclipse.m2e - lifecycle-mapping - 1.0.0 - - - - - - org.apache.maven.plugins - maven-enforcer-plugin - [1.0-beta-1,) - - enforce - - - - - - - - - - - - - - - - - junit - junit - ${junit-version} - test - - + + 4.0.0 + com.j256.simplecsv + simplecsv + 2.7-SNAPSHOT + jar + Simple CSV + https://256stuff.com/sources/simplecsv/ + Simple CSV Java package which helps with the reading and writing of CSV files using annotations. + + + + ISC License + repo + https://opensource.org/licenses/ISC + + + + org.sonatype.oss + oss-parent + 5 + + + + + 3.4 + 4.13.2 + 2.13 + + + https://github.com/j256/simplecsv + scm:git:ssh://git@github.com/j256/simplecsv.git + scm:git:ssh://git@github.com/j256/simplecsv.git + + + + gray + Gray Watson + https://256stuff.com/gray/ + 256stuff.com + https://256stuff.com/ + + architect + developer + + -5 + + + + + disable-java8-doclint + + [1.8,) + + + -Xdoclint:none + + + + st + + + sonatype-nexus-staging + Nexus Release Repository + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + sonatype-nexus-snapshots + Sonatype Nexus Snapshots + https://oss.sonatype.org/content/repositories/snapshots/ + + + + + + simplecsv + + + + src/main/resources + + + + + + src/test/resources + + + + + + org.apache.maven.plugins + maven-release-plugin + + forked-path + false + -Psonatype-oss-release + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.5.1 + + 8 + 8 + + + + org.apache.maven.plugins + maven-dependency-plugin + 2.6 + + + org.apache.maven.plugins + maven-source-plugin + 2.2.1 + + + + jar + test-jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.9.1 + + + + jar + + + false + -tag inheritDoc:X ${disableDoclint} + + + + + false + + This documentation content is licensed by Gray Watson under the + <a + href="https://creativecommons.org/licenses/by-sa/3.0/" >Creative Commons Attribution-Share + Alike 3.0 License. + </a> + + -tag inheritDoc:X ${disableDoclint} + + + + + + org.apache.maven.plugins + maven-eclipse-plugin + 2.9 + + + org.apache.maven.plugins + maven-gpg-plugin + 1.6 + + + sign-artifacts + verify + + sign + + + + + + D3412AC1 + + + + org.jacoco + jacoco-maven-plugin + 0.8.6 + + + + prepare-agent + + + + report + test + + report + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.19.1 + + false + + + + com.j256.testcheckpublisher + test-check-publisher-maven-plugin + 1.5 + + + + + + org.eclipse.m2e + lifecycle-mapping + 1.0.0 + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + [1.0-beta-1,) + + enforce + + + + + + + + + + + + + + + + + junit + junit + ${junit-version} + test + + diff --git a/src/main/java/com/j256/simplecsv/converter/ConverterUtils.java b/src/main/java/com/j256/simplecsv/converter/ConverterUtils.java index d6ab1fd..d84b569 100644 --- a/src/main/java/com/j256/simplecsv/converter/ConverterUtils.java +++ b/src/main/java/com/j256/simplecsv/converter/ConverterUtils.java @@ -3,60 +3,62 @@ import java.lang.reflect.Constructor; import java.math.BigDecimal; import java.math.BigInteger; +import java.time.Instant; import java.util.Date; import java.util.Map; import java.util.UUID; /** * Manages the collection of converter objects so we can reuse them as necessary. - * + * * @author graywatson */ public class ConverterUtils { - /** - * Add internal converters to the map. - */ - public static void addInternalConverters(Map, Converter> converterMap) { - converterMap.put(BigDecimal.class, BigDecimalConverter.getSingleton()); - converterMap.put(BigInteger.class, BigIntegerConverter.getSingleton()); - converterMap.put(Boolean.class, BooleanConverter.getSingleton()); - converterMap.put(boolean.class, BooleanConverter.getSingleton()); - converterMap.put(Byte.class, ByteConverter.getSingleton()); - converterMap.put(byte.class, ByteConverter.getSingleton()); - converterMap.put(Character.class, CharacterConverter.getSingleton()); - converterMap.put(char.class, CharacterConverter.getSingleton()); - converterMap.put(Date.class, DateConverter.getSingleton()); - converterMap.put(Double.class, DoubleConverter.getSingleton()); - converterMap.put(double.class, DoubleConverter.getSingleton()); - converterMap.put(Enum.class, EnumConverter.getSingleton()); - converterMap.put(Float.class, FloatConverter.getSingleton()); - converterMap.put(float.class, FloatConverter.getSingleton()); - converterMap.put(Integer.class, IntegerConverter.getSingleton()); - converterMap.put(int.class, IntegerConverter.getSingleton()); - converterMap.put(Long.class, LongConverter.getSingleton()); - converterMap.put(long.class, LongConverter.getSingleton()); - converterMap.put(Short.class, ShortConverter.getSingleton()); - converterMap.put(short.class, ShortConverter.getSingleton()); - converterMap.put(String.class, StringConverter.getSingleton()); - converterMap.put(UUID.class, UuidConverter.getSingleton()); - } + /** + * Add internal converters to the map. + */ + public static void addInternalConverters(Map, Converter> converterMap) { + converterMap.put(BigDecimal.class, BigDecimalConverter.getSingleton()); + converterMap.put(BigInteger.class, BigIntegerConverter.getSingleton()); + converterMap.put(Boolean.class, BooleanConverter.getSingleton()); + converterMap.put(boolean.class, BooleanConverter.getSingleton()); + converterMap.put(Byte.class, ByteConverter.getSingleton()); + converterMap.put(byte.class, ByteConverter.getSingleton()); + converterMap.put(Character.class, CharacterConverter.getSingleton()); + converterMap.put(char.class, CharacterConverter.getSingleton()); + converterMap.put(Date.class, DateConverter.getSingleton()); + converterMap.put(Instant.class, InstantConverter.getSingleton()); + converterMap.put(Double.class, DoubleConverter.getSingleton()); + converterMap.put(double.class, DoubleConverter.getSingleton()); + converterMap.put(Enum.class, EnumConverter.getSingleton()); + converterMap.put(Float.class, FloatConverter.getSingleton()); + converterMap.put(float.class, FloatConverter.getSingleton()); + converterMap.put(Integer.class, IntegerConverter.getSingleton()); + converterMap.put(int.class, IntegerConverter.getSingleton()); + converterMap.put(Long.class, LongConverter.getSingleton()); + converterMap.put(long.class, LongConverter.getSingleton()); + converterMap.put(Short.class, ShortConverter.getSingleton()); + converterMap.put(short.class, ShortConverter.getSingleton()); + converterMap.put(String.class, StringConverter.getSingleton()); + converterMap.put(UUID.class, UuidConverter.getSingleton()); + } - /** - * Construct a converter instance. - */ - public static Converter constructConverter(Class> clazz) { - Constructor> constructor; - try { - constructor = clazz.getConstructor(); - } catch (Exception e) { - throw new IllegalArgumentException("Could not find public no-arg constructor for CSV converter class: " - + clazz, e); - } - try { - return constructor.newInstance(); - } catch (Exception e) { - throw new IllegalArgumentException("Could not construct new CSV converter: " + clazz, e); - } - } + /** + * Construct a converter instance. + */ + public static Converter constructConverter(Class> clazz) { + Constructor> constructor; + try { + constructor = clazz.getConstructor(); + } catch (Exception e) { + throw new IllegalArgumentException("Could not find public no-arg constructor for CSV converter class: " + + clazz, e); + } + try { + return constructor.newInstance(); + } catch (Exception e) { + throw new IllegalArgumentException("Could not construct new CSV converter: " + clazz, e); + } + } } diff --git a/src/main/java/com/j256/simplecsv/converter/InstantConverter.java b/src/main/java/com/j256/simplecsv/converter/InstantConverter.java new file mode 100644 index 0000000..eacc052 --- /dev/null +++ b/src/main/java/com/j256/simplecsv/converter/InstantConverter.java @@ -0,0 +1,124 @@ +package com.j256.simplecsv.converter; + +import com.j256.simplecsv.common.CsvColumn; +import com.j256.simplecsv.processor.ColumnInfo; +import com.j256.simplecsv.processor.ParseError; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.time.Instant; +import java.util.Date; + +/** + * Converter for the Java java.util.Date type which uses the {@link SimpleDateFormat} -- don't worry I protect it for + * reentrance. + * + *

+ * The {@link CsvColumn#format()} parameter can be set the {@link SimpleDateFormat} format string to read and write the + * date. + *

+ * + * @author graywatson + */ +public class InstantConverter implements Converter { + + /** + * Default {@link SimpleDateFormat} format pattern used to read/write java.util.Date types. + */ + public static final String DEFAULT_DATE_PATTERN = "MM/dd/yyyy"; + + /* + * We need to do this because SimpleDateFormat is not thread safe. + */ + private final ThreadLocal threadLocal = new ThreadLocal() { + @Override + protected OurDateFormatter initialValue() { + return new OurDateFormatter(); + } + }; + + private static final InstantConverter singleton = new InstantConverter(); + + /** + * Get singleton for class. + */ + public static InstantConverter getSingleton() { + return singleton; + } + + @Override + public String configure(String format, long flags, ColumnInfo fieldInfo) { + String datePattern; + if (format == null) { + datePattern = DEFAULT_DATE_PATTERN; + } else { + datePattern = format; + } + // we do this to validate that the pattern is correct so we throw immediately here + new SimpleDateFormat(datePattern); + return datePattern; + } + + @Override + public boolean isNeedsQuotes(String datePattern) { + return true; + } + + @Override + public boolean isAlwaysTrimInput() { + return false; + } + + @Override + public String javaToString(ColumnInfo columnInfo, Instant value) { + if (value == null) { + return null; + } else { + String datePattern = (String) columnInfo.getConfigInfo(); + return threadLocal.get().format(datePattern, value); + } + } + + @Override + public Instant stringToJava(String line, int lineNumber, int linePos, ColumnInfo columnInfo, String value, + ParseError parseError) throws ParseException { + if (value.isEmpty()) { + return null; + } + String datePattern = (String) columnInfo.getConfigInfo(); + try { + return threadLocal.get().parse(datePattern, value); + } catch (ParseException pe) { + ParseException wrappedPe = + new ParseException("Problem when using date-pattern: " + datePattern, pe.getErrorOffset()); + wrappedPe.initCause(pe); + throw wrappedPe; + } + } + + /** + * Formatter which saves a single SimpleDateFormat object in a thread-local. + */ + private static class OurDateFormatter { + + private String format; + private SimpleDateFormat formatter; + + public Instant parse(String format, String dateString) throws ParseException { + return checkFormatter(format).parse(dateString).toInstant(); + } + + public String format(String format, Instant date) { + return checkFormatter(format).format(Date.from(date)); + } + + private SimpleDateFormat checkFormatter(String format) { + // if we have no format or the format doesn't matched the cached one + if (this.format == null || !this.format.equals(format)) { + this.formatter = new SimpleDateFormat(format); + this.format = format; + } + return this.formatter; + } + } +} diff --git a/src/test/java/com/j256/simplecsv/converter/InstantConverterTest.java b/src/test/java/com/j256/simplecsv/converter/InstantConverterTest.java new file mode 100644 index 0000000..d971ee7 --- /dev/null +++ b/src/test/java/com/j256/simplecsv/converter/InstantConverterTest.java @@ -0,0 +1,56 @@ +package com.j256.simplecsv.converter; + +import org.junit.Test; + +import java.text.ParseException; +import java.time.Instant; +import java.util.Calendar; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class InstantConverterTest extends AbstractConverterTest { + + @Test + public void testStuff() throws ParseException { + InstantConverter converter = InstantConverter.getSingleton(); + testConverter(converter, Instant.class, null, 0, makeDate(2014, 11, 23)); + testConverter(converter, Instant.class, null, 0, makeDate(2014, 1, 1)); + testConverter(converter, Instant.class, null, 0, makeDate(2014, 1, 30)); + testConverter(converter, Instant.class, null, 0, makeDate(2014, 12, 31)); + testConverter(converter, Instant.class, null, 0, null); + + converter = InstantConverter.getSingleton(); + testConverter(converter, Instant.class, "yyyyMMdd", 0, makeDate(2014, 11, 23)); + testConverter(converter, Instant.class, "yyyyMMdd", 0, makeDate(2014, 1, 1)); + testConverter(converter, Instant.class, "yyyyMMdd", 0, makeDate(2014, 1, 30)); + testConverter(converter, Instant.class, "yyyyMMdd", 0, makeDate(2014, 12, 31)); + testConverter(converter, Instant.class, "yyyyMMdd", 0, null); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidPattern() { + InstantConverter converter = InstantConverter.getSingleton(); + converter.configure("notagoodpattern", 0, null); + } + + @Test + public void testConverage() { + InstantConverter converter = InstantConverter.getSingleton(); + assertTrue(converter.isNeedsQuotes(null)); + assertFalse(converter.isAlwaysTrimInput()); + } + + private Instant makeDate(int year, int month, int day) { + Calendar calender = Calendar.getInstance(); + calender.set(Calendar.YEAR, year); + calender.set(Calendar.MONTH, month - 1); + calender.set(Calendar.DAY_OF_MONTH, day); + calender.set(Calendar.HOUR, 0); + calender.set(Calendar.MINUTE, 0); + calender.set(Calendar.SECOND, 0); + calender.set(Calendar.MILLISECOND, 0); + calender.set(Calendar.AM_PM, 0); + return calender.getTime().toInstant(); + } +}