Implementing a tabular pretty-print using Java 8 Stream API

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
3
down vote

favorite












About six years ago I implemented a simple tabular pretty-print Java class that mostly simulated MySQL CLI query result tables.
I don't really like it because it was implemented in a, I believe, pretty dirty manner being not extensible, and I would assume that the same thing might be implemented using Java 8 Stream API that might be a bit more generic.
The following implementation is just a matter of reimplementation interest and I'm trying to learn writing some Spliterators.



The source code:



public final class Table 

private Table()


public static Collector<String, ?, Stream<String>> toTable()
return Collector.of(
State::new,
State::addRow,
State::mergeState,
State::stream
);


public static Collector<String, ?, Stream<String>> toTable(final String... columnHeaders)
return Collector.of(
() ->
final State state = new State();
state.addRow(columnHeaders);
return state;
,
State::addRow,
State::mergeState,
State::stream
);


private static final class State

private static final int emptyIntArray = ;

private final Collection<String> rows = new ArrayList<>();

private int maxColumnWidths = emptyIntArray;

private State()


private void addRow(final String... values)
ensureColumnMaxLengths(values);
recalculateWidths(values);
rows.add(values);


private State mergeState(final State stateR)
for ( final String row : stateR.rows )
addRow(row);

return this;


private void ensureColumnMaxLengths(final String... values)
if ( maxColumnWidths.length < values.length )
final int newWidths = new int[values.length];
System.arraycopy(maxColumnWidths, 0, newWidths, 0, maxColumnWidths.length);
maxColumnWidths = newWidths;



private void recalculateWidths(final String... values)
final int count = values.length;
for ( int i = 0; i < count; i++ )
final String value = values[i];
final int valueLength = value.length();
if ( valueLength > maxColumnWidths[i] )
maxColumnWidths[i] = valueLength;




private Stream<String> stream()
return StreamSupport.stream(FormattedRowSpliterator.get(rows, maxColumnWidths), false);





private static final class FormattedRowSpliterator
implements Spliterator<String>

private static final char KNOT = '+';
private static final char VERTICAL = '




Example of use:



public static void main(final String... args) 
Stream.generate(() -> new int random(), random(), random() )
.limit(5)
.map(row -> new String Integer.toString(row[0]), Integer.toString(row[1]), Integer.toString(row[2]) )
.collect(Table.toTable("value 1"))
.forEach(System.out::println);


private static int random()
final int digit = (int) (Math.random() * 10);
return (int) Math.pow(10, digit - 1) * digit;



Example result:



+-------+---------+--------+
|value 1|value 2 |value 3 |
+-------+---------+--------+
|300 |900000000|50000 |
+-------+---------+--------+
|600000 |900000000|80000000|
+-------+---------+--------+
|20 |900000000|80000000|
+-------+---------+--------+
|4000 |7000000 |4000 |
+-------+---------+--------+
|1 |20 |80000000|
+-------+---------+--------+


Known limitations:



  • The implementation does not deal with nulls by design (and I do believe this is fine, however the code above does not check for input nulls and may throw "obscure" exceptions).

  • This implementation is not designed to be extensible (for example, its design is not really suitable for CSV streams since the current tabular representation must scan the whole table in advance whereas CSV does not require it and may transform/render infinite data streams).

My concerns at least are:



  1. This implementation renders the formatted strings and table borders as whole strings. Probably rendering the each cell separately might be a better or more optimal idea, but I don't know how it would affect the n character then.

  2. I don't know how fine is the implemented spliterator, especially its characteristics() method.

  3. I'm not 100% sure if dynamic expanding the maxColumnWidths array is a good idea. For example, I would assume that the collector must only expect well-formed data, and detecting jagged rows is basically out of the scope of the collector (simply speaking, it probably should always expect for n x m matrices).

  4. Well, a collector that produces a stream.

Any corrections and suggestions are welcome. Thank you.







share|improve this question

























    up vote
    3
    down vote

    favorite












    About six years ago I implemented a simple tabular pretty-print Java class that mostly simulated MySQL CLI query result tables.
    I don't really like it because it was implemented in a, I believe, pretty dirty manner being not extensible, and I would assume that the same thing might be implemented using Java 8 Stream API that might be a bit more generic.
    The following implementation is just a matter of reimplementation interest and I'm trying to learn writing some Spliterators.



    The source code:



    public final class Table 

    private Table()


    public static Collector<String, ?, Stream<String>> toTable()
    return Collector.of(
    State::new,
    State::addRow,
    State::mergeState,
    State::stream
    );


    public static Collector<String, ?, Stream<String>> toTable(final String... columnHeaders)
    return Collector.of(
    () ->
    final State state = new State();
    state.addRow(columnHeaders);
    return state;
    ,
    State::addRow,
    State::mergeState,
    State::stream
    );


    private static final class State

    private static final int emptyIntArray = ;

    private final Collection<String> rows = new ArrayList<>();

    private int maxColumnWidths = emptyIntArray;

    private State()


    private void addRow(final String... values)
    ensureColumnMaxLengths(values);
    recalculateWidths(values);
    rows.add(values);


    private State mergeState(final State stateR)
    for ( final String row : stateR.rows )
    addRow(row);

    return this;


    private void ensureColumnMaxLengths(final String... values)
    if ( maxColumnWidths.length < values.length )
    final int newWidths = new int[values.length];
    System.arraycopy(maxColumnWidths, 0, newWidths, 0, maxColumnWidths.length);
    maxColumnWidths = newWidths;



    private void recalculateWidths(final String... values)
    final int count = values.length;
    for ( int i = 0; i < count; i++ )
    final String value = values[i];
    final int valueLength = value.length();
    if ( valueLength > maxColumnWidths[i] )
    maxColumnWidths[i] = valueLength;




    private Stream<String> stream()
    return StreamSupport.stream(FormattedRowSpliterator.get(rows, maxColumnWidths), false);





    private static final class FormattedRowSpliterator
    implements Spliterator<String>

    private static final char KNOT = '+';
    private static final char VERTICAL = '




    Example of use:



    public static void main(final String... args) 
    Stream.generate(() -> new int random(), random(), random() )
    .limit(5)
    .map(row -> new String Integer.toString(row[0]), Integer.toString(row[1]), Integer.toString(row[2]) )
    .collect(Table.toTable("value 1"))
    .forEach(System.out::println);


    private static int random()
    final int digit = (int) (Math.random() * 10);
    return (int) Math.pow(10, digit - 1) * digit;



    Example result:



    +-------+---------+--------+
    |value 1|value 2 |value 3 |
    +-------+---------+--------+
    |300 |900000000|50000 |
    +-------+---------+--------+
    |600000 |900000000|80000000|
    +-------+---------+--------+
    |20 |900000000|80000000|
    +-------+---------+--------+
    |4000 |7000000 |4000 |
    +-------+---------+--------+
    |1 |20 |80000000|
    +-------+---------+--------+


    Known limitations:



    • The implementation does not deal with nulls by design (and I do believe this is fine, however the code above does not check for input nulls and may throw "obscure" exceptions).

    • This implementation is not designed to be extensible (for example, its design is not really suitable for CSV streams since the current tabular representation must scan the whole table in advance whereas CSV does not require it and may transform/render infinite data streams).

    My concerns at least are:



    1. This implementation renders the formatted strings and table borders as whole strings. Probably rendering the each cell separately might be a better or more optimal idea, but I don't know how it would affect the n character then.

    2. I don't know how fine is the implemented spliterator, especially its characteristics() method.

    3. I'm not 100% sure if dynamic expanding the maxColumnWidths array is a good idea. For example, I would assume that the collector must only expect well-formed data, and detecting jagged rows is basically out of the scope of the collector (simply speaking, it probably should always expect for n x m matrices).

    4. Well, a collector that produces a stream.

    Any corrections and suggestions are welcome. Thank you.







    share|improve this question





















      up vote
      3
      down vote

      favorite









      up vote
      3
      down vote

      favorite











      About six years ago I implemented a simple tabular pretty-print Java class that mostly simulated MySQL CLI query result tables.
      I don't really like it because it was implemented in a, I believe, pretty dirty manner being not extensible, and I would assume that the same thing might be implemented using Java 8 Stream API that might be a bit more generic.
      The following implementation is just a matter of reimplementation interest and I'm trying to learn writing some Spliterators.



      The source code:



      public final class Table 

      private Table()


      public static Collector<String, ?, Stream<String>> toTable()
      return Collector.of(
      State::new,
      State::addRow,
      State::mergeState,
      State::stream
      );


      public static Collector<String, ?, Stream<String>> toTable(final String... columnHeaders)
      return Collector.of(
      () ->
      final State state = new State();
      state.addRow(columnHeaders);
      return state;
      ,
      State::addRow,
      State::mergeState,
      State::stream
      );


      private static final class State

      private static final int emptyIntArray = ;

      private final Collection<String> rows = new ArrayList<>();

      private int maxColumnWidths = emptyIntArray;

      private State()


      private void addRow(final String... values)
      ensureColumnMaxLengths(values);
      recalculateWidths(values);
      rows.add(values);


      private State mergeState(final State stateR)
      for ( final String row : stateR.rows )
      addRow(row);

      return this;


      private void ensureColumnMaxLengths(final String... values)
      if ( maxColumnWidths.length < values.length )
      final int newWidths = new int[values.length];
      System.arraycopy(maxColumnWidths, 0, newWidths, 0, maxColumnWidths.length);
      maxColumnWidths = newWidths;



      private void recalculateWidths(final String... values)
      final int count = values.length;
      for ( int i = 0; i < count; i++ )
      final String value = values[i];
      final int valueLength = value.length();
      if ( valueLength > maxColumnWidths[i] )
      maxColumnWidths[i] = valueLength;




      private Stream<String> stream()
      return StreamSupport.stream(FormattedRowSpliterator.get(rows, maxColumnWidths), false);





      private static final class FormattedRowSpliterator
      implements Spliterator<String>

      private static final char KNOT = '+';
      private static final char VERTICAL = '




      Example of use:



      public static void main(final String... args) 
      Stream.generate(() -> new int random(), random(), random() )
      .limit(5)
      .map(row -> new String Integer.toString(row[0]), Integer.toString(row[1]), Integer.toString(row[2]) )
      .collect(Table.toTable("value 1"))
      .forEach(System.out::println);


      private static int random()
      final int digit = (int) (Math.random() * 10);
      return (int) Math.pow(10, digit - 1) * digit;



      Example result:



      +-------+---------+--------+
      |value 1|value 2 |value 3 |
      +-------+---------+--------+
      |300 |900000000|50000 |
      +-------+---------+--------+
      |600000 |900000000|80000000|
      +-------+---------+--------+
      |20 |900000000|80000000|
      +-------+---------+--------+
      |4000 |7000000 |4000 |
      +-------+---------+--------+
      |1 |20 |80000000|
      +-------+---------+--------+


      Known limitations:



      • The implementation does not deal with nulls by design (and I do believe this is fine, however the code above does not check for input nulls and may throw "obscure" exceptions).

      • This implementation is not designed to be extensible (for example, its design is not really suitable for CSV streams since the current tabular representation must scan the whole table in advance whereas CSV does not require it and may transform/render infinite data streams).

      My concerns at least are:



      1. This implementation renders the formatted strings and table borders as whole strings. Probably rendering the each cell separately might be a better or more optimal idea, but I don't know how it would affect the n character then.

      2. I don't know how fine is the implemented spliterator, especially its characteristics() method.

      3. I'm not 100% sure if dynamic expanding the maxColumnWidths array is a good idea. For example, I would assume that the collector must only expect well-formed data, and detecting jagged rows is basically out of the scope of the collector (simply speaking, it probably should always expect for n x m matrices).

      4. Well, a collector that produces a stream.

      Any corrections and suggestions are welcome. Thank you.







      share|improve this question











      About six years ago I implemented a simple tabular pretty-print Java class that mostly simulated MySQL CLI query result tables.
      I don't really like it because it was implemented in a, I believe, pretty dirty manner being not extensible, and I would assume that the same thing might be implemented using Java 8 Stream API that might be a bit more generic.
      The following implementation is just a matter of reimplementation interest and I'm trying to learn writing some Spliterators.



      The source code:



      public final class Table 

      private Table()


      public static Collector<String, ?, Stream<String>> toTable()
      return Collector.of(
      State::new,
      State::addRow,
      State::mergeState,
      State::stream
      );


      public static Collector<String, ?, Stream<String>> toTable(final String... columnHeaders)
      return Collector.of(
      () ->
      final State state = new State();
      state.addRow(columnHeaders);
      return state;
      ,
      State::addRow,
      State::mergeState,
      State::stream
      );


      private static final class State

      private static final int emptyIntArray = ;

      private final Collection<String> rows = new ArrayList<>();

      private int maxColumnWidths = emptyIntArray;

      private State()


      private void addRow(final String... values)
      ensureColumnMaxLengths(values);
      recalculateWidths(values);
      rows.add(values);


      private State mergeState(final State stateR)
      for ( final String row : stateR.rows )
      addRow(row);

      return this;


      private void ensureColumnMaxLengths(final String... values)
      if ( maxColumnWidths.length < values.length )
      final int newWidths = new int[values.length];
      System.arraycopy(maxColumnWidths, 0, newWidths, 0, maxColumnWidths.length);
      maxColumnWidths = newWidths;



      private void recalculateWidths(final String... values)
      final int count = values.length;
      for ( int i = 0; i < count; i++ )
      final String value = values[i];
      final int valueLength = value.length();
      if ( valueLength > maxColumnWidths[i] )
      maxColumnWidths[i] = valueLength;




      private Stream<String> stream()
      return StreamSupport.stream(FormattedRowSpliterator.get(rows, maxColumnWidths), false);





      private static final class FormattedRowSpliterator
      implements Spliterator<String>

      private static final char KNOT = '+';
      private static final char VERTICAL = '




      Example of use:



      public static void main(final String... args) 
      Stream.generate(() -> new int random(), random(), random() )
      .limit(5)
      .map(row -> new String Integer.toString(row[0]), Integer.toString(row[1]), Integer.toString(row[2]) )
      .collect(Table.toTable("value 1"))
      .forEach(System.out::println);


      private static int random()
      final int digit = (int) (Math.random() * 10);
      return (int) Math.pow(10, digit - 1) * digit;



      Example result:



      +-------+---------+--------+
      |value 1|value 2 |value 3 |
      +-------+---------+--------+
      |300 |900000000|50000 |
      +-------+---------+--------+
      |600000 |900000000|80000000|
      +-------+---------+--------+
      |20 |900000000|80000000|
      +-------+---------+--------+
      |4000 |7000000 |4000 |
      +-------+---------+--------+
      |1 |20 |80000000|
      +-------+---------+--------+


      Known limitations:



      • The implementation does not deal with nulls by design (and I do believe this is fine, however the code above does not check for input nulls and may throw "obscure" exceptions).

      • This implementation is not designed to be extensible (for example, its design is not really suitable for CSV streams since the current tabular representation must scan the whole table in advance whereas CSV does not require it and may transform/render infinite data streams).

      My concerns at least are:



      1. This implementation renders the formatted strings and table borders as whole strings. Probably rendering the each cell separately might be a better or more optimal idea, but I don't know how it would affect the n character then.

      2. I don't know how fine is the implemented spliterator, especially its characteristics() method.

      3. I'm not 100% sure if dynamic expanding the maxColumnWidths array is a good idea. For example, I would assume that the collector must only expect well-formed data, and detecting jagged rows is basically out of the scope of the collector (simply speaking, it probably should always expect for n x m matrices).

      4. Well, a collector that produces a stream.

      Any corrections and suggestions are welcome. Thank you.









      share|improve this question










      share|improve this question




      share|improve this question









      asked Mar 2 at 12:19









      Lyubomyr Shaydariv

      23117




      23117

























          active

          oldest

          votes











          Your Answer




          StackExchange.ifUsing("editor", function ()
          return StackExchange.using("mathjaxEditing", function ()
          StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
          StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
          );
          );
          , "mathjax-editing");

          StackExchange.ifUsing("editor", function ()
          StackExchange.using("externalEditor", function ()
          StackExchange.using("snippets", function ()
          StackExchange.snippets.init();
          );
          );
          , "code-snippets");

          StackExchange.ready(function()
          var channelOptions =
          tags: "".split(" "),
          id: "196"
          ;
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function()
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled)
          StackExchange.using("snippets", function()
          createEditor();
          );

          else
          createEditor();

          );

          function createEditor()
          StackExchange.prepareEditor(
          heartbeatType: 'answer',
          convertImagesToLinks: false,
          noModals: false,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: null,
          bindNavPrevention: true,
          postfix: "",
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          );



          );








           

          draft saved


          draft discarded


















          StackExchange.ready(
          function ()
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f188673%2fimplementing-a-tabular-pretty-print-using-java-8-stream-api%23new-answer', 'question_page');

          );

          Post as a guest



































          active

          oldest

          votes













          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes










           

          draft saved


          draft discarded


























           


          draft saved


          draft discarded














          StackExchange.ready(
          function ()
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f188673%2fimplementing-a-tabular-pretty-print-using-java-8-stream-api%23new-answer', 'question_page');

          );

          Post as a guest













































































          Popular posts from this blog

          Greedy Best First Search implementation in Rust

          Function to Return a JSON Like Objects Using VBA Collections and Arrays

          C++11 CLH Lock Implementation