Java tutorial
/** * 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<String, String, String> table = HashBasedTable.create(); * table.put("a", "e", "A"); * table.put("b", "d", "B"); * table.put("b", "f", "C"); * table.put("c", "e", "D"); * System.out.println("table=" + table); * * TextMatrix matrix = TextMatrix.<String, String, String> create(table); * System.out.println("matrix=" + 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<String> set = Sets.newHashSet("A", "B", "C"); * System.out.println("set=" + set); * * TextMatrix matrix = TextMatrix.<String> create(set); * System.out.println("matrix=" + 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<String, Integer> map = Maps.asMap(Sets.newHashSet("A", "B", "C"), * new Function<String, Integer>() { * * @Override * public Integer apply(String value) { * return value.codePointAt(0); * } * }); * System.out.println("map=" + map); * * TextMatrix matrix = TextMatrix.create(map); * System.out.println("matrix=" + 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(); } } }