/* * The MIT License (MIT) * * Copyright (c) 2016 Lachlan Dowding * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package permafrost.tundra.lang; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; /** * A collection of convenience methods for working with two dimensional arrays. */ public final class TableHelper { /** * The default separator string between rows of a table when converting it to a string. */ public final static String DEFAULT_ROW_SEPARATOR = ", "; /** * Disallow instantiation of this class. */ private TableHelper() {} /** * Returns a new table with all null elements removed. * * @param table The two dimensional array to be compacted. * @param <T> The class of the items in the array. * @return A copy of the given array with all null items removed. */ public static <T> T[][] compact(T[][] table) { if (table == null) return null; List<T[]> list = new ArrayList<T[]>(table.length); for (T[] row : table) { if (row != null) list.add(ArrayHelper.compact(row)); } return list.toArray(Arrays.copyOf(table, list.size())); } /** * Returns a string created by concatenating each element of the given table. * * @param table The table whose contents are to be joined. * @param <T> The class of items stored in the table. * @return A string representation of the given table created by concatenating together the string * representation of each item in order. */ public static <T> String join(T[][] table) { return join(table, null); } /** * Returns a string created by concatenating each element of the given table, separated by the given separator * string. * * @param table The table whose contents are to be joined. * @param separator An optional separator string to be used between rows of the table and items of a row. * @param <T> The class of items stored in the table. * @return A string representation of the given table created by concatenating together the string * representation of each item in order. */ public static <T> String join(T[][] table, String separator) { return join(table, separator, separator); } /** * Returns a string created by concatenating each element of the given table, separated by the given separator * string. * * @param table The table whose contents are to be joined. * @param separator An optional separator string to be used between rows of the table and items of a row. * @param <T> The class of items stored in the table. * @return A string representation of the given table created by concatenating together the string * representation of each item in order. */ public static <T> String join(T[][] table, String separator, Sanitization mode) { return join(table, separator, separator, null, null, mode); } /** * Returns a string created by concatenating each element of the given table, separated by the given separator * string. * * @param table The table whose contents are to be joined. * @param rowSeparator An optional separator string to be used between rows of the table. * @param itemSeparator An optional separator string to be used between items of a row. * @param <T> The class of items stored in the table. * @return A string representation of the given table created by concatenating together the string * representation of each item in order. */ public static <T> String join(T[][] table, String rowSeparator, String itemSeparator) { return join(table, rowSeparator, itemSeparator, null, (String)null); } /** * Returns a string created by concatenating each element of the given table, separated by the given separator * string. * * @param table The table whose contents are to be joined. * @param rowSeparator An optional separator string to be used between rows of the table. * @param itemSeparator An optional separator string to be used between items of a row. * @param tableDefaultValue An optional value used when the table is null or empty. * @param rowDefaultValue An optional value used when a table row is null or empty. * @param <T> The class of items stored in the table. * @return A string representation of the given table created by concatenating together the string * representation of each item in order. */ public static <T> String join(T[][] table, String rowSeparator, String itemSeparator, String tableDefaultValue, String rowDefaultValue) { return join(table, rowSeparator, itemSeparator, tableDefaultValue, rowDefaultValue, null); } /** * Returns a string created by concatenating each element of the given table, separated by the given separator * string. * * @param table The table whose contents are to be joined. * @param rowSeparator An optional separator string to be used between rows of the table. * @param itemSeparator An optional separator string to be used between items of a row. * @param tableDefaultValue An optional value used when the table is null or empty. * @param rowDefaultValue An optional value used when a table row is null or empty. * @param sanitization The type of sanitization required, if any. * @param <T> The class of items stored in the table. * @return A string representation of the given table created by concatenating together the string * representation of each item in order. */ public static <T> String join(T[][] table, String rowSeparator, String itemSeparator, String tableDefaultValue, String rowDefaultValue, Sanitization sanitization) { table = sanitize(table, sanitization); if (table == null || table.length == 0) return tableDefaultValue; StringBuilder builder = new StringBuilder(); join(table, rowSeparator, itemSeparator, rowDefaultValue, builder); return builder.toString(); } /** * Returns a string created by concatenating each element of the given table, separated by the given separator * string. * * @param table The table whose contents are to be joined. * @param rowSeparator An optional separator string to be used between rows of the table. * @param itemSeparator An optional separator string to be used between items of the table. * @param rowDefaultValue An optional value used when a table row is null or empty. * @param <T> The class of items stored in the table. */ public static <T> void join(T[][] table, String rowSeparator, String itemSeparator, String rowDefaultValue, StringBuilder builder) { if (table == null || builder == null) return; for (int i = 0; i < table.length; i++) { if (rowSeparator != null && i > 0) builder.append(rowSeparator); if (table[i] == null) { builder.append(rowDefaultValue); } else { ArrayHelper.join(table[i], itemSeparator, builder); } } } /** * Returns a string representation of the given table. * * @param table The table to be stringified. * @param <T> The class of items stored in the array. * @return A string representation of the given array. */ public static <T> String stringify(T[][] table) { return stringify(table, null, null); } /** * Returns a string representation of the given table. * * @param table The table to be stringified. * @param separator The separator string to be used between rows, and between items of a row. * @param <T> The class of items stored in the array. * @return A string representation of the given array. */ public static <T> String stringify(T[][] table, String separator) { return stringify(table, separator, separator, (Sanitization)null); } /** * Returns a string representation of the given table. * * @param table The table to be stringified. * @param separator The separator string to be used between rows, and between items of a row. * @param sanitization The type of sanitization required, if any. * @param <T> The class of items stored in the array. * @return A string representation of the given array. */ public static <T> String stringify(T[][] table, String separator, Sanitization sanitization) { return stringify(table, separator, separator, sanitization); } /** * Returns a string representation of the given table. * * @param table The table to be stringified. * @param rowSeparator The separator string to be used between rows of the table. * @param itemSeparator The separator string to be used between items of a row. * @param sanitization The type of sanitization required, if any. * @param <T> The class of items stored in the array. * @return A string representation of the given array. */ public static <T> String stringify(T[][] table, String rowSeparator, String itemSeparator, Sanitization sanitization) { table = sanitize(table, sanitization); if (table == null) return null; StringBuilder builder = new StringBuilder(); stringify(table, rowSeparator, itemSeparator, builder); return builder.toString(); } /** * Returns a string representation of the given table. * * @param table The table to be stringified. * @param rowSeparator The separator string to be used between rows of the table. * @param itemSeparator The separator string to be used between items of a row. * @param builder The string builder to use when building the string. * @param <T> The class of items stored in the array. */ public static <T> void stringify(T[][] table, String rowSeparator, String itemSeparator, StringBuilder builder) { if (table == null || builder == null) return; if (rowSeparator == null) rowSeparator = DEFAULT_ROW_SEPARATOR; if (itemSeparator == null) itemSeparator = ArrayHelper.DEFAULT_ITEM_SEPARATOR; builder.append("["); for (int i = 0; i < table.length; i++) { if (i > 0) builder.append(rowSeparator); if (table[i] == null) { builder.append(table[i]); } else { ArrayHelper.stringify(table[i], itemSeparator, builder); } } builder.append("]"); } /** * Compacts the given table using the given compaction method. * * @param table The table to be compacted. * @param sanitization The type of sanitization required, if any. * @param <T> The class of item stored in the array. * @return The resulting compacted table. */ private static <T> T[][] sanitize(T[][] table, Sanitization sanitization) { if (table != null && sanitization != null) { if (sanitization == Sanitization.REMOVE_NULLS) { table = compact(table); } else if (sanitization == Sanitization.REMOVE_NULLS_AND_BLANKS) { table = squeeze(table); } } return table; } /** * Returns a new table with all empty or null elements removed. * * @param table A table to be squeezed. * @param <T> The type of item in the table. * @return A new table that is the given table squeezed. */ private static <T> T[][] squeeze(T[][] table) { if (table == null || table.length == 0) return null; List<T[]> list = new ArrayList<T[]>(table.length); for (T[] row : table) { row = ArrayHelper.squeeze(row); if (row != null) list.add(row); } table = list.toArray(Arrays.copyOf(table, list.size())); return table.length == 0 ? null : table; } /** * Converts each string in the given table to null if it only contains whitespace characters. * * @param table The table to be nullified. * @param <T> The nullified table. * @return */ private static <T> T[][] nullify(T[][] table) { if (table == null || table.length == 0) return null; List<T[]> list = new ArrayList<T[]>(table.length); for (T[] row : table) { row = ArrayHelper.nullify(row); if (row != null) list.add(row); } table = list.toArray(Arrays.copyOf(table, list.size())); return table.length == 0 ? null : table; } /** * Converts the given two dimensional array of objects to a string table. * * @param table The two dimensional array to be converted. * @param <T> The type of item in the given array. * @return The converted string table. */ public static <T> String[][] toStringTable(T[][] table) { if (table == null) return null; String[][] stringTable = new String[table.length][]; for (int i = 0; i < table.length; i++) { stringTable[i] = ArrayHelper.toStringArray(table[i]); } return stringTable; } /** * Returns a new table whose class is the nearest ancestor class of all contained items. * * @param table The table to be normalized. * @param <T> The type of item in the table. * @return A new copy of the given table whose class is the nearest ancestor of all contained items. */ @SuppressWarnings("unchecked") public static <T> T[][] normalize(T[][] table) { if (table == null) return null; List<T[]> list = new ArrayList<T[]>(table.length); Set<Class<?>> classes = new HashSet<Class<?>>(); for (T[] row : table) { Class<?> nearestAncestor = ObjectHelper.getNearestAncestor(row); classes.add(nearestAncestor); list.add(ArrayHelper.normalize(row, nearestAncestor)); } Class nearestAncestor = ClassHelper.getNearestAncestor(classes); return list.toArray((T[][])instantiate(nearestAncestor, 0, 0)); } /** * Returns a new table whose class is the nearest ancestor class of all contained items. * * @param table The table to be normalized. * @param <T> The type of item in the table. * @return A new copy of the given table whose class is the nearest ancestor of all contained items. */ @SuppressWarnings("unchecked") public static <T> T[][] normalize(Collection<T[]> table) { if (table == null) return null; List<T[]> list = new ArrayList<T[]>(table.size()); Set<Class<?>> classes = new HashSet<Class<?>>(); for (T[] row : table) { Class<?> nearestAncestor = ObjectHelper.getNearestAncestor(row); classes.add(nearestAncestor); list.add(ArrayHelper.normalize(row, nearestAncestor)); } Class nearestAncestor = ClassHelper.getNearestAncestor(classes); return list.toArray((T[][])instantiate(nearestAncestor, 0, 0)); } /** * Dynamically instantiates a new array of the given class with the given length. * * @param componentClass The class of items to be stored in the array. * @param dimensions The desired length of the returned array. * @param <T> The class of items to be stored in the array. * @return A new array of the given class with the given length. */ @SuppressWarnings("unchecked") public static <T> Object instantiate(Class<T> componentClass, int... dimensions) { return Array.newInstance(componentClass, dimensions); } }