5
\$\begingroup\$

See the previous iteration. See the next iteration.

I emphasized the fact that serialization routine may not return a string with new line character by using more or less explicit identifier. Moved the actual de/serialization routines to a factory class. That's what I have:

LineStringSerializationFactory.java:

package net.coderodde.lists.serial;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Scanner;
/**
 * This class contains static methods for serializing the lists of elements to
 * a textual representation and deserializing it back to the list of elements.
 * 
 * @author Rodion "rodde" Efremov
 * @version 1.61
 */
public class LineStringSerializationFactory {
 private LineStringSerializationFactory() {}
 /**
 * Serializes the elements in <code>list</code>. The <code>serializer</code>
 * is supposed to return a single line of text with no new line characters.
 * That string is supposed to encode the state of the serialized element. 
 * The order of encoding lines is dictated by the iteration order of the 
 * input list.
 * 
 * <b>Note:</b> this serialization procedure assumes that
 * <code>serializer</code> does not map any element to a string containing
 * a new line character as it is used for separating the element encodings.
 * 
 * @param <E> the actual type of elements to serialize.
 * @param collection the collection of elements to serialize.
 * @param serializer the serializer returning a line of text encoding the 
 * state of the input element.
 * @return a string each line of which encodes a single element.
 * 
 * @throws IllegalArgumentException if <code>serializer</code> returns a
 * string containing a new line character.
 */
 public static <E> String serialize(Collection<E> collection, 
 LineStringSerializer<E> serializer) {
 StringBuilder sb = new StringBuilder();
 for (E element : collection) {
 String line = serializer.serialize(element);
 if (line.contains("\n")) {
 throw new IllegalArgumentException(
 "The line serializer may not return the new line " +
 "character in its output.");
 }
 sb.append(line).append("\n");
 }
 return sb.toString();
 }
 /**
 * Deserializes the list from the input text <code>text</code>. Each line 
 * is expected to produce exactly one element.
 * 
 * @param <E> the actual deserialized element type.
 * @param text the entire string holding the encoding of the entire
 * list.
 * @param deserializer the deserializer converting each input line to an
 * element whose state is encoded by that line.
 * @return the list of elements encoded by <code>text</code> in
 * the same order as their respective encoding lines.
 */
 public static <E> List<E> 
 deserialize(String text, LineStringDeserializer<E> deserializer) {
 List<E> ret = new ArrayList<>();
 Scanner scanner = new Scanner(text);
 while (scanner.hasNextLine()) {
 ret.add(deserializer.deserialize(scanner.nextLine()));
 }
 scanner.close();
 return ret;
 }
}

LineStringSerializer.java:

package net.coderodde.lists.serial;
/**
 * This interface defines the API for serializing an object to a string.
 * 
 * @author Rodion "rodde" Efremov
 * @version 1.61
 * @param <E> the object type.
 */
@FunctionalInterface
public interface LineStringSerializer<E> {
 /**
 * Returns the textual representation of the input object.
 * 
 * @param object the object to serialize.
 * @return the textual representation of the input object.
 */
 public String serialize(E object);
}

LineStringDeserializer.java:

package net.coderodde.lists.serial;
/**
 * This interface defines the API for deserializing the objects from their 
 * textual representation.
 * 
 * @author Rodion "rodde" Efremov
 * @version 1.61
 * @param <E> the object type.
 */
@FunctionalInterface
public interface LineStringDeserializer<E> {
 /**
 * Deserializes an object from its textual representation.
 * 
 * @param text the string representing the state of the object.
 * @return the actual, deserialized object.
 */
 public E deserialize(String text);
}

LineStringSerializationFactoryTest.java:

package net.coderodde.lists.serial;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
import org.junit.Test;
import static org.junit.Assert.*;
import org.junit.BeforeClass;
public class LineStringSerializationFactoryTest {
 private static Random random;
 @BeforeClass
 public static void init() {
 long seed = System.currentTimeMillis();
 System.out.println("Seed: " + seed);
 random = new Random(seed);
 }
 @Test
 public void testSimpleIntegers() {
 List<Integer> input = getRandomIntegerList(1000, random);
 String text = LineStringSerializationFactory
 .serialize(input, Object::toString);
 List<Integer> output = LineStringSerializationFactory
 .deserialize(text, Integer::parseInt);
 assertTrue(input.equals(output));
 }
 /**
 * janos, yes we can serialize a list of lists.
 */
 @Test
 public void testListOfLists() {
 List<List<Integer>> input = getRandomListOfLists(1000, random);
 String text = LineStringSerializationFactory
 .serialize(input, Object::toString);
 List<List<Integer>> output = 
 LineStringSerializationFactory
 .deserialize(text, new IntListDeserializer());
 assertTrue(input.equals(output));
 }
 /**
 * Constructs a random integer array.
 * 
 * @param size the length of the integer array.
 * @param random the random number generator.
 * @return the integer array.
 */
 private static List<Integer> 
 getRandomIntegerList(int size, Random random) {
 return random.ints(size).boxed().collect(Collectors.toList());
 }
 /**
 * Constructs a random list of integer lists.
 * 
 * @param size the length of the outer list.
 * @param random the random number generator.
 * @return the random list of integer lists.
 */
 private static List<List<Integer>> 
 getRandomListOfLists(int size, Random random) {
 List<List<Integer>> ret = new ArrayList<>(size);
 for (int i = 0; i < size; ++i) {
 ret.add(getRandomIntegerList(random.nextInt(50), random));
 }
 return ret;
 }
 private static class IntListDeserializer 
 implements LineStringDeserializer<List<Integer>> {
 @Override
 public List<Integer> deserialize(String text) {
 List<Integer> ret = new ArrayList<>();
 String[] intStrings = text.substring(1, text.length() - 1)
 .split(",");
 for (String s : intStrings) {
 // 'trim' in order to get rid of any leading/trailing 
 // white space.
 s = s.trim();
 if (!s.equals("")) {
 ret.add(Integer.parseInt(s.trim()));
 }
 }
 return ret; 
 }
 }
}

So, what do you think?

asked Jun 22, 2015 at 18:38
\$\endgroup\$
3
  • 1
    \$\begingroup\$ I like your JavaDoc. \$\endgroup\$ Commented Jun 22, 2015 at 18:43
  • \$\begingroup\$ Is this on GitHub? If not, could you put it there? \$\endgroup\$ Commented Jun 22, 2015 at 20:27
  • \$\begingroup\$ @QPaysTaxes Thanks for pointing that out. File fixed. Also, I pushed it to GitHub at github.com/coderodde/TextSerialization \$\endgroup\$ Commented Jun 24, 2015 at 7:18

1 Answer 1

1
\$\begingroup\$
/**
 * Serializes the elements in <code>list</code>. The <code>serializer</code>
 * is supposed to return a single line of text with no new line characters.
 * ...
 */

As of JDK 5, you can use the @code Javadoc literal to do the equivalent of <code>...</code> too. :)

Re-written:

/**
 * Serializes the elements in {@code list}. The {@code serializer}
 * is supposed to return a single line of text with no new line characters.
 * ...
 */
if (line.contains("\n")) {
 ...
}
sb.append(line).append("\n");

You can probably externalize "\n" as a private static final field. I was about to suggest append()-ing the single-character '\n' instead, but let's stick with consistency when you have that.

/**
 * janos, yes we can serialize a list of lists.
 */

(削除) Redundant (削除ここまで) (削除) Incorrect (削除ここまで) Unnecessary Javadoc...? :p

There's a potential problem for IntListDeserializer.

String[] intStrings = text.substring(1, text.length() - 1).split(",");

This particular way of parsing text is entirely dependent on the toString() representation of a List implementation, which is not always guaranteed to be in this format, much less for all List implementations. In other words, this may be prone to breakage and you'll probably have to revisit if you want to include any form of validation.

Ok, so let's just assume this suffice for now, and review the next part:

for (String s : intStrings) {
 // 'trim' in order to get rid of any leading/trailing 
 // white space.
 s = s.trim();
 if (!s.equals("")) {
 ret.add(Integer.parseInt(s.trim()));
 }
}

The second trim() is redundant, and s.equals("") can be replaced by s.isEmpty(). A Stream-based way of doing this will be:

return Stream.of(intStrings).map(String::trim).filter(s -> !s.isEmpty())
 .map(Integer::parseInt).collect(Collectors.toList());

Alternatively, if you want a way to skip invalid inputs and just return the numbers, you'll need a simple method wrapper over Integer.parseInt():

public static Integer asInteger(String value) {
 try {
 return Integer.parseInt(value);
 } catch (NumberFormatException e) {
 return null;
 }
}

And then modify the stream operations slightly:

return Stream.of(intStrings).map(String::trim).filter(s -> !s.isEmpty())
 .map(IntListDeserializer::asInteger).filter(Objects::nonNull)
 .collect(Collectors.toList());
answered Jun 23, 2015 at 0:46
\$\endgroup\$
3
  • \$\begingroup\$ Will the last snippet (return Stream.of...) omit any string that cannot be parsed as an integer? \$\endgroup\$ Commented Jun 24, 2015 at 9:11
  • \$\begingroup\$ @coderodde If you use the 'guard' method asInteger(), then it will omit by virtue of filtering away null values (which are returned as a result of NumberFormatException). \$\endgroup\$ Commented Jun 24, 2015 at 9:12
  • \$\begingroup\$ Yes, cool! That what I did in the next iteration. \$\endgroup\$ Commented Jun 24, 2015 at 9:14

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.