Take the 2-minute tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

See the previous iteration. I have incorporated the answer of h.j.k.. Now 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 {

    /**
     * The alias for the new line string.
     */
    private static final String ENDL = "\n";

    // Do not instantiate this class.
    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(ENDL)) {
                throw new IllegalArgumentException(
                        "The line serializer may not return the new line " +
                        "character in its output.");
            }

            sb.append(line).append(ENDL);
        }

        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.Objects;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.stream.Stream;
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));
    }

    @Test
    public void testListOfLists() {
        // Create data to serialize.
        List<List<Integer>> input = getRandomListOfLists(1000, random);
        // Serialize it.
        String text = LineStringSerializationFactory
                      .serialize(input, new IntListSerializer());
        // Deserialize it.
        List<List<Integer>> output = 
                LineStringSerializationFactory
                .deserialize(text, new IntListDeserializer());
        // Compare.
        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 IntListSerializer
    implements LineStringSerializer<List<Integer>> {

        @Override
        public String serialize(List<Integer> list) {
            StringBuilder sb = new StringBuilder();
            int index = 0;

            for (Integer i : list) {
                sb.append(i.toString());

                if (index < list.size() - 1) {
                    sb.append(",");
                }
            }

            return sb.toString();
        }
    }

    private static class IntListDeserializer 
    implements LineStringDeserializer<List<Integer>> {

        @Override
        public List<Integer> deserialize(String text) {
            return Stream.of(text.split(","))
                         .map(String::trim)
                         .filter(s -> !s.isEmpty())
                         .map(LineStringSerializationFactoryTest::asInteger)
                         .filter(Objects::nonNull)
                         .collect(Collectors.toList());
        }
    }

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

So, what do you think?

share|improve this question

1 Answer 1

up vote 1 down vote accepted

I'm suggesting this serialize-method:

return input

        // convert items to strings
        .map(serializer::serialize)

        // check for illegal values
        .peek(line -> {
            if (line.contains(ENDL)) {
                throw new IllegalArgumentException(
                        "The line serializer may not return the new line " +
                        "character in its output.");
            }
        })

        // join them together to one text
        .collect(Collectors.joining(ENDL));

And this deserialize-method:

// split text at line breaks
String[] elements = text.split(ENDL);

return Arrays.stream(elements)

        // turn every item into a java object
        .map(deserializer::deserialize);

My changes:

  • Use Streams in method signature (both parameter and return value)
    • this hinders other code to expose encapsulated Collections
  • Use Stream syntax instead of old foreach loop
    • this will make your code shorter and therefore better readable
  • Use split instead of a Scanner
    • this will make your code shorter and more focused on functionality than on technical implementation

The complete factory class will look like this:

package net.coderodde.lists.serial;

import java.util.Arrays;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 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 {

    /**
     * The alias for the new line string.
     */
    private static final String ENDL = "\n";

    // Do not instantiate this class.
    private LineStringSerializationFactory() {}

    /**
     * Serializes the elements in <code>input</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 stream.
     * 
     * <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 stream 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(Stream<E> input, 
            LineStringSerializer<E> serializer) {
        return input

                // convert items to strings
                .map(serializer::serialize)

                // check for illegal values
                .peek(line -> {
                    if (line.contains(ENDL)) {
                        throw new IllegalArgumentException(
                                "The line serializer may not return the new line " +
                                "character in its output.");
                    }
                })

                // join them together to one text
                .collect(Collectors.joining(ENDL));
    }

    /**
     * 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 stream of elements encoded by <code>text</code> in
     *                      the same order as their respective encoding lines.
     */
    public static <E> Stream<E> 
    deserialize(String text, LineStringDeserializer<E> deserializer) {

        // split text at line breaks
        String[] elements = text.split(ENDL);

        return Arrays.stream(elements)

                // turn every item into a java object
                .map(deserializer::deserialize);
    }
}

For the test class you will need only minor changes. For completness - here is the code:

package net.coderodde.lists.serial;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.stream.Stream;
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.stream(), Object::toString);
        List<Integer> output = LineStringSerializationFactory
                               .deserialize(text, Integer::parseInt).collect(Collectors.toList());
        assertTrue(input.equals(output));
    }

    @Test
    public void testListOfLists() {
        // Create data to serialize.
        List<List<Integer>> input = getRandomListOfLists(1000, random);
        // Serialize it.
        String text = LineStringSerializationFactory
                      .serialize(input.stream(), new IntListSerializer());
        // Deserialize it.
        List<List<Integer>> output = 
                LineStringSerializationFactory
                .deserialize(text, new IntListDeserializer()).collect(Collectors.toList());
        // Compare.
        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 IntListSerializer
    implements LineStringSerializer<List<Integer>> {

        @Override
        public String serialize(List<Integer> list) {
            StringBuilder sb = new StringBuilder();
            int index = 0;

            for (Integer i : list) {
                sb.append(i.toString());

                if (index < list.size() - 1) {
                    sb.append(",");
                }
            }

            return sb.toString();
        }
    }

    private static class IntListDeserializer 
    implements LineStringDeserializer<List<Integer>> {

        @Override
        public List<Integer> deserialize(String text) {
            return Stream.of(text.split(","))
                         .map(String::trim)
                         .filter(s -> !s.isEmpty())
                         .map(LineStringSerializationFactoryTest::asInteger)
                         .filter(Objects::nonNull)
                         .collect(Collectors.toList());
        }
    }

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

No changes required on both interfaces.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.