org.shaf.core.util.TextMatrix.java Source code

Java tutorial

Introduction

Here is the source code for org.shaf.core.util.TextMatrix.java

Source

/**
 * Copyright 2014-2015 SHAF-WORK
 * 
 * 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 org.shaf.core.util;

import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.TreeMap;

import org.shaf.core.util.TextMatrix.TextCell;

import com.google.common.base.Objects;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Iterators;
import com.google.common.collect.Table;

/**
 * The text matrix.
 * 
 * @author Mykola Galushka
 */
public class TextMatrix implements Iterable<TextCell> {

    /**
     * Creates a new text matrix from the specified {@link Table table}.
     * 
     * <p>
     * <b>Usage example:</b>
     * 
     * <pre>
     *      d   e   f                         0   1   2
     *    +---+---+---+                     +---+---+---+
     *  a |   | A |   |                   0 |   | A |   |
     *    +---+---+---+                     +---+---+---+
     *  b | B |   | C |  transforming to  1 | B |   | C | 
     *    +---+---+---+                     +---+---+---+
     *  c |   | D |   |                   2 |   | D |   |
     *    +---+---+---+                     +---+---+---+
     * 
     * Table&lt;String, String, String&gt; table = HashBasedTable.create();
     * table.put(&quot;a&quot;, &quot;e&quot;, &quot;A&quot;);
     * table.put(&quot;b&quot;, &quot;d&quot;, &quot;B&quot;);
     * table.put(&quot;b&quot;, &quot;f&quot;, &quot;C&quot;);
     * table.put(&quot;c&quot;, &quot;e&quot;, &quot;D&quot;);
     * System.out.println(&quot;table=&quot; + table);
     * 
     * TextMatrix matrix = TextMatrix.&lt;String, String, String&gt; create(table);
     * System.out.println(&quot;matrix=&quot; + matrix);
     * </pre>
     * 
     * </p>
     * 
     * <p>
     * <b>Output:</b>
     * 
     * <pre>
     * table={b={f=C, d=B}, c={e=D}, a={e=A}}
     * matrix=TextMatrix{[TextCell{(0, 1)=A}, TextCell{(1, 0)=B}, TextCell{(1, 2)=C}, TextCell{(2, 1)=D}]}
     * </pre>
     * 
     * </p>
     * 
     * @param tbl
     *            the converting table.
     * @return the created text matrix.
     */
    public static final <R, C, V> TextMatrix create(final Table<R, C, V> tbl) {
        TextMatrix matrix = TextMatrix.create();

        Map<R, Integer> rows = new TreeMap<>();
        Map<C, Integer> cols = new TreeMap<>();
        for (Table.Cell<R, C, V> entry : tbl.cellSet()) {
            rows.put(entry.getRowKey(), 0);
            cols.put(entry.getColumnKey(), 0);
        }

        int row = 0;
        for (R r : rows.keySet()) {
            rows.put(r, row++);
        }

        int col = 0;
        for (C c : cols.keySet()) {
            cols.put(c, col++);
        }

        for (R r : rows.keySet()) {
            for (C c : cols.keySet()) {
                if (tbl.contains(r, c)) {
                    matrix.putValue(rows.get(r), cols.get(c), tbl.get(r, c).toString());
                }
            }
        }

        return matrix;
    }

    /**
     * Creates a new text matrix from the specified {@link Collection
     * collection}. All collection entries are converted to string and placed
     * into the one column matrix {@code [nx1]}, where the {@code n} equals to
     * the collection size.
     * 
     * <p>
     * <b>Usage example:</b>
     * 
     * <pre>
     * Set&lt;String&gt; set = Sets.newHashSet(&quot;A&quot;, &quot;B&quot;, &quot;C&quot;);
     * System.out.println(&quot;set=&quot; + set);
     * 
     * TextMatrix matrix = TextMatrix.&lt;String&gt; create(set);
     * System.out.println(&quot;matrix=&quot; + matrix);
     * </pre>
     * 
     * </p>
     * 
     * <p>
     * <b>Output:</b>
     * 
     * <pre>
     * set=[A, B, C]
     * matrix=TextMatrix{[TextCell{(0, 0)=A}, TextCell{(1, 0)=B}, TextCell{(2, 0)=C}]}
     * </pre>
     * 
     * </p>
     * 
     * @param coll
     *            the converting collection.
     * @return the created text matrix.
     */
    public static final <V> TextMatrix create(final Collection<V> coll) {
        TextMatrix matrix = TextMatrix.create();

        int row = 0;
        for (V entry : coll) {
            matrix.putValue(row, 0, entry.toString());
            ++row;
        }

        return matrix;
    }

