diff --git a/src/main/java/me/mneri/csv/CsvReader.java b/src/main/java/me/mneri/csv/CsvReader.java index 876be20..d1e31ce 100644 --- a/src/main/java/me/mneri/csv/CsvReader.java +++ b/src/main/java/me/mneri/csv/CsvReader.java @@ -222,8 +222,52 @@ public T next() throws CsvException, IOException { */ public static CsvReader open(File file, CsvDeserializer deserializer) throws IOException { return open(file, CsvOptions.defaultOptions(), deserializer); - } - + } + + /** + * Opens a file for reading using {@link DefaultCsvDeserializer}, returning a {@code CsvReader}. Bytes from the file are + * decoded into characters using the default JVM charset. Reading commences at the beginning of the file. + * + * @param file the file to open. + * @param clazz The class of the objects. + * @param the type of the objects to read. + * @return A new {@code CsvReader} to read the specified file. + * @throws IOException if an I/O error occurs. + */ + public static CsvReader open(File file, Class clazz) throws IOException { + return open(file, CsvOptions.defaultOptions(), new DefaultCsvDeserializer(clazz)); + } + + /** + * Opens a file for reading using {@link DefaultCsvDeserializer}, returning a {@code CsvReader}. Bytes from the file are + * decoded into characters using the default JVM charset. Reading commences at the beginning of the file. + * + * @param file the file to open. + * @param options reading options. + * @param clazz The class of the objects. + * @param the type of the objects to read. + * @return A new {@code CsvReader} to read the specified file. + * @throws IOException if an I/O error occurs. + */ + public static CsvReader open(File file, CsvOptions options, Class clazz) throws IOException { + return open(file, TextUtil.defaultCharset(), options, new DefaultCsvDeserializer(clazz)); + } + + /** + * Opens a file for reading using {@link DefaultCsvDeserializer}, returning a {@code CsvReader}. Bytes from the file are + * decoded into characters using the specified charset. Reading commences at the beginning of the file. + * + * @param file the file to open. + * @param charset the charset of the file. + * @param clazz The class of the objects. + * @param the type of the objects to read. + * @return A new {@code CsvReader} to read the specified file. + * @throws IOException if an I/O error occurs. + */ + public static CsvReader open(File file, Charset charset, Class clazz) throws IOException { + return open(file, charset, CsvOptions.defaultOptions(), new DefaultCsvDeserializer(clazz)); + } + /** * Opens a file for reading, returning a {@code CsvReader}. Bytes from the file are decoded into characters using * the default JVM charset. Reading commences at the beginning of the file. diff --git a/src/main/java/me/mneri/csv/CsvWriter.java b/src/main/java/me/mneri/csv/CsvWriter.java index f09b7d5..de79d88 100644 --- a/src/main/java/me/mneri/csv/CsvWriter.java +++ b/src/main/java/me/mneri/csv/CsvWriter.java @@ -127,6 +127,24 @@ public static CsvWriter open(File file, Charset charset, CsvOptions optio throws IOException { return open(Files.newBufferedWriter(file.toPath(), charset), options, serializer); } + + /** + * Opens or creates a file for writing, returning a {@code CsvWriter} that may be used to write object to the file in + * csv format. The file is opened for writing, created if it doesn't exist or initially truncated to a size of 0 if it + * exists. Characters are encoded using the specified charset. Objects are serialized using + * {@link DefaultCsvSerializer}. + * + * @param file the file to open. + * @param charset the charset to use for encoding. + * @param options writing options. + * @param clazz The class of the objects. + * @param the type of the objects to serialize. + * @return A new {@code CsvWriter} to write into the specified file. + * @throws IOException if an I/O error occurs. + */ + public static CsvWriter open(File file, Charset charset, CsvOptions options, Class clazz) throws IOException { + return open(Files.newBufferedWriter(file.toPath(), charset), options, new DefaultCsvSerializer(clazz)); + } /** * Opens or creates a file for writing, returning a {@code CsvWriter} that may be used to write object to the file @@ -142,21 +160,71 @@ public static CsvWriter open(File file, Charset charset, CsvOptions optio */ public static CsvWriter open(File file, CsvSerializer serializer) throws IOException { return open(file, CsvOptions.defaultOptions(), serializer); - } - - /** - * Opens or creates a file for writing, returning a {@code CsvWriter} that may be used to write object to the file - * in csv format. The file is opened for writing, created if it doesn't exist or initially truncated to a size of 0 - * if it exists. Characters are encoded using the default JVM charset. Objects are serialized using the specified - * serializer. - * - * @param file the file to open. - * @param options writing options. - * @param serializer the serializer used to convert objects into csv lines. - * @param the type of the objects to serialize. - * @return A new {@code CsvWriter} to write into the specified file. - * @throws IOException if an I/O error occurs. - */ + } + + /** + * Opens or creates a file for writing, returning a {@code CsvWriter} that may be used to write object to the file in + * csv format. The file is opened for writing, created if it doesn't exist or initially truncated to a size of 0 if it + * exists. Characters are encoded using the default JVM charset. Objects are serialized using + * {@link DefaultCsvSerializer}. + * + * @param file the file to open. + * @param clazz The class of the objects. + * @param the type of the objects to serialize. + * @return A new {@code CsvWriter} to write into the specified file. + * @throws IOException if an I/O error occurs. + */ + public static CsvWriter open(File file, Class clazz) throws IOException { + return open(file, new DefaultCsvSerializer(clazz)); + } + + /** + * Opens or creates a file for writing, returning a {@code CsvWriter} that may be used to write object to the file in + * csv format. The file is opened for writing, created if it doesn't exist or initially truncated to a size of 0 if it + * exists. Characters are encoded using the default JVM charset. Objects are serialized using + * {@link DefaultCsvSerializer}. + * + * + * @param file the file to open. + * @param options writing options. + * @param clazz The class of the objects. + * @param the type of the objects to serialize. + * @return A new {@code CsvWriter} to write into the specified file. + * @throws IOException if an I/O error occurs. + */ + public static CsvWriter open(File file, CsvOptions options, Class clazz) throws IOException { + return open(file, TextUtil.defaultCharset(), options, new DefaultCsvSerializer(clazz)); + } + + /** + * Return a new {@code CsvWriter} using the specified {@link Writer} for writing and a {@link DefaultCsvSerializer} to + * serialize. Bytes file are encoded into characters using the writer's charset. Writing commences at the point + * specified by the reader. + * + * @param writer the {@link Writer} used to write. + * @param clazz The class of the objects. + * @param the type of the objects to serialize. + * @return A new {@code CsvWriter} to write into the specified file. + */ + public static CsvWriter open(Writer writer, Class clazz) { + return open(writer, CsvOptions.defaultOptions(), new DefaultCsvSerializer(clazz)); + } + + + /** + * Opens or creates a file for writing, returning a {@code CsvWriter} that may + * be used to write object to the file in csv format. The file is opened for + * writing, created if it doesn't exist or initially truncated to a size of 0 if + * it exists. Characters are encoded using the default JVM charset. Objects are + * serialized using the specified serializer. + * + * @param file the file to open. + * @param options writing options. + * @param serializer the serializer used to convert objects into csv lines. + * @param the type of the objects to serialize. + * @return A new {@code CsvWriter} to write into the specified file. + * @throws IOException if an I/O error occurs. + */ public static CsvWriter open(File file, CsvOptions options, CsvSerializer serializer) throws IOException { return open(file, TextUtil.defaultCharset(), options, serializer); } diff --git a/src/main/java/me/mneri/csv/DefaultCsvDeserializer.java b/src/main/java/me/mneri/csv/DefaultCsvDeserializer.java new file mode 100644 index 0000000..1df9bf2 --- /dev/null +++ b/src/main/java/me/mneri/csv/DefaultCsvDeserializer.java @@ -0,0 +1,112 @@ +/* + * Copyright 2018 Massimo Neri + * + * This file is part of mneri/csv. + * + * 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 me.mneri.csv; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * A {@link CsvDeserializer} to deserialize objects after they have been serialized with {@link DefaultCsvSerializer}. + * Values are passed to the fields using the setters of the class. Transient fields will be ignored. + * + * @author George Zougianos github.com/gzougianos + * + * @param The type of the Java objects to deserialize. + */ +public class DefaultCsvDeserializer implements CsvDeserializer { + private List setters; + private Class clazz; + + public DefaultCsvDeserializer(Class clazz) { + this.clazz = clazz; + setters = new ArrayList<>(); + Field[] fields = clazz.getDeclaredFields(); + final Method[] methods = clazz.getMethods(); + for (Field f : fields) { + f.setAccessible(true); // Need to access private field + if (Modifier.isTransient(f.getModifiers())) // Skip Transient fields + continue; + findSetter(methods, f.getName()); + } + } + + private void findSetter(Method[] methods, String fieldName) { + for (Method m : methods) { + if (m.getName().equalsIgnoreCase("set" + fieldName)) + setters.add(m); + } + } + + @Override + public T deserialize(RecyclableCsvLine line) throws Exception { + T obj = clazz.newInstance(); + int lineId = 0; + for (Method m : setters) { + // Recognize the type of the value by the argument the setter has. + String argType = m.getParameterTypes()[0].getSimpleName(); + Object val = null; + switch (argType.toLowerCase()) { + case "string": + val = line.getString(lineId); + break; + case "date": + val = line.getString(lineId); + if (val != null) { + long timestamp = Long.parseLong(String.valueOf(val)); + val = new Date(timestamp); + } + break; + case "int": + case "integer": + val = line.getInteger(lineId); + break; + case "double": + val = line.getDouble(lineId); + break; + case "long": + val = line.getLong(lineId); + break; + case "float": + val = line.getFloat(lineId); + break; + case "short": + val = line.getShort(lineId); + break; + case "biginteger": + val = line.getBigInteger(lineId); + break; + case "bigdecimal": + val = line.getBigDecimal(lineId); + break; + default: + throw new CsvException("Cannot deserialize value for type " + argType + "."); + } + lineId++; + if (val != null) { + m.invoke(obj, val); + } + } + return obj; + } + +} diff --git a/src/main/java/me/mneri/csv/DefaultCsvSerializer.java b/src/main/java/me/mneri/csv/DefaultCsvSerializer.java new file mode 100644 index 0000000..f6c4371 --- /dev/null +++ b/src/main/java/me/mneri/csv/DefaultCsvSerializer.java @@ -0,0 +1,80 @@ +/* + * Copyright 2018 Massimo Neri + * + * This file is part of mneri/csv. + * + * 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 me.mneri.csv; + +import java.lang.reflect.Field; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * A {@link CsvSerializer} to serialize all (non transient) fields of an Object using {@link java.lang.reflect} + * package. Getter methods will be invoked in order to get the values of the fields. Transient fields will + * be ignored. + * @see DefaultCsvDeserializer + * + * @author George Zougianos github.com/gzougianos + * + * @param The type of the Java objects to serialize. + */ +public class DefaultCsvSerializer implements CsvSerializer { + private List getters; + + public DefaultCsvSerializer(Class clazz) { + getters = new ArrayList<>(); + Field[] fields = clazz.getDeclaredFields(); + final Method[] methods = clazz.getMethods(); + for (Field f : fields) { + f.setAccessible(true); // Need to access private field + if (Modifier.isTransient(f.getModifiers())) // Skip Transient fields + continue; + findGetter(methods, f.getName()); + } + } + + private void findGetter(Method[] methods, String fieldName) { + for (Method m : methods) { + if (m.getName().equalsIgnoreCase("get" + fieldName)) + getters.add(m); + } + } + + @Override + public void serialize(T object, List out) throws Exception { + for (Method getter : getters) { + Object obj = getter.invoke(object); + String value = ""; + String returnType = getter.getReturnType().getName().toString(); + if (obj != null) { + switch (returnType.toLowerCase()) { + case "java.util.date": // If it is a date, save it as timestamp + Date d = (Date) obj; + value = Long.toString(d.getTime()); + break; + default: + value = obj.toString(); + } + } + out.add(value.equalsIgnoreCase("null") ? "" : value); + } + } +} diff --git a/src/test/java/me/mneri/csv/test/MainTest.java b/src/test/java/me/mneri/csv/test/MainTest.java index 9e41484..1a3056f 100644 --- a/src/test/java/me/mneri/csv/test/MainTest.java +++ b/src/test/java/me/mneri/csv/test/MainTest.java @@ -18,15 +18,13 @@ package me.mneri.csv.test; -import me.mneri.csv.*; -import me.mneri.csv.test.model.CityPop; -import me.mneri.csv.test.model.Person; -import me.mneri.csv.test.serialization.*; -import org.junit.Assert; -import org.junit.Test; - -import java.io.*; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; import java.util.Arrays; +import java.util.Calendar; import java.util.List; import java.util.NoSuchElementException; import java.util.Objects; @@ -34,6 +32,30 @@ import java.util.stream.Stream; import java.util.zip.ZipInputStream; +import org.junit.Assert; +import org.junit.Test; + +import me.mneri.csv.CsvConversionException; +import me.mneri.csv.CsvException; +import me.mneri.csv.CsvReader; +import me.mneri.csv.CsvStreamSupport; +import me.mneri.csv.CsvWriter; +import me.mneri.csv.UncheckedCsvException; +import me.mneri.csv.UnexpectedCharacterException; +import me.mneri.csv.test.model.CityPop; +import me.mneri.csv.test.model.Person; +import me.mneri.csv.test.serialization.CityPopDeserializer; +import me.mneri.csv.test.serialization.CityPopSerializer; +import me.mneri.csv.test.serialization.ExceptionDeserializer; +import me.mneri.csv.test.serialization.ExceptionSerializer; +import me.mneri.csv.test.serialization.IntegerListDeserializer; +import me.mneri.csv.test.serialization.IntegerListSerializer; +import me.mneri.csv.test.serialization.PersonDeserializer; +import me.mneri.csv.test.serialization.PersonSerializer; +import me.mneri.csv.test.serialization.StringListDeserializer; +import me.mneri.csv.test.serialization.StringListSerializer; +import me.mneri.csv.test.serialization.VoidDeserializer; + public class MainTest { @Test(expected = CsvConversionException.class) public void conversionException1() throws CsvException, IOException { @@ -71,19 +93,29 @@ private Person createMneri() { mneri.setNickname("\"mneri\""); mneri.setAddress("Gambettola, Italy"); mneri.setWebsite("http://mneri.me"); + mneri.setAge(30); return mneri; } private Person createRms() { - Person rms = new Person(); - rms.setFirstName("Richard"); - rms.setMiddleName("Matthew"); - rms.setLastName("Stallman"); - rms.setNickname("\"rms\""); - rms.setAddress("Cambridge, Massachusetts"); - rms.setWebsite("http://stallman.org/"); - return rms; - } + Person rms = new Person(); + rms.setFirstName("Richard"); + rms.setMiddleName("Matthew"); + rms.setLastName("Stallman"); + rms.setNickname("\"rms\""); + rms.setAddress("Cambridge, Massachusetts"); + rms.setWebsite("http://stallman.org/"); + Calendar c = Calendar.getInstance(); + c.set(Calendar.MONTH, Calendar.JANUARY); + c.set(Calendar.YEAR, 1975); + c.set(Calendar.DAY_OF_MONTH, 0); + c.set(Calendar.MINUTE,0); + c.set(Calendar.HOUR_OF_DAY,0); + c.set(Calendar.SECOND,0); + c.set(Calendar.MILLISECOND,0); + rms.setBirthDate(c.getTime()); + return rms; + } private File createTempFile() throws IOException { File dir = new File(System.getProperty("java.io.tmpdir")); @@ -391,4 +423,29 @@ public void writeRead() throws CsvException, IOException { //@formatter:on } } + + @Test + public void defaultSerialize() throws IOException, CsvConversionException { + Person mneri = createMneri(); + Person rms = createRms(); + File f = getResourceFile("default.csv"); + try (CsvWriter writer = CsvWriter.open(f, Person.class)) { + writer.put(rms); + writer.put(mneri); + } + } + + @Test + public void defaultDeserialize() throws CsvException, IOException { + Person mneri = createMneri(); + Person rms = createRms(); + //Websites must be null since it is a transient field. + rms.setWebsite(null); + mneri.setWebsite(null); + File f = getResourceFile("default.csv"); + try (CsvReader reader = CsvReader.open(f, Person.class)) { + Assert.assertEquals(rms, reader.next()); + Assert.assertEquals(mneri, reader.next()); + } + } } diff --git a/src/test/java/me/mneri/csv/test/model/Person.java b/src/test/java/me/mneri/csv/test/model/Person.java index 22be6dc..8601cd5 100644 --- a/src/test/java/me/mneri/csv/test/model/Person.java +++ b/src/test/java/me/mneri/csv/test/model/Person.java @@ -28,8 +28,9 @@ public class Person implements Cloneable { private String lastName; private String middleName; private String nickname; - private String website; - + private transient String website; + private int age; + @Override public Person clone() { try { @@ -42,7 +43,7 @@ public Person clone() { clone.middleName = middleName; clone.nickname = nickname; clone.website = website; - + clone.age = age; return clone; } catch (CloneNotSupportedException ignored) { return null; @@ -62,7 +63,6 @@ public boolean equals(Object object) { if (!(object instanceof Person)) { return false; } - Person other = (Person) object; //@formatter:off @@ -72,7 +72,8 @@ public boolean equals(Object object) { Objects.equals(getLastName(), other.getLastName()) && Objects.equals(getMiddleName(), other.getMiddleName()) && Objects.equals(getNickname(), other.getNickname()) && - Objects.equals(getWebsite(), other.getWebsite()); + Objects.equals(getWebsite(), other.getWebsite()) && + Objects.equals(getAge(), other.getAge()); //@formatter:on } @@ -103,7 +104,11 @@ public String getNickname() { public String getWebsite() { return website; } - + + public int getAge() { + return age; + } + @Override public int hashCode() { //@formatter:off @@ -113,7 +118,8 @@ public int hashCode() { getLastName(), getMiddleName(), getNickname(), - getWebsite()); + getWebsite(), + getAge()); //@formatter:on } @@ -145,17 +151,21 @@ public void setWebsite(String website) { this.website = website; } + public void setAge(int age) { + this.age = age; + } @Override public String toString() { //@formatter:off - return String.format("['%s', '%s', '%s', '%s', '%s', '%s', '%s']", + return String.format("['%s', '%s', '%s', '%s', '%s', '%s', '%s','%d']", getFirstName(), getMiddleName(), getLastName(), getNickname(), getBirthDate(), getAddress(), - getWebsite()); + getWebsite(), + getAge()); //@formatter:on } } diff --git a/src/test/java/me/mneri/csv/test/serialization/PersonDeserializer.java b/src/test/java/me/mneri/csv/test/serialization/PersonDeserializer.java index 975a5a4..6df7b86 100644 --- a/src/test/java/me/mneri/csv/test/serialization/PersonDeserializer.java +++ b/src/test/java/me/mneri/csv/test/serialization/PersonDeserializer.java @@ -42,7 +42,7 @@ public Person deserialize(RecyclableCsvLine line) { person.setAddress(line.getString(5)); person.setWebsite(line.getString(6)); - + person.setAge(line.getInteger(7)); return person; } } diff --git a/src/test/java/me/mneri/csv/test/serialization/PersonSerializer.java b/src/test/java/me/mneri/csv/test/serialization/PersonSerializer.java index 53c1347..f885833 100644 --- a/src/test/java/me/mneri/csv/test/serialization/PersonSerializer.java +++ b/src/test/java/me/mneri/csv/test/serialization/PersonSerializer.java @@ -42,5 +42,6 @@ public void serialize(Person person, List out) { out.add(person.getAddress()); out.add(person.getWebsite()); + out.add(String.valueOf(person.getAge())); } } diff --git a/src/test/resources/default.csv b/src/test/resources/default.csv new file mode 100644 index 0000000..e69de29