Реализовать довольно-печать в табличной форме с использованием Java 8 API для трансляций


Около шести лет назад я реализовал простой табличной довольно-принт классов Java , что в основном моделируется в MySQL командной строки результата запроса таблицы. Мне не очень нравится, потому что это было реализовано в, Я считаю, довольно грязным способом, не расширяемая, и я предполагаю, что то же самое может быть реализовано с помощью Java 8 API в поток, который может быть немного более универсальным. Следующая реализация-это лишь вопрос реализации интересов и я пытаюсь учиться писать SpliteratorС.

Исходный код:

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 = '|';
        private static final char HORIZONTAL = '-';
        private static final char EMPTY = ' ';

        private enum State {

            BEFORE_ROW,
            AT_ROW,
            NO_MORE_ROWS

        }

        private final int estimatedSize;
        private final Iterator<String[]> rowsIterator;
        private final int[] maxColumnWidths;

        private State state;

        @Nullable
        private String border;

        private FormattedRowSpliterator(final int estimatedSize, final Iterator<String[]> rowsIterator, final int[] maxColumnWidths) {
            this.estimatedSize = estimatedSize;
            this.rowsIterator = rowsIterator;
            this.maxColumnWidths = maxColumnWidths;
            state = rowsIterator.hasNext() ? State.BEFORE_ROW : State.NO_MORE_ROWS;
        }

        private static Spliterator<String> get(final Collection<String[]> collection, final int[] maxColumnWidths) {
            final Iterator<String[]> iterator = collection.iterator();
            return new FormattedRowSpliterator(collection.size() * 2 + 1, iterator, maxColumnWidths);
        }

        @Override
        public boolean tryAdvance(final Consumer<? super String> action) {
            switch ( state ) {
            case BEFORE_ROW:
                renderBorder(action);
                state = State.AT_ROW;
                return true;
            case AT_ROW:
                final String[] values = rowsIterator.next();
                renderRow(values, action);
                state = rowsIterator.hasNext() ? State.BEFORE_ROW : State.NO_MORE_ROWS;
                return true;
            case NO_MORE_ROWS:
                renderBorder(action);
                return false;
            default:
                throw new AssertionError(state);
            }
        }

        @Override
        @Nullable
        public Spliterator<String> trySplit() {
            return null;
        }

        @Override
        public long estimateSize() {
            return estimatedSize;
        }

        @Override
        public int characteristics() {
            return Spliterator.IMMUTABLE | Spliterator.NONNULL /*| Spliterator.ORDERED*/ | Spliterator.SIZED;
        }

        private void renderBorder(final Consumer<? super String> action) {
            if ( border == null ) {
                final StringBuilder borderBuilder = new StringBuilder();
                borderBuilder.append(KNOT);
                for ( final int columnMaxLength : maxColumnWidths ) {
                    for ( int i = 0; i < columnMaxLength; i++ ) {
                        borderBuilder.append(HORIZONTAL);
                    }
                    borderBuilder.append(KNOT);
                }
                border = borderBuilder.toString();
            }
            action.accept(border);
        }

        private void renderRow(final String[] values, final Consumer<? super String> action) {
            assert border != null;
            final StringBuilder rowBuilder = new StringBuilder(border.length());
            renderRowValues(values, rowBuilder);
            renderRowMissingValues(rowBuilder, values.length);
            final String row = rowBuilder.toString();
            action.accept(row);
        }

        private void renderRowValues(final String[] values, final StringBuilder rowBuilder) {
            rowBuilder.append(VERTICAL);
            for ( int i = 0; i < values.length; i++ ) {
                final String value = values[i];
                rowBuilder.append(value);
                final int valueLength = value.length();
                for ( int j = valueLength; j < maxColumnWidths[i]; j++ ) {
                    rowBuilder.append(EMPTY);
                }
                rowBuilder.append(VERTICAL);
            }
        }

        private void renderRowMissingValues(final StringBuilder rowBuilder, final int valueCount) {
            if ( valueCount == 0 ) {
                rowBuilder.append(VERTICAL);
            }
            for ( int i = valueCount; i < maxColumnWidths.length; i++ ) {
                for ( int j = 0; j < maxColumnWidths[i]; j++ ) {
                    rowBuilder.append(EMPTY);
                }
                rowBuilder.append(VERTICAL);
            }
        }

    }

}

Пример использования:

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;
}

Пример результата:

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

Известные ограничения:

  • Реализация не заниматься nullС дизайном (и я считаю, что это нормально, однако приведенный выше код не проверяет входные nullS и может бросить "непонятных" исключения).
  • Эта реализация не является разработан, чтобы быть расширяемым (например, его дизайн не совсем подходит для CSV потоков с текущей табличному представлению должна просмотреть всю таблицу заранее, в то время как CSV не требуют и могут трансформировать потоки/визуализация бесконечного данные).

Мои опасения, по крайней мере:

  1. Эта реализация делает отформатированные строки и таблица границ как целых строк. Наверное отрисовки каждой ячейки в отдельности может быть лучше или более оптимальная идея, но я не знаю, как это повлияет на \n характер затем.
  2. Я не знаю, насколько хорошо это реализовано как spliterator, особенно его characteristics() метод.
  3. Я не уверен на 100%, если динамическое расширение maxColumnWidths массив-это хорошая идея. Например, я бы предположил, что коллекционер должен только ожидать данных и обнаружения неровными рядами в основном из области коллектора (попросту говоря, это, наверное, должно всегда ожидать, что для н х м матриц).
  4. Ну, коллекционер, который создает поток.

Любые поправки и предложения приветствуются. Спасибо.



185
3
задан 2 марта 2018 в 12:03 Источник Поделиться
Комментарии