/* * 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.data.transform; import com.wm.data.IData; import com.wm.data.IDataCursor; import com.wm.data.IDataFactory; import com.wm.data.IDataPortable; import com.wm.util.Table; import com.wm.util.coder.IDataCodable; import com.wm.util.coder.ValuesCodable; import permafrost.tundra.data.IDataHelper; import permafrost.tundra.lang.ArrayHelper; import permafrost.tundra.lang.TableHelper; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.List; /** * An abstract class representing a transformer that operates on an IData's elements. */ public abstract class Transformer<V, T> { /** * The class of values to be transformed. */ protected Class<V> valueClass; /** * The class of values after they are transformed. */ protected Class<T> transformedValueClass; /** * The class of values held in an array or table to be transformed, calculated from the valueClass. */ protected Class<?> arrayClass, tableClass, transformedArrayClass; /** * Whether embedded IData and IData[] children should be recursively transformed. */ protected boolean recurse, includeNulls, includeEmptyDocuments, includeEmptyArrays; /** * Whether transformed arrays and tables should be normalized to use the nearest ancestor class of all items as the * component type. */ protected boolean normalizeTransformedArrays = false, normalizeTransformedTables = false; /** * The type of transformation required. */ protected TransformerMode mode; /** * Constructs a new Transformer object. * * @param valueClass The class of values to be transformed; only values that are instances of this class * are transformed by the object. * @param transformedValueClass The class of transformed values. * @param mode The mode of transformation required. * @param recurse Whether to recursively transform child IData and IData[] objects. */ public Transformer(Class<V> valueClass, Class<T> transformedValueClass, TransformerMode mode, boolean recurse, boolean includeNulls, boolean includeEmptyDocuments, boolean includeEmptyArrays) { this.valueClass = valueClass; this.transformedValueClass = transformedValueClass; this.mode = TransformerMode.normalize(mode); this.recurse = recurse; this.includeNulls = includeNulls; this.includeEmptyDocuments = includeEmptyDocuments; this.includeEmptyArrays = includeEmptyArrays; this.arrayClass = Array.newInstance(valueClass, 0).getClass(); this.tableClass = Array.newInstance(valueClass, 0, 0).getClass(); this.transformedArrayClass = Array.newInstance(transformedValueClass, 0).getClass(); } /** * Transforms the elements of the given IData document. * * @param document The IData document whose elements are to be transformed. * @return A new IData document containing the transformed elements from the given IData document. */ public IData transform(IData document) { return transformIData(document); } /** * Transforms the elements of the given IData[] document list. * * @param array The IData[] document list whose elements are to be transformed. * @return A new IData[] document list containing the transformed elements from the given IData[] document * list. */ public IData[] transform(IData[] array) { return transformIDataArray(array); } /** * Transforms the elements of the given IData document. * * @param document The IData document whose elements are to be transformed. * @return A new IData document containing the transformed elements from the given IData document. */ @SuppressWarnings("unchecked") protected IData transformIData(IData document) { if (document == null) return null; boolean transformKeys = (mode == TransformerMode.KEYS || mode == TransformerMode.KEYS_AND_VALUES); boolean transformValues = (mode == TransformerMode.VALUES || mode == TransformerMode.KEYS_AND_VALUES); IData output = IDataFactory.create(); IDataCursor inputCursor = document.getCursor(); IDataCursor outputCursor = output.getCursor(); while (inputCursor.next()) { String key = inputCursor.getKey(); Object value = inputCursor.getValue(); String transformedKey; if (transformKeys) { transformedKey = transformKey(key, value); } else { transformedKey = key; } if (transformedKey != null) { if (transformValues) { if (value == null) { T transformedValue = transformNull(key); if (includeNulls || transformedValue != null) { outputCursor.insertAfter(transformedKey, transformedValue); } } else if (value instanceof IData[] || value instanceof Table || value instanceof IDataCodable[] || value instanceof IDataPortable[] || value instanceof ValuesCodable[]) { if (recurse) { IData[] transformedArray = transformIDataArray(IDataHelper.toIDataArray(value)); if (includeNulls || (transformedArray != null && (includeEmptyArrays || transformedArray.length > 0))) { outputCursor.insertAfter(transformedKey, transformedArray); } } else { outputCursor.insertAfter(transformedKey, value); } } else if (value instanceof IData || value instanceof IDataCodable || value instanceof IDataPortable || value instanceof ValuesCodable) { if (recurse) { IData transformedIData = transformIData(IDataHelper.toIData(value)); if (includeNulls || (transformedIData != null && (includeEmptyDocuments || IDataHelper.size(transformedIData) > 0))) { outputCursor.insertAfter(transformedKey, transformedIData); } } else { outputCursor.insertAfter(transformedKey, value); } } else if (tableClass.isInstance(value)) { T[][] transformedTable = transformTable(key, (V[][])value); if (includeNulls || (transformedTable != null && (includeEmptyArrays || transformedTable.length > 0))) { outputCursor.insertAfter(transformedKey, transformedTable); } } else if (arrayClass.isInstance(value)) { T[] transformedArray = transformArray(key, (V[])value); if (includeNulls || (transformedArray != null && (includeEmptyArrays || transformedArray.length > 0))) { outputCursor.insertAfter(transformedKey, transformedArray); } } else if (valueClass.isInstance(value)) { T transformedValue = transformValue(key, (V)value); if (includeNulls || transformedValue != null) { outputCursor.insertAfter(transformedKey, transformedValue); } } else { outputCursor.insertAfter(transformedKey, value); } } else { outputCursor.insertAfter(transformedKey, value); } } } inputCursor.destroy(); outputCursor.destroy(); return includeEmptyDocuments || IDataHelper.size(output) > 0 ? output : null; } /** * Transforms the elements of the given IData[] document list. * * @param array The IData[] document list whose elements are to be transformed. * @return A new IData[] document list containing the transformed elements from the given IData[] document * list. */ protected IData[] transformIDataArray(IData[] array) { if (array == null) return null; List<IData> output = new ArrayList<IData>(array.length); for (IData document : array) { IData transformedDocument = transformIData(document); if (includeNulls || transformedDocument != null) { output.add(transformedDocument); } } return includeEmptyArrays || output.size() > 0 ? output.toArray(new IData[output.size()]) : null; } /** * Transforms a key. * * @param key The key to be transformed. * @param value The value associated with the key being transformed. * @return The transformed key. */ protected abstract String transformKey(String key, Object value); /** * Transforms a value. * * @param key The key associated with the value being transformed. * @param value The value to be transformed. * @return The transformed value. */ protected abstract T transformValue(String key, V value); /** * Transforms a null value. * * @param key The key associated with the null value being transformed. * @return The transformed value. */ protected T transformNull(String key) { return null; } /** * Transforms an array of values. * * @param array The array of values to be transformed. * @return A new array of transformed values. */ @SuppressWarnings("unchecked") protected T[] transformArray(String key, V[] array) { if (array == null) return null; List<T> output = new ArrayList<T>(array.length); for (V item : array) { T transformedItem = item == null ? transformNull(key) : transformValue(key, item); if (includeNulls || transformedItem != null) { output.add(transformedItem); } } if (includeEmptyArrays || output.size() > 0) { if (normalizeTransformedArrays) { return ArrayHelper.normalize(output); } else { return output.toArray(ArrayHelper.instantiate(transformedValueClass, output.size())); } } else { return null; } } /** * Transforms a table of values. * * @param table The table of values to be transformed. * @return A new table of transformed values. */ @SuppressWarnings("unchecked") protected T[][] transformTable(String key, V[][] table) { if (table == null) return null; List<T[]> output = new ArrayList<T[]>(table.length); for (V[] array : table) { T[] transformedArray = transformArray(key, array); if (includeNulls || (transformedArray != null && (includeEmptyArrays || transformedArray.length > 0))) { output.add(transformedArray); } } if (includeEmptyArrays || output.size() > 0) { if (normalizeTransformedTables) { return TableHelper.normalize(output); } else { return output.toArray((T[][])TableHelper.instantiate(transformedValueClass, 0, 0)); } } else { return null; } } }