    /**
     * Creates a new text matrix from the specified {@link Map map}. All map
     * keys/values are converted to string and placed into the two columns
     * matrix {@code [nx2]}, where the n equals to the map size. The map keys
     * are placed into the first column and values to the second column.
     * 
     * <p>
     * <b>Usage example:</b>
     * 
     * <pre>
     * Map&lt;String, Integer&gt; map = Maps.asMap(Sets.newHashSet(&quot;A&quot;, &quot;B&quot;, &quot;C&quot;),
     *       new Function&lt;String, Integer&gt;() {
     * 
     *          &#064;Override
     *          public Integer apply(String value) {
     *             return value.codePointAt(0);
     *          }
     *       });
     * System.out.println(&quot;map=&quot; + map);
     * 
     * TextMatrix matrix = TextMatrix.create(map);
     * System.out.println(&quot;matrix=&quot; + matrix);
     * 
     * </p>
     * 
     * <p>
     * <b>Output:</b>
     * 
     * <pre>
     * map={A=65, B=66, C=67}
     * matrix=TextMatrix{[TextCell{(0, 0)=A}, TextCell{(0, 1)=65}, TextCell{(1, 0)=B}, TextCell{(1, 1)=66}, TextCell{(2, 0)=C}, TextCell{(2, 1)=67}]}
     * 
     * </pre>
     * 
     * </p>
     * 
     * @param map
     *            the converting map.
     * @return the created text matrix.
     */
    public static final <K, V> TextMatrix create(final Map<K, V> map) {
        TextMatrix matrix = TextMatrix.create();

        int row = 0;
        for (Map.Entry<K, V> entry : map.entrySet()) {
            matrix.putValue(row, 0, entry.getKey().toString());
            matrix.putValue(row, 1, entry.getValue().toString());
            ++row;
        }

        return matrix;
    }

    /**
     * Creates a new empty text matrix
     * 
     * @return the text matrix.
     */
    public static final TextMatrix create() {
        return new TextMatrix(HashBasedTable.<Integer, Integer, String>create());
    }

    /**
     * The matrix cells container.
     */
    private LinkedList<TextCell> cells;

    /**
     * The number of rows.
     */
    private int numRows;

    /**
     * The number of columns.
     */
    private int numColumns;

    /**
     * Constructs a new text matrix from the lookup table.
     * 
     * @param table
     *            the lookup table sample.
     */
    private TextMatrix(final Table<Integer, Integer, String> table) {
        this.cells = new LinkedList<>();
        this.numColumns = 0;
        this.numRows = 0;

        for (Table.Cell<Integer, Integer, String> cell : table.cellSet()) {
            this.putValue(cell.getRowKey(), cell.getColumnKey(), cell.getValue());
        }
    }

    /**
     * Checks and throws exception if the row index out of bounds.
     * 
     * @param row
     *            the row index to check.
     * @param isIgnoreUpperBound
     *            {@code true} indicates that the upper bound should be ignored
     *            and {@code false} otherwise.
     * @throws IndexOutOfBoundsException
     *             if the row index out of bounds.
     */
    private final void rowShouldBeInRange(final int row, final boolean isIgnoreUpperBound)
            throws IndexOutOfBoundsException {
        if (isIgnoreUpperBound) {
            if (row < 0) {
                throw new IndexOutOfBoundsException("The row index " + row + " < 0");
            }
        } else {
            if (row < 0 || row >= this.numRows) {
                throw new IndexOutOfBoundsException("The row index " + row + " out of range  [0, " + this.numRows);
            }
        }
    }

    /**
     * Checks and throws exception if the row index out of bounds.
     * 
     * @param row
     *            the row index to check.
     * @throws IndexOutOfBoundsException
     *             if the row index out of bounds.
     */
    private final void rowShouldBeInRange(final int row) throws IndexOutOfBoundsException {
        this.rowShouldBeInRange(row, false);
    }

    /**
     * Checks and throws exception if the column index out of bounds.
     * 
     * @param col
     *            the column index to check.
     * @param isIgnoreUpperBound
     *            {@code true} indicates that the upper bound should be ignored
     *            and {@code false} otherwise.
     * @throws IndexOutOfBoundsException
     *             if the column index out of bounds.
     */
    private final void columnShouldBeInRange(final int col, final boolean isIgnoreUpperBound)
            throws IndexOutOfBoundsException {
        if (isIgnoreUpperBound) {
            if (col < 0) {
                throw new IndexOutOfBoundsException("The row index " + col + " < 0");
            }
        } else {
            if (col < 0 || col >= this.numColumns) {
                throw new IndexOutOfBoundsException(
                        "The row index " + col + " out of range  [0, " + this.numColumns);
            }
        }
    }

    /**
     * Checks and throws exception if the column index out of bounds.
     * 
     * @param col
     *            the column index to check.
     * @throws IndexOutOfBoundsException
     *             if the column index out of bounds.
     */
    private final void columnShouldBeInRange(final int col) throws IndexOutOfBoundsException {
        this.columnShouldBeInRange(col, false);
    }

    /**
     * Checks and throws exception if the matrix index out of bounds.
     * 
     * @param row
     *            the row index to check.
     * @param col
     *            the column index to check.
     * @param isIgnoreUpperBound
     *            {@code true} indicates that the upper bound should be ignored
     *            and {@code false} otherwise.
     * @throws IndexOutOfBoundsException
     *             if the row or column indices out of bounds.
     */
    private final void matrixShouldBeInRange(final int row, final int col, final boolean isIgnoreUpperBound)
            throws IndexOutOfBoundsException {
        this.rowShouldBeInRange(row, isIgnoreUpperBound);
        this.columnShouldBeInRange(col, isIgnoreUpperBound);
    }

    /**
     * Checks and throws exception if the matrix index out of bounds.
     * 
     * @param row
     *            the row index to check.
     * @param col
     *            the column index to check.
     * @throws IndexOutOfBoundsException
     *             if the row or column indices out of bounds.
     */
    private final void matrixShouldBeInRange(final int row, final int col) throws IndexOutOfBoundsException {
        this.matrixShouldBeInRange(row, col, false);
    }

    /**
     * Returns the number of rows.
     * 
     * @return the number of rows.
     */
    public final int getNumRows() {
        return this.numRows;
    }

    /**
     * Returns the number of columns.
     * 
     * @return the number of columns.
     */
    public final int getNumColumns() {
        return this.numColumns;
    }

    /**
     * Returns the maximum column widths.
     * 
     * @return the maximum column widths.
     */
    public final int[] getColumnWidths() {
        int[] widths = new int[this.numColumns];

        for (TextCell cell : this.cells) {
            if (cell.getWidth() > widths[cell.getColumn()]) {
                widths[cell.getColumn()] = cell.getWidth();
            }
        }

        return widths;
    }

    /**
     * Returns the closest to the given cell indices.
     * 
     * @param row
     *            the given row index.
     * @param column
     *            the given column index.
     * @return the closest cell.
     */
    private final int findClosestCellIndex(final int row, final int column) {
        Iterator<TextCell> itr = this.cells.iterator();

        TextCell cell = null;
        while (itr.hasNext()) {
            cell = itr.next();
            if (row > cell.getRow()) {
                continue;
            } else if (row == cell.getRow()) {
                if (column > cell.getColumn()) {
                    continue;
                } else if (column <= cell.getColumn()) {
                    return this.cells.indexOf(cell);
                }
            } else if (row < cell.getRow()) {
                return this.cells.indexOf(cell);
            }
        }

        return -1;
    }

    /**
     * Puts a new value to the matrix.
     * 
     * @param row
     *            the row index.
     * @param column
     *            the column index.
     * @param value
     *            the value to put.
     */
    public final void putValue(final int row, final int column, final String value) {
        this.matrixShouldBeInRange(row, column, true);

        TextCell cell = new TextCell(row, column, value);
        int index = this.findClosestCellIndex(row, column);
        if (index < 0) {
            this.cells.add(cell);
        } else if (this.cells.get(index).equals(cell)) {
            this.cells.set(index, cell);
        } else {
            this.cells.add(index, cell);
        }

        if (row >= this.numRows) {
            this.numRows = row + 1;
        }

        if (column >= this.numColumns) {
            this.numColumns = column + 1;
        }
    }

    /**
     * Returns the cell value by its index.
     * 
     * @param row
     *            the row index.
     * @param column
     *            the column index.
     * @return the cell value.
     */
    public final String getValue(final int row, final int column) {
        this.matrixShouldBeInRange(row, column);

        String value = "";
        int index = this.findClosestCellIndex(row, column);
        if (index >= 0) {
            value = this.cells.get(index).getValue();
        }

        return value;
    }

    /**
     * Returns the row values by its index.
     * 
     * @param row
     *            the row index.
     * @return the row values.
     */
    public final String[] getRow(final int row) {
        this.rowShouldBeInRange(row);

        String[] values = new String[this.numColumns];
        for (int column = 0; column < this.numColumns; column++) {
            values[column] = this.getValue(row, column);
        }
        return values;
    }

    /**
     * Returns the rows iterator.
     * 
     * @return the rows iterator.
     */
    public final Iterator<String[]> getRows() {
        String[][] buf = new String[this.numRows][this.numColumns];

        for (int row = 0; row < this.numRows; row++) {
            buf[row] = this.getRow(row);
        }

        return Iterators.forArray(buf);
    }

    /**
     * Returns the column values by its index.
     * 
     * @param column
     *            the column index.
     * @return the column values.
     */
    public final String[] getColumn(final int column) {
        this.columnShouldBeInRange(column);

        String[] values = new String[this.numRows];
        for (int row = 0; row < this.numRows; row++) {
            values[row] = this.getValue(row, column);
        }
        return values;
    }

    /**
     * Returns the columns iterator.
     * 
     * @return the columns iterator.
     */
    public final Iterator<String[]> getColumns() {
        String[][] buf = new String[this.numColumns][this.numRows];

        for (int col = 0; col < this.numRows; col++) {
            buf[col] = this.getColumn(col);
        }

        return Iterators.forArray(buf);
    }

    /**
     * Returns {@code Map} created from two matrix columns.
     * 
     * @param keyColumn
     *            the column index for map keys.
     * @param valueColumn
     *            the column index for map values.
     * @return the map instance.
     */
    public final Map<String, String> getMap(final int keyColumn, final int valueColumn) {
        this.columnShouldBeInRange(keyColumn);
        this.columnShouldBeInRange(valueColumn);

        Map<String, String> map = new HashMap<>();
        for (int row = 0; row < this.numRows; row++) {
            map.put(this.getValue(row, keyColumn), this.getValue(row, valueColumn));
        }
        return map;
    }

    /**
     * Tests if the text matrix is empty.
     * 
     * @return {@code true} if the text matrix is empty and false otherwise.
     */
    public final boolean isEmpty() {
        return this.cells.isEmpty();
    }

    /**
     * Modified to produce a hash code for the {@code TextMatrix} instance.
     */
    @Override
    public int hashCode() {
        return Objects.hashCode(this.numRows, this.numColumns, this.cells);
    }

    /**
     * Modified to test if two {@code TextMatrix} instances equal.
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }

        if (obj == null) {
            return false;
        }

        if (!(obj instanceof TextMatrix)) {
            return false;
        }

        TextMatrix other = (TextMatrix) obj;
        return Objects.equal(this.cells, other.cells) && Objects.equal(this.numRows, other.numRows)
                && Objects.equal(this.numColumns, other.numColumns);
    }

    /**
     * Returns an iterator through all matrix cells.
     */
    @Override
    public Iterator<TextCell> iterator() {
        return this.cells.iterator();
    }

    /**
     * Returns a {@code String} representation for the {@code TextMatrix}
     * instance.
     */
    @Override
    public String toString() {
        return Objects.toStringHelper(this).addValue(this.cells).toString();
    }

    /**
     * The text cell.
     * 
     * @author Mykola Galushka
     */
    public static class TextCell {
        /**
         * The cell row index.
         */
        final private int row;

        /**
         * The cell column index.
         */
        final int column;

        /**
         * The cell value.
         */
        final String value;

        /**
         * Constructs a new text cell.
         * 
         * @param row
         *            the cell row index.
         * @param column
         *            the cell column index.
         * @param value
         *            the cell value.
         * @throws IndexOutOfBoundsException
         *             if the specified row or column index is negative.
         * @throws NullPointerException
         *             if the cell value is null.
         */
        private TextCell(final int row, final int column, final String value) {
            if (row < 0) {
                throw new IndexOutOfBoundsException("The cell row index " + row + " < 0");
            }

            if (column < 0) {
                throw new IndexOutOfBoundsException("The cell column index " + column + " < 0");
            }

            if (value == null) {
                throw new NullPointerException("The cell value is null.");
            }

            this.row = row;
            this.column = column;
            this.value = value;
        }

        /**
         * Returns the cell row index.
         * 
         * @return the cell row index.
         */
        public int getRow() {
            return this.row;
        }

        /**
         * Returns the cell column index.
         * 
         * @return the cell column index.
         */
        public int getColumn() {
            return this.column;
        }

        /**
         * Returns the cell value.
         * 
         * @return the cell value.
         */
        public String getValue() {
            return this.value;
        }

        /**
         * Returns the cell value width.
         * 
         * @return the cell value width.
         */
        public int getWidth() {
            return this.value.length();
        }

        /**
         * Modified to produce a hash code for the {@code TextCell} instance.
         */
        @Override
        public int hashCode() {
            return Objects.hashCode(this.row, this.column, this.value);
        }

        /**
         * Modified to test if two {@code TextCell} instances equal.
         */
        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }

            if (obj == null) {
                return false;
            }

            if (!(obj instanceof TextCell)) {
                return false;
            }

            TextCell other = (TextCell) obj;
            return Objects.equal(this.row, other.row) && Objects.equal(this.column, other.column)
                    && Objects.equal(this.value, other.value);
        }

        /**
         * Returns a {@code String} representation for the {@code TextCell}
         * instance.
         */
        @Override
        public String toString() {
            return Objects.toStringHelper(this).add("(" + this.row + ", " + this.column + ")", this.value)
                    .toString();
        }
    }
}