/* * The MIT License (MIT) * * Copyright (c) 2015 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; import com.wm.app.b2b.server.ServiceException; import com.wm.data.IData; import com.wm.data.IDataCursor; import com.wm.data.IDataFactory; import com.wm.data.IDataPortable; import com.wm.data.IDataUtil; import com.wm.util.Table; import com.wm.util.coder.IDataCodable; import com.wm.util.coder.ValuesCodable; import org.w3c.dom.Node; import permafrost.tundra.data.transform.Blankifier; import permafrost.tundra.data.transform.Nullifier; import permafrost.tundra.data.transform.Replacer; import permafrost.tundra.data.transform.Stringifier; import permafrost.tundra.data.transform.TransformerMode; import permafrost.tundra.data.transform.Squeezer; import permafrost.tundra.data.transform.Transformer; import permafrost.tundra.data.transform.Trimmer; import permafrost.tundra.flow.ConditionEvaluator; import permafrost.tundra.flow.variable.SubstitutionHelper; import permafrost.tundra.io.filter.FilenameFilterType; import permafrost.tundra.lang.ArrayHelper; import permafrost.tundra.lang.BooleanHelper; import permafrost.tundra.lang.CharsetHelper; import permafrost.tundra.lang.ObjectConvertMode; import permafrost.tundra.lang.Sanitization; import permafrost.tundra.lang.ObjectHelper; import permafrost.tundra.lang.TableHelper; import permafrost.tundra.math.BigDecimalHelper; import permafrost.tundra.math.BigIntegerHelper; import permafrost.tundra.math.DoubleHelper; import permafrost.tundra.math.FloatHelper; import permafrost.tundra.math.IntegerHelper; import permafrost.tundra.math.LongHelper; import permafrost.tundra.math.RoundingModeHelper; import permafrost.tundra.mime.MIMETypeHelper; import permafrost.tundra.net.http.HTTPMethod; import permafrost.tundra.server.NodePermission; import permafrost.tundra.time.DateTimeHelper; import permafrost.tundra.time.DurationHelper; import permafrost.tundra.time.DurationPattern; import permafrost.tundra.xml.dom.NodeHelper; import permafrost.tundra.xml.dom.Nodes; import permafrost.tundra.xml.namespace.IDataNamespaceContext; import permafrost.tundra.xml.xpath.XPathHelper; import java.math.BigDecimal; import java.math.BigInteger; import java.math.RoundingMode; import java.nio.charset.Charset; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.activation.MimeType; import javax.xml.datatype.Duration; import javax.xml.namespace.NamespaceContext; import javax.xml.xpath.XPathExpression; import javax.xml.xpath.XPathExpressionException; /** * A collection of convenience methods for working with IData objects. */ public final class IDataHelper { /** * Regular expression pattern for matching an IData node XPath expression. */ public static final Pattern KEY_NODE_XPATH_REGULAR_EXPRESSION_PATTERN = Pattern.compile("(?i)([^\\/]+)(\\/.+)"); /** * Disallow instantiation of this class. */ private IDataHelper() {} /** * Returns all the keys in the given IData document. * * @param document An IData document to retrieve the keys from. * @return The list of keys present in the given IData document. */ public static String[] getKeys(IData document) { List<String> keys = getKeyList(document); return keys.toArray(new String[keys.size()]); } /** * Returns the keys that match the given regular expression pattern in the given IData document. * * @param document An IData document to retrieve the keys from. * @param patternString A regular expression pattern which the returned set of keys must match. * @return The list of keys present in the given IData document that match the given regular expression pattern. */ public static String[] getKeys(IData document, String patternString) { return getKeys(document, patternString == null ? null : Pattern.compile(patternString)); } /** * Returns the keys that match the given regular expression pattern in the given IData document. * * @param document An IData document to retrieve the keys from. * @param pattern A regular expression pattern which the returned set of keys must match. * @return The list of keys present in the given IData document that match the given regular expression * pattern. */ public static String[] getKeys(IData document, Pattern pattern) { List<String> keys = getKeyList(document, pattern); return keys.toArray(new String[keys.size()]); } /** * Returns the keys that match the given regular expression pattern in the given IData document. * * @param document An IData document. * @param pattern A regular expression pattern which the returned set of keys must match. * @return The list of keys present in the given IData document that match the given regular expression * pattern. */ public static List<String> getKeyList(IData document, Pattern pattern) { List<String> keys = new ArrayList<String>(size(document)); if (document != null) { IDataCursor cursor = document.getCursor(); while(cursor.next()) { String key = cursor.getKey(); if (pattern == null) { keys.add(key); } else { Matcher matcher = pattern.matcher(key); if (matcher.matches()) keys.add(key); } } cursor.destroy(); } return keys; } /** * Returns the top-level keys in the given IData document. * * @param document An IData document. * @return The list of top-level keys in the given IData document. */ public static List<String> getKeyList(IData document) { List<String> keys = new ArrayList<String>(size(document)); if (document != null) { IDataCursor cursor = document.getCursor(); while(cursor.next()) { keys.add(cursor.getKey()); } cursor.destroy(); } return keys; } /** * Returns all the top-level values that are instances of the given class from the given document. * * @param document An IData document. * @param valueClass The class that the returned values are instances of. * @return The list of top-level values that are instances of the given class from the given IData * document. */ public static <V> V[] getValues(IData document, Class<V> valueClass) { List<V> values = getValueList(document, valueClass); return values.toArray(ArrayHelper.instantiate(valueClass, values.size())); } /** * Returns all the top-level values from the given document. * * @param document An IData document from which to return all values. * @return The list of top-level values present in the given IData document. */ public static Object[] getValues(IData document) { return ArrayHelper.normalize(getValueList(document)); } /** * Returns all the top-level values that are instances of the given class from the given document. * * @param document An IData document. * @param valueClass The class that the returned values are instances of. * @return The list of top-level values that are instances of the given class from the given IData * document. */ @SuppressWarnings("unchecked") public static <V> List<V> getValueList(IData document, Class<V> valueClass) { if (valueClass == null) throw new NullPointerException("valueClass must not be null"); List<V> values = new ArrayList<V>(size(document)); if (document != null) { IDataCursor cursor = document.getCursor(); try { while (cursor.next()) { Object value = cursor.getValue(); if (valueClass.isInstance(value)) { values.add((V)value); } } } finally { cursor.destroy(); } } return values; } /** * Returns all the top-level values from the given document. * * @param document An IData document. * @return The list of top-level values from given IData document. */ @SuppressWarnings("unchecked") public static List getValueList(IData document) { List values = new ArrayList(size(document)); if (document != null) { IDataCursor cursor = document.getCursor(); try { while (cursor.next()) { values.add(cursor.getValue()); } } finally { cursor.destroy(); } } return values; } /** * Returns all the top-level values that are IData compatible objects, including elements in IData[] compatible * arrays, from the given IData document. * * @param document An IData document. * @return The list of top-level values that are IData compatible objects, including elements in IData[] * compatible arrays, from the given IData document */ @SuppressWarnings("unchecked") public static List<IData> getIDataValueList(IData document) { List<IData> values = new ArrayList<IData>(size(document)); if (document != null) { IDataCursor cursor = document.getCursor(); try { while (cursor.next()) { Object value = cursor.getValue(); if (value instanceof IData[] || value instanceof Table || value instanceof IDataCodable[] || value instanceof IDataPortable[] || value instanceof ValuesCodable[]) { values.addAll(Arrays.asList(toIDataArray(value))); } else if (value instanceof IData || value instanceof IDataCodable || value instanceof IDataPortable || value instanceof ValuesCodable) { values.add(toIData(value)); } } } finally { cursor.destroy(); } } return values; } /** * Returns all the top-level values that are IData documents or can be converted to IData documents from the given * IData document. * * @param document An IData document. * @return The list of top-level values that are IData documents or can be converted to IData documents * from the given IData document. */ public static IData[] getIDataValues(IData document) { List<IData> values = getIDataValueList(document); return values.toArray(new IData[values.size()]); } /** * Returns all leaf values from the given document. * * @param document The document to getLeafValues. * @return All leaf values recursively collected from the given document and its children. */ public static Object[] getLeafValues(IData document) { return getLeafValues(document, new Class[0]); } /** * Returns all leaf values that are instances of the given classes from the given document. * * @param document The document to getLeafValues. * @param classes List of classes the returned values must be instances of. * @return All leaf values recursively collected from the given document and its children. */ public static Object[] getLeafValues(IData document, Class... classes) { return ArrayHelper.normalize(getLeafValues(new ArrayList<Object>(), document, classes).toArray()); } /** * Returns all leaf values from the given document list. * * @param array The document list to getLeafValues. * @return All leaf values recursively collected from the given document list and its children. */ public static Object[] getLeafValues(IData[] array) { return getLeafValues(array, new Class[0]); } /** * Returns all leaf values that are instances of the given classes from the given document list. * * @param array The document list to getLeafValues. * @param classes List of classes the returned values must be instances of. * @return All leaf values recursively collected from the given document list and its children. */ public static Object[] getLeafValues(IData[] array, Class... classes) { return ArrayHelper.normalize(getLeafValues(new ArrayList<Object>(), array, classes).toArray()); } /** * Determine if IData leaves/children should be recursed. * * @param classes The list of classes the leaves are required to be instances of. * @return True if IData leaves should be recursed. */ private static boolean recurseIDataLeaves(Class ... classes) { boolean recurse = true; // if one of the requested classes is an IData compatible class, then we shouldn't recurse IData documents if (classes != null && classes.length > 0) { for (Class klass : classes) { if (klass != null && (klass == IData.class || klass == IDataCodable.class || klass == IDataPortable.class || klass == ValuesCodable.class)) { recurse = false; break; } } } return recurse; } /** * Returns all leaf values that are instances of the given classes from the given top-level IData. * * @param values The list to add the flattened values to. * @param value The IData to getLeafValues. * @param classes List of classes the returned values must be instances of. * @return The list of flattened values. */ private static List<Object> getLeafValues(List<Object> values, IData value, Class... classes) { return getLeafValues(values, value, recurseIDataLeaves(classes), classes); } /** * Returns all leaf values that are instances of the given classes from the given IData[]. * * @param values The list to add the flattened values to. * @param value The IData[] to getLeafValues. * @param classes List of classes the returned values must be instances of. * @return The list of flattened values. */ private static List<Object> getLeafValues(List<Object> values, IData[] value, Class... classes) { return getLeafValues(values, value, recurseIDataLeaves(classes), classes); } /** * Returns all leaf values that are instances of the given classes from the given IData. * * @param values The list to add the flattened values to. * @param value The IData to getLeafValues. * @param recurse If true, all IData objects will be recursed to construct the list of leaf values. * @param classes List of classes the returned values must be instances of. * @return The list of flattened values. */ private static List<Object> getLeafValues(List<Object> values, IData value, boolean recurse, Class... classes) { for (Map.Entry<String, Object> entry : IDataMap.of(value)) { values = getLeafValues(values, entry.getValue(), recurse, classes); } return values; } /** * Returns all leaf values that are instances of the given classes from the given IData[]. * * @param values The list to add the flattened values to. * @param value The IData[] to getLeafValues. * @param recurse If true, all IData objects will be recursed to construct the list of leaf values. * @param classes List of classes the returned values must be instances of. * @return The list of flattened values. */ private static List<Object> getLeafValues(List<Object> values, IData[] value, boolean recurse, Class... classes) { for (IData item : value) { values = getLeafValues(values, item, recurse, classes); } return values; } /** * Returns all leaf values that are instances of the given classes from the given Object[][]. * * @param values The list to add the flattened values to. * @param value The Object[][] to getLeafValues. * @param recurse If true, all IData objects will be recursed to construct the list of leaf values. * @param classes List of classes the returned values must be instances of. * @return The list of flattened values. */ private static List<Object> getLeafValues(List<Object> values, Object[][] value, boolean recurse, Class... classes) { for (Object[] array : value) { values = getLeafValues(values, array, recurse, classes); } return values; } /** * Returns all leaf values that are instances of the given classes from the given Object[]. * * @param values The list to add the flattened values to. * @param value The Object[] to getLeafValues. * @param recurse If true, all IData objects will be recursed to construct the list of leaf values. * @param classes List of classes the returned values must be instances of. * @return The list of flattened values. */ private static List<Object> getLeafValues(List<Object> values, Object[] value, boolean recurse, Class... classes) { for (Object item : value) { values = getLeafValues(values, item, recurse, classes); } return values; } /** * Returns all leaf values that are instances of the given classes from the given Object. * * @param values The list to add the flattened values to. * @param value The Object to getLeafValues. * @param recurse If true, all IData objects will be recursed to construct the list of leaf values. * @param classes List of classes the returned values must be instances of. * @return The list of flattened values. */ private static List<Object> getLeafValues(List<Object> values, Object value, boolean recurse, Class... classes) { if (value instanceof IData[] || value instanceof Table || value instanceof IDataCodable[] || value instanceof IDataPortable[] || value instanceof ValuesCodable[]) { values = getLeafValues(values, toIDataArray(value), recurse, classes); } else if (value instanceof IData || value instanceof IDataCodable || value instanceof IDataPortable || value instanceof ValuesCodable) { value = toIData(value); if (recurse) { values = getLeafValues(values, toIData(value), recurse, classes); } else { values.add(value); } } else if (value instanceof Object[][]) { values = getLeafValues(values, (Object[][])value, recurse, classes); } else if (value instanceof Object[]) { values = getLeafValues(values, (Object[])value, recurse, classes); } else { if (classes == null || classes.length == 0) { values.add(value); } else { for (Class klass : classes) { if (klass != null && klass.isInstance(value)) { values.add(value); break; } } } } return values; } /** * Merges multiple IData documents into a single new IData document. * * @param documents One or more IData documents to be merged. * @return A new IData document containing the keys and values from all merged input documents. */ public static IData merge(IData... documents) { return merge(documents == null ? null : Arrays.asList(documents)); } /** * Merges multiple IData documents into a single new IData document. * * @param recurse If true, a recursive merge is performed. * @param documents One or more IData documents to be merged. * @return A new IData document containing the keys and values from all merged input documents. */ public static IData merge(boolean recurse, IData... documents) { return merge(documents, recurse); } /** * Merges multiple IData documents into a single new IData document. * * @param documents One or more IData documents to be merged. * @param recurse If true, a recursive merge is performed. * @return A new IData document containing the keys and values from all merged input documents. */ public static IData merge(IData[] documents, boolean recurse) { return merge(documents == null ? null : Arrays.asList(documents), recurse); } /** * Merges the top-level IData values from the given IData document. * * @param documents An IData document containing IData values to be merged. * @param recurse If true, a recursive merge is performed. * @return A new IData document containing the keys and values from all merged IData documents. */ public static IData merge(IData documents, boolean recurse) { return merge(getIDataValueList(documents), recurse); } /** * Merges multiple IData documents into a single new IData document. * * @param documents One or more IData documents to be merged. * @return A new IData document containing the keys and values from all merged input documents. */ public static IData merge(Iterable<IData> documents) { IData output = IDataFactory.create(); if (documents != null) { for (IData document : documents) { if (document != null) { IDataUtil.merge(document, output); } } } return output; } /** * Merges multiple IData documents into a single new IData document. * * @param documents One or more IData documents to be merged. * @param recurse If true, a recursive merge is performed. * @return A new IData document containing the keys and values from all merged input documents. */ public static IData merge(Iterable<IData> documents, boolean recurse) { IData output = IDataFactory.create(); if (documents != null) { for (IData document : documents) { if (document != null) { IDataCursor documentCursor = document.getCursor(); IDataCursor outputCursor = output.getCursor(); try { while(documentCursor.next()) { String key = documentCursor.getKey(); Object value = documentCursor.getValue(); Object existingValue = IDataUtil.get(outputCursor, key); if (value != null) { if (recurse && (value instanceof IData || value instanceof IDataCodable || value instanceof IDataPortable || value instanceof ValuesCodable) && (existingValue instanceof IData || existingValue instanceof IDataCodable || existingValue instanceof IDataPortable || existingValue instanceof ValuesCodable)) { IDataUtil.put(outputCursor, key, merge(Arrays.asList(toIData(existingValue), toIData(value)), recurse)); } else { IDataUtil.put(outputCursor, key, value); } } } } finally { documentCursor.destroy(); outputCursor.destroy(); } } } } return output; } /** * Returns the number of top-level key value pairs in the given IData document. * * @param document An IData document. * @return The number of key value pairs in the given IData document. */ public static int size(IData document) { int size = 0; if (document != null) { IDataCursor cursor = document.getCursor(); size = IDataUtil.size(cursor); cursor.destroy(); } return size; } /** * Returns the number of occurrences of the given key in the given IData document. * * @param document An IData document. * @param key The key whose occurrences are to be counted. * @return The number of occurrences of the given key in the given IData document. */ public static int size(IData document, String key) { return size(document, key, false); } /** * Returns the number of occurrences of the given key in the given IData document. * * @param document An IData document. * @param key The key whose occurrences are to be counted. * @param literal If true, the key will be treated as a literal key, rather than potentially as a fully-qualified * key. * @return The number of occurrences of the given key in the given IData document. */ public static int size(IData document, String key, boolean literal) { int size = 0; if (document != null && key != null) { IDataCursor cursor = document.getCursor(); if (cursor.first(key)) { size++; while (cursor.next(key)) size++; } else if (IDataKey.isFullyQualified(key, literal)) { size = size(document, IDataKey.of(key, literal)); } cursor.destroy(); } return size; } /** * Returns the number of occurrences of the given fully-qualified key in the given IData document. * * @param document An IData document. * @param key The parsed fully-qualified key whose occurrences are to be counted. * @return The number of occurrences of the given parsed fully-qualified key in the given IData document. */ private static int size(IData document, IDataKey key) { int size = 0; if (document != null && key != null && key.size() > 0) { IDataCursor cursor = document.getCursor(); IDataKey.Part keyPart = key.remove(); if (key.size() > 0) { if (keyPart.hasArrayIndex()) { size = size(ArrayHelper.get(toIDataArray(IDataUtil.get(cursor, keyPart.getKey())), keyPart.getIndex()), key); } else if (keyPart.hasKeyIndex()) { size = size(toIData(get(document, keyPart.getKey(), keyPart.getIndex())), key); } else { size = size(toIData(IDataUtil.get(cursor, keyPart.getKey())), key); } } else { if (keyPart.hasArrayIndex()) { Object[] array = IDataUtil.getObjectArray(cursor, keyPart.getKey()); if (array != null && array.length > keyPart.getIndex()) { size = 1; } } else if (keyPart.hasKeyIndex()) { size = size(document, keyPart.getKey(), keyPart.getIndex()); } else { while (cursor.next(keyPart.getKey())) size++; } } cursor.destroy(); } return size; } /** * Returns the number of occurrences of the given nth key in the given IData document. * * @param document An IData document. * @param key The key whose occurrence is to be counted. * @param n The nth occurrence to be counted. * @return The number of occurrences of the given nth key in the given IData document. */ private static int size(IData document, String key, int n) { int size = 0; if (document != null && key != null && n >= 0) { int i = 0; IDataCursor cursor = document.getCursor(); while (cursor.next(key) && i++ < n) ; if (i > n) size = 1; cursor.destroy(); } return size; } /** * Returns true if the given key exists in the given IData document. * * @param document An IData document. * @param key The key to check the existence of. * @return True if the given key exists in the given IData document. */ public static boolean exists(IData document, String key) { return exists(document, key, false); } /** * Returns true if the given key exists in the given IData document. * * @param document An IData document. * @param key The key to check the existence of. * @param literal If true, the key will be treated as a literal key, rather than potentially as a fully-qualified * key. * @return True if the given key exists in the given IData document. */ public static boolean exists(IData document, String key, boolean literal) { return size(document, key, literal) > 0; } /** * Removes the given key from the given IData document, returning the associated value if one exists. * * @param document The document to remove the key from. * @param key The key to remove. * @return The value that was associated with the given key. */ public static Object remove(IData document, String key) { return remove(document, key, false); } /** * Removes the given key from the given IData document, returning the associated value if one exists. * * @param document The document to remove the key from. * @param key The key to remove. * @param literal If true, the key will be treated as a literal key, rather than potentially as a fully-qualified * key. * @return The value that was associated with the given key. */ public static Object remove(IData document, String key, boolean literal) { Object value = get(document, key, literal); drop(document, key, literal); return value; } /** * Removes all occurrences of the given key from the given IData document, returning the associated values if there * were any. * * @param document The document to remove the key from. * @param key The key to remove. * @return The values that were associated with the given key. */ public static Object[] removeAll(IData document, String key) { return removeAll(document, key, false); } /** * Removes all occurrences of the given key from the given IData document, returning the associated values if there * were any. * * @param document The document to remove the key from. * @param key The key to remove. * @param literal If true, the key will be treated as a literal key, rather than potentially as a fully-qualified * key. * @return The values that were associated with the given key. */ public static Object[] removeAll(IData document, String key, boolean literal) { Object[] value = getAsArray(document, key, literal); dropAll(document, key, literal); return value; } /** * Returns a recursive clone of the given IData document. * * @param document An IData document to be duplicated. * @return A new IData document which is a copy of the given IData document. */ public static IData duplicate(IData document) { return duplicate(document, true); } /** * Returns a new IData document which is a copy of the given IData document. * * @param document An IData document to be duplicated. * @param recurse When true, nested IData documents and IData[] document lists will also be duplicated. * @return A new IData document which is a copy of the given IData document. */ public static IData duplicate(IData document, boolean recurse) { if (document == null) return null; IData output = IDataFactory.create(); IDataCursor inputCursor = document.getCursor(); IDataCursor outputCursor = output.getCursor(); while(inputCursor.next()) { String key = inputCursor.getKey(); Object value = inputCursor.getValue(); if (recurse) { if (value instanceof IData[] || value instanceof Table || value instanceof IDataCodable[] || value instanceof IDataPortable[] || value instanceof ValuesCodable[]) { value = duplicate(toIDataArray(value), recurse); } else if (value instanceof IData || value instanceof IDataCodable || value instanceof IDataPortable || value instanceof ValuesCodable) { value = duplicate(toIData(value), recurse); } } outputCursor.insertAfter(key, value); } inputCursor.destroy(); outputCursor.destroy(); return output; } /** * Returns a new IData[] document list which is a copy of the given IData[] document list. * * @param array An IData[] document list to be duplicated. * @param recurse When true, nested IData documents and IData[] document lists will also be duplicated. * @return A new IData[] document list which is a copy of the given IData[] document list. */ public static IData[] duplicate(IData[] array, boolean recurse) { if (array == null) return null; IData[] output = new IData[array.length]; for (int i = 0; i < array.length; i++) { output[i] = duplicate(array[i], recurse); } return output; } /** * Removes the value with the given key from the given IData document. * * @param document An IData document. * @param key A simple or fully-qualified key identifying the value to be removed from the given IData * document. * @return The given IData document. */ public static IData drop(IData document, String key) { return drop(document, key, false); } /** * Removes the value with the given key from the given IData document. * * @param document An IData document. * @param key A simple or fully-qualified key identifying the value to be removed from the given IData * document. * @param literal If true, the key will be treated as a literal key, rather than potentially as a fully-qualified * key. * @return The given IData document. */ public static IData drop(IData document, String key, boolean literal) { if (document != null && key != null) { IDataCursor cursor = document.getCursor(); if (cursor.first(key)) { cursor.delete(); } else if (IDataKey.isFullyQualified(key, literal)) { drop(document, IDataKey.of(key, literal)); } cursor.destroy(); } return document; } /** * Removes the value with the given key from the given IData document. * * @param document An IData document. * @param key A fully-qualified key identifying the value to be removed from the given IData document. * @return The given IData document. */ private static IData drop(IData document, IDataKey key) { if (document != null && key != null && key.size() > 0) { IDataCursor cursor = document.getCursor(); IDataKey.Part keyPart = key.remove(); if (key.size() > 0) { if (keyPart.hasArrayIndex()) { drop(ArrayHelper.get(toIDataArray(IDataUtil.get(cursor, keyPart.getKey())), keyPart.getIndex()), key); } else if (keyPart.hasKeyIndex()) { drop(toIData(get(document, keyPart.getKey(), keyPart.getIndex())), key); } else { Object value = IDataUtil.get(cursor, keyPart.getKey()); IData[] array = toIDataArray(value); if (array != null) { // if we are referencing an IData[], drop the key from all items in the array for (IData item : array) { drop(item, key.clone()); } } else { drop(toIData(value), key); } } } else { if (keyPart.hasArrayIndex()) { IDataUtil.put(cursor, keyPart.getKey(), ArrayHelper.drop(IDataUtil.getObjectArray(cursor, keyPart.getKey()), keyPart.getIndex())); } else if (keyPart.hasKeyIndex()) { drop(document, keyPart.getKey(), keyPart.getIndex()); } else { IDataUtil.remove(cursor, keyPart.getKey()); } } cursor.destroy(); } return document; } /** * Removes the element with the given nth key from the given IData document. * * @param document The IData document to remove the key value pair from. * @param key The key to be removed. * @param n Determines which occurrence of the key to remove. */ private static void drop(IData document, String key, int n) { if (document == null || key == null || n < 0) return; int i = 0; IDataCursor cursor = document.getCursor(); while (cursor.next(key) && i++ < n) ; if (i > n) cursor.delete(); cursor.destroy(); } /** * Removes all occurrences of the given key from the given IData document. * * @param document The IData document to remove the key from. * @param key The key to be removed. * @return The given IData document, to allow for method chaining. */ public static IData dropAll(IData document, String key) { return dropAll(document, key, false); } /** * Removes all occurrences of the given key from the given IData document. * * @param document The IData document to remove the key from. * @param key The key to be removed. * @param literal If true, the key will be treated as a literal key, rather than potentially as a fully-qualified * key. * @return The given IData document, to allow for method chaining. */ public static IData dropAll(IData document, String key, boolean literal) { if (document != null && key != null) { IDataCursor cursor = document.getCursor(); if (cursor.next(key)) { do { cursor.delete(); } while (cursor.next(key)); } else if (IDataKey.isFullyQualified(key, literal)) { dropAll(document, IDataKey.of(key, literal)); } cursor.destroy(); } return document; } /** * Removes all occurrences of the given key from the given IData document. * * @param document An IData document. * @param key A fully-qualified key identifying the values to be removed from the given IData document. * @return The given IData document. */ private static IData dropAll(IData document, IDataKey key) { if (document != null && key != null && key.size() > 0) { IDataCursor cursor = document.getCursor(); IDataKey.Part keyPart = key.remove(); if (key.size() > 0) { if (keyPart.hasArrayIndex()) { dropAll(ArrayHelper.get(toIDataArray(IDataUtil.get(cursor, keyPart.getKey())), keyPart.getIndex()), key); } else if (keyPart.hasKeyIndex()) { dropAll(toIData(get(document, keyPart.getKey(), keyPart.getIndex())), key); } else { dropAll(toIData(IDataUtil.get(cursor, keyPart.getKey())), key); } } else { if (keyPart.hasArrayIndex()) { IDataUtil.put(cursor, keyPart.getKey(), ArrayHelper.drop(IDataUtil.getObjectArray(cursor, keyPart.getKey()), keyPart.getIndex())); } else if (keyPart.hasKeyIndex()) { drop(document, keyPart.getKey(), keyPart.getIndex()); } else { while (cursor.next(keyPart.getKey())) { cursor.delete(); } } } cursor.destroy(); } return document; } /** * Renames a key from source to target within the given IData document. * * @param document An IData document. * @param source A simple or fully-qualified key identifying the value in the given IData document to be renamed. * @param target The new simple or fully-qualified key for the renamed value. * @return The given IData document. */ public static IData rename(IData document, String source, String target) { return rename(document, source, target, false); } /** * Renames a key from source to target within the given IData document. * * @param document An IData document. * @param source A simple or fully-qualified key identifying the value in the given IData document to be renamed. * @param target The new simple or fully-qualified key for the renamed value. * @param literal If true, the key will be treated as a literal key, rather than potentially as a fully-qualified * key. * @return The given IData document. */ public static IData rename(IData document, String source, String target, boolean literal) { if (document != null && source != null && target != null && !source.equals(target)) { document = copy(document, source, target, literal); document = drop(document, source, literal); } return document; } /** * Copies a value from source key to target key within the given IData document. * * @param document An IData document. * @param source A simple or fully-qualified key identifying the value in the given IData document to be copied. * @param target A simple or fully-qualified key the source value will be copied to. * @return The given IData document. */ public static IData copy(IData document, String source, String target) { return copy(document, source, target, false); } /** * Copies a value from source key to target key within the given IData document. * * @param document An IData document. * @param source A simple or fully-qualified key identifying the value in the given IData document to be copied. * @param target A simple or fully-qualified key the source value will be copied to. * @param literal If true, the key will be treated as a literal key, rather than potentially as a fully-qualified * key. * @return The given IData document. */ public static IData copy(IData document, String source, String target, boolean literal) { if (document != null && source != null && target != null && !source.equals(target)) { document = put(document, target, get(document, source, literal), literal); } return document; } /** * Amends the given IData document with the key value pairs specified in the amendments IData document. * * @param document The IData document to be amended. * @param amendments The list of key value pairs to amend the document with. * @param scope The scope against which to resolve variable substitution statements. * @return The amended IData document. * @throws ServiceException If an error occurs. */ public static IData amend(IData document, IData[] amendments, IData scope) throws ServiceException { if (amendments == null) return document; IData output = duplicate(document); for (int i = 0; i < amendments.length; i++) { if (amendments[i] != null) { IDataCursor cursor = amendments[i].getCursor(); String key = IDataUtil.getString(cursor, "key"); String value = IDataUtil.getString(cursor, "value"); String condition = IDataUtil.getString(cursor, "condition"); cursor.destroy(); key = SubstitutionHelper.substitute(key, scope); value = SubstitutionHelper.substitute(value, scope); if ((condition == null) || ConditionEvaluator.evaluate(condition, scope)) { output = IDataHelper.put(output, key, value); } } } return output; } /** * Trims all string values in the given IData of leading and trailing whitespace. * * @param document The IData to be trimmed. * @return A new IData containing trimmed versions of the elements in the given input. */ public static IData trim(IData document) { return trim(document, true); } /** * Trims all string values in the given IData of leading and trailing whitespace. * * @param document The IData to be trimmed. * @param recurse Whether to recursively trim. * @return A new IData containing trimmed versions of the elements in the given input. */ public static IData trim(IData document, boolean recurse) { return trim(document, TransformerMode.VALUES, recurse); } /** * Trims all string keys and/or values in the given IData of leading and trailing whitespace. * * @param document The IData to be trimmed. * @param mode The transformer mode to use. * @param recurse Whether to recursively trim. * @return A new IData containing trimmed versions of the elements in the given input. */ public static IData trim(IData document, TransformerMode mode, boolean recurse) { return transform(document, new Trimmer(mode, recurse, true, true, true)); } /** * Trims all string values in the given IData[] of leading and trailing whitespace. * * @param input The IData[] to be trimmed. * @return A new IData[] containing trimmed versions of the elements in the given input. */ public static IData[] trim(IData[] input) { return trim(input, true); } /** * Trims all string values in the given IData[] of leading and trailing whitespace. * * @param input The IData[] to be trimmed. * @param recurse Whether to recursively trim. * @return A new IData[] containing trimmed versions of the elements in the given input. */ public static IData[] trim(IData[] input, boolean recurse) { return trim(input, TransformerMode.VALUES, recurse); } /** * Trims all string keys and/or values in the given IData[] of leading and trailing whitespace. * * @param input The IData[] to be trimmed. * @param mode The transformer mode to use. * @param recurse Whether to recursively trim. * @return A new IData[] containing trimmed versions of the elements in the given input. */ public static IData[] trim(IData[] input, TransformerMode mode, boolean recurse) { return transform(input, new Trimmer(mode, recurse, true, true, true)); } /** * Replaces either the first or all occurrences of the given regular expression in the given IData document with the * given replacement. * * @param document The IData document whose string values are to be replaced. * @param pattern The regular expression pattern. * @param replacement The replacement string. * @param literal Whether the replacement string is literal and therefore requires quoting. * @param firstOnly If true, only the first occurrence is replaced, otherwise all occurrences are replaced. * @param recurse Whether to recursively process the document. * @return The document with replaced string values. */ public static IData replace(IData document, String pattern, String replacement, boolean literal, boolean firstOnly, boolean recurse) { return replace(document, pattern == null ? null : Pattern.compile(pattern), replacement != null && literal ? Matcher.quoteReplacement(replacement) : replacement, firstOnly, recurse); } /** * Replaces either the first or all occurrences of the given regular expression in the given IData document with the * given replacement. * * @param document The IData document whose string values are to be replaced. * @param pattern The regular expression pattern. * @param replacement The replacement string. * @param firstOnly If true, only the first occurrence is replaced, otherwise all occurrences are replaced. * @param recurse Whether to recursively process the document. * @return The document with replaced string values. */ public static IData replace(IData document, Pattern pattern, String replacement, boolean firstOnly, boolean recurse) { return replace(document, pattern, replacement, firstOnly, TransformerMode.VALUES, recurse); } /** * Replaces either the first or all occurrences of the given regular expression in the given IData document with the * given replacement. * * @param document The IData document whose string values are to be replaced. * @param pattern The regular expression pattern. * @param replacement The replacement string. * @param firstOnly If true, only the first occurrence is replaced, otherwise all occurrences are replaced. * @param mode The transformer mode to use. * @param recurse Whether to recursively process the document. * @return The document with replaced string values. */ public static IData replace(IData document, Pattern pattern, String replacement, boolean firstOnly, TransformerMode mode, boolean recurse) { return transform(document, new Replacer(mode, pattern, replacement, firstOnly, recurse)); } /** * Replaces either the first or all occurrences of the given regular expression in the given IData[] document list * with the given replacement. * * @param array The IData[] document list whose string values are to be replaced. * @param pattern The regular expression pattern. * @param replacement The replacement string. * @param firstOnly If true, only the first occurrence is replaced, otherwise all occurrences are replaced. * @param recurse Whether to recursively process the document list. * @return The document list with replaced string values. */ public static IData[] replace(IData[] array, String pattern, String replacement, boolean literal, boolean firstOnly, boolean recurse) { return replace(array, pattern == null ? null : Pattern.compile(pattern), replacement != null && literal ? Matcher.quoteReplacement(replacement) : replacement, firstOnly, recurse); } /** * Replaces either the first or all occurrences of the given regular expression in the given IData[] document list * with the given replacement. * * @param array The IData[] document list whose string values are to be replaced. * @param pattern The regular expression pattern. * @param replacement The replacement string. * @param firstOnly If true, only the first occurrence is replaced, otherwise all occurrences are replaced. * @param recurse Whether to recursively process the document list. * @return The document list with replaced string values. */ public static IData[] replace(IData[] array, Pattern pattern, String replacement, boolean firstOnly, boolean recurse) { return replace(array, pattern, replacement, firstOnly, TransformerMode.VALUES, recurse); } /** * Replaces either the first or all occurrences of the given regular expression in the given IData[] document list * with the given replacement. * * @param array The IData[] document list whose string values are to be replaced. * @param pattern The regular expression pattern. * @param replacement The replacement string. * @param firstOnly If true, only the first occurrence is replaced, otherwise all occurrences are replaced. * @param mode The transformer mode to use. * @param recurse Whether to recursively process the document list. * @return The document list with replaced string values. */ public static IData[] replace(IData[] array, Pattern pattern, String replacement, boolean firstOnly, TransformerMode mode, boolean recurse) { return transform(array, new Replacer(mode, pattern, replacement, firstOnly, recurse)); } /** * Trims all string values, then converts empty strings to nulls, then compacts by removing all null values. * * @param document An IData document to be squeezed. * @return A new IData document that is the given IData squeezed. */ public static IData squeeze(IData document) { return squeeze(document, true); } /** * Trims all string values, then converts empty strings to nulls, then compacts by removing all null values. * * @param document An IData document to be squeezed. * @param recurse Whether to also squeeze embedded IData and IData[] objects. * @return A new IData document that is the given IData squeezed. */ public static IData squeeze(IData document, boolean recurse) { return transform(document, new Squeezer(recurse)); } /** * Returns a new IData[] with all empty and null values removed. * * @param array An IData[] to be squeezed. * @return A new IData[] that is the given IData[] squeezed. */ public static IData[] squeeze(IData[] array) { return squeeze(array, true); } /** * Returns a new IData[] with all empty and null values removed. * * @param array An IData[] to be squeezed. * @param recurse Whether to also squeeze embedded IData and IData[] objects. * @return A new IData[] that is the given IData[] squeezed. */ public static IData[] squeeze(IData[] array, boolean recurse) { return transform(array, new Squeezer(recurse)); } /** * Converts all strings that only contain whitespace characters to null. * * @param document An IData document to be nullified. * @return A new IData document that is the given IData nullified. */ public static IData nullify(IData document) { return nullify(document, true); } /** * Converts all strings that only contain whitespace characters to null. * * @param document An IData document to be nullified. * @param recurse Whether to also nullify embedded IData and IData[] objects. * @return A new IData document that is the given IData nullified. */ public static IData nullify(IData document, boolean recurse) { return transform(document, new Nullifier(recurse)); } /** * Converts all strings that only contain whitespace characters to null. * * @param input An IData[] to be nullified. * @return A new IData[] that is the given IData[] nullify. */ public static IData[] nullify(IData[] input) { return nullify(input, true); } /** * Converts all strings that only contain whitespace characters to null. * * @param input An IData[] to be nullified. * @param recurse Whether to also nullify embedded IData and IData[] objects. * @return A new IData[] that is the given IData[] nullify. */ public static IData[] nullify(IData[] input, boolean recurse) { return transform(input, new Nullifier(recurse)); } /** * Converts all null values to empty strings. * * @param document The IData document to blankify. * @param recurse Whether embedded IData and IData[] objects should be recursively blankified. * @return The blankified IData. */ public static IData blankify(IData document, boolean recurse) { return transform(document, new Blankifier(recurse)); } /** * Converts all null values to empty strings. * * @param array The IData[] to blankify. * @param recurse Whether embedded IData and IData[] objects should be recursively blankified. * @return The blankified IData[]. */ public static IData[] blankify(IData[] array, boolean recurse) { return transform(array, new Blankifier(recurse)); } /** * Converts all non-string values to strings, except for IData and IData[] compatible objects. * * @param document The IData document to stringify. * @return The stringified IData document. */ public static IData stringify(IData document) { return stringify(document, true); } /** * Converts all non-string values to strings, except for IData and IData[] compatible objects. * * @param document The IData document to stringify. * @param recurse Whether embedded IData and IData[] objects should also be stringified recursively. * @return The stringified IData document. */ public static IData stringify(IData document, boolean recurse) { return transform(document, new Stringifier(recurse)); } /** * Converts all non-string values to strings, except for IData and IData[] compatible objects. * * @param array The IData[] to stringify. * @return The stringified IData[]. */ public static IData[] stringify(IData[] array) { return stringify(array, true); } /** * Converts all non-string values to strings, except for IData and IData[] compatible objects. * * @param array The IData[] to stringify. * @param recurse Whether to stringify embedded IData and IData[] objects recursively. * @return The stringified IData[]. */ public static IData[] stringify(IData[] array, boolean recurse) { return transform(array, new Stringifier(recurse)); } /** * Transforms the given IData document using the given transformer. * * @param document The IData document to be transformed. * @param transformer The Transformer object to use to transform the given IData document. * @return The resulting transformed IData document. */ public static IData transform(IData document, Transformer transformer) { return transformer.transform(document); } /** * Transforms the given IData[] document list using the given transformer. * * @param array The IData[] document list to be transformed. * @param transformer The Transformer object to use to transform the given IData[] document list. * @return The resulting transformed IData[] document list. */ public static IData[] transform(IData[] array, Transformer transformer) { return transformer.transform(array); } /** * Returns a string created by concatenating each element of the given IData document. * * @param document The IData document to be converted to a string. * @return A string representation of the given IData document. */ public static String join(IData document) { return join(document, null, null, null); } /** * Returns a string created by concatenating each element of the given IData document, separated by the given * separator strings. * * @param document The IData document to be converted to a string. * @param itemSeparator The string to use to delimit entries in IData documents. * @param listSeparator The string to use to delimit list items. * @param valueSeparator The string to use to delimit key value pairs. * @return A string representation of the given IData document. */ public static String join(IData document, String itemSeparator, String listSeparator, String valueSeparator) { return join(document, itemSeparator, listSeparator, valueSeparator, (Sanitization)null); } /** * Returns a string created by concatenating each element of the given IData document, separated by the given * separator strings. * * @param document The IData document to be converted to a string. * @param itemSeparator The string to use to delimit entries in IData documents. * @param listSeparator The string to use to delimit list items. * @param valueSeparator The string to use to delimit key value pairs. * @param sanitization The type of sanitization required, if any. * @return A string representation of the given IData document. */ public static String join(IData document, String itemSeparator, String listSeparator, String valueSeparator, Sanitization sanitization) { document = sanitize(document, sanitization); if (document == null || size(document) == 0) return null; StringBuilder builder = new StringBuilder(); join(document, itemSeparator, listSeparator, valueSeparator, builder); return builder.toString(); } /** * Returns a string created by concatenating each element of the given IData document, separated by the given * separator strings. * * @param document The IData document to be converted to a string. * @param itemSeparator The string to use to delimit entries in IData documents. * @param listSeparator The string to use to delimit list items. * @param valueSeparator The string to use to delimit key value pairs. * @param builder The string builder to use when building the output. */ private static void join(IData document, String itemSeparator, String listSeparator, String valueSeparator, StringBuilder builder) { if (document == null || builder == null) return; if (itemSeparator == null) itemSeparator = ", "; if (listSeparator == null) listSeparator = ", "; if (valueSeparator == null) valueSeparator = ": "; boolean itemSeparatorRequired = false; IDataCursor cursor = document.getCursor(); while (cursor.next()) { String key = cursor.getKey(); Object value = cursor.getValue(); if (itemSeparatorRequired) builder.append(itemSeparator); builder.append(key); builder.append(valueSeparator); if (value instanceof IData[] || value instanceof Table || value instanceof IDataCodable[] || value instanceof IDataPortable[] || value instanceof ValuesCodable[]) { builder.append("["); join(toIDataArray(value), itemSeparator, listSeparator, valueSeparator, builder); builder.append("]"); } else if (value instanceof IData || value instanceof IDataCodable || value instanceof IDataPortable || value instanceof ValuesCodable) { builder.append("{"); join(toIData(value), itemSeparator, listSeparator, valueSeparator, builder); builder.append("}"); } else if (value instanceof Object[][]) { TableHelper.stringify(TableHelper.toStringTable((Object[][])value), listSeparator, listSeparator, builder); } else if (value instanceof Object[]) { ArrayHelper.stringify(ArrayHelper.toStringArray((Object[])value), listSeparator, builder); } else { builder.append(ObjectHelper.stringify(value)); } itemSeparatorRequired = true; } cursor.destroy(); } /** * Returns a string created by concatenating each element of the given IData[] document list. * * @param array The IData[] document list to be converted to a string. * @return A string representation of the given IData document. */ public static String join(IData[] array) { return join(array, null, null, null); } /** * Returns a string created by concatenating each element of the given IData[] document list, separated by the given * separator strings. * * @param array The IData[] document list to be converted to a string. * @param itemSeparator The string to use to delimit entries in IData documents. * @param listSeparator The string to use to delimit list items. * @param valueSeparator The string to use to delimit key value pairs. * @return A string representation of the given IData document. */ public static String join(IData[] array, String itemSeparator, String listSeparator, String valueSeparator) { return join(array, itemSeparator, listSeparator, valueSeparator, (Sanitization)null); } /** * Returns a string created by concatenating each element of the given IData[] document list, separated by the given * separator strings. * * @param array The IData[] document list to be converted to a string. * @param itemSeparator The string to use to delimit entries in IData documents. * @param listSeparator The string to use to delimit list items. * @param valueSeparator The string to use to delimit key value pairs. * @param sanitization The type of sanitization required, if any. * @return A string representation of the given IData document. */ public static String join(IData[] array, String itemSeparator, String listSeparator, String valueSeparator, Sanitization sanitization) { array = sanitize(array, sanitization); if (array == null || array.length == 0) return null; StringBuilder builder = new StringBuilder(); join(array, itemSeparator, listSeparator, valueSeparator, builder); return builder.toString(); } /** * Returns a string created by concatenating each element of the given IData[] document list, separated by the given * separator strings. * * @param array The IData[] document list to be converted to a string. * @param itemSeparator The string to use to delimit entries in IData documents. * @param listSeparator The string to use to delimit list items. * @param valueSeparator The string to use to delimit key value pairs. * @param builder The string builder to use when building the output. */ public static void join(IData[] array, String itemSeparator, String listSeparator, String valueSeparator, StringBuilder builder) { if (array == null || builder == null) return; if (itemSeparator == null) itemSeparator = ", "; if (listSeparator == null) listSeparator = ", "; if (valueSeparator == null) valueSeparator = ": "; for(int i = 0; i < array.length; i++) { if (i > 0) builder.append(listSeparator); builder.append("{"); join(array[i], itemSeparator, listSeparator, valueSeparator, builder); builder.append("}"); } } /** * Converts the value associated with the given key to an array in the given IData document. * * @param document An IData document. * @param key The key whose associated value is to be converted to an array. * @return The given IData with the given key's value converted to an array. */ public static IData arrayify(IData document, String key) { if (exists(document, key)) { Object[] value = getAsArray(document, key); dropAll(document, key); put(document, key, value); } return document; } /** * Sanitizes the given IData document by removing nulls, or removing nulls and blanks. * * @param document The IData document to be sanitized. * @param sanitization The type of sanitization required, if any. * @return The sanitized IData document. */ public static IData sanitize(IData document, Sanitization sanitization) { if (document != null && sanitization != null) { if (sanitization == Sanitization.REMOVE_NULLS) { document = compact(document); } else if (sanitization == Sanitization.REMOVE_NULLS_AND_BLANKS) { document = squeeze(document); } } return document; } /** * Removes all null values from the given IData document. * * @param document The IData document to be compacted. * @return The compacted IData. */ public static IData compact(IData document) { return compact(document, true); } /** * Removes all null values from the given IData document. * * @param document The IData document to be compacted. * @param recurse Whether embedded IData and IData[] objects should be recursively compacted. * @return The compacted IData. */ public static IData compact(IData document, boolean recurse) { if (document == null) return null; IData output = IDataFactory.create(); IDataCursor inputCursor = document.getCursor(); IDataCursor outputCursor = output.getCursor(); while (inputCursor.next()) { String key = inputCursor.getKey(); Object value = inputCursor.getValue(); if (value != null) { if (recurse) { if (value instanceof IData[] || value instanceof Table || value instanceof IDataCodable[] || value instanceof IDataPortable[] || value instanceof ValuesCodable[]) { value = compact(toIDataArray(value), recurse); } else if (value instanceof IData || value instanceof IDataCodable || value instanceof IDataPortable || value instanceof ValuesCodable) { value = compact(toIData(value), recurse); } else if (value instanceof Object[][]) { value = TableHelper.compact((Object[][])value); } else if (value instanceof Object[]) { value = ArrayHelper.compact((Object[])value); } } } if (value != null) IDataUtil.put(outputCursor, key, value); } inputCursor.destroy(); outputCursor.destroy(); return output; } /** * Sanitizes the given IData[] by removing nulls, or removing nulls and blanks. * * @param array The IData[] to be sanitized. * @param sanitization The type of sanitization required. * @return The sanitized IData[]. */ public static IData[] sanitize(IData[] array, Sanitization sanitization) { if (array != null && sanitization != null) { if (sanitization == Sanitization.REMOVE_NULLS) { array = compact(array); } else if (sanitization == Sanitization.REMOVE_NULLS_AND_BLANKS) { array = squeeze(array); } } return array; } /** * Removes all null values from the given IData[]. * * @param array The IData[] to be compacted. * @return The compacted IData[]. */ public static IData[] compact(IData[] array) { return compact(array, true); } /** * Removes all null values from the given IData[]. * * @param array The IData[] to be compacted. * @param recurse Whether embedded IData and IData[] objects should be recursively compacted. * @return The compacted IData[]. */ public static IData[] compact(IData[] array, boolean recurse) { if (array == null) return null; IData[] output = new IData[array.length]; for (int i = 0; i < array.length; i++) { output[i] = compact(array[i], recurse); } return ArrayHelper.compact(output); } /** * Normalizes the given Object. * * @param value An Object to be normalized. * @return A new normalized version of the given Object. */ private static Object normalize(Object value) { if (value instanceof Table) { value = normalize((Table)value); } else if (value instanceof IDataCodable[]) { value = normalize((IDataCodable[])value); } else if (value instanceof IDataPortable[]) { value = normalize((IDataPortable[])value); } else if (value instanceof ValuesCodable[]) { value = normalize((ValuesCodable[])value); } else if (value instanceof Collection) { value = normalize((Collection)value); } else if (value instanceof Map[]) { value = normalize((Map[]) value); } else if (value instanceof IData[]) { value = normalize((IData[])value); } else if (value instanceof IDataCodable) { value = normalize((IDataCodable)value); } else if (value instanceof IDataPortable) { value = normalize((IDataPortable)value); } else if (value instanceof ValuesCodable) { value = normalize((ValuesCodable)value); } else if (value instanceof Map) { value = normalize((Map)value); } else if (value instanceof IData) { value = normalize((IData)value); } return value; } /** * Normalizes the given Object[]. * * @param array The Object[] to be normalized. * @return Normalized version of the Object[]. */ private static Object[] normalize(Object[] array) { return (Object[])normalize((Object)ArrayHelper.normalize(array)); } /** * Returns a new IData document, where all nested IData and IData[] objects are implemented with the same class, and * all fully-qualified keys are replaced with their representative nested structure. * * @param document An IData document to be normalized. * @return A new normalized version of the given IData document. */ public static IData normalize(IData document) { if (document == null) return null; // support normalizing TN FixedData objects such as document types, which are both IData and IDataCodable if (document instanceof IDataCodable) { document = normalize((IDataCodable)document); } else if (document instanceof ValuesCodable) { document = normalize((ValuesCodable)document); } else if (document instanceof IDataPortable) { document = normalize((IDataPortable)document); } IData output = IDataFactory.create(); IDataCursor inputCursor = document.getCursor(); IDataCursor outputCursor = output.getCursor(); boolean outputCursorDirty = false; while(inputCursor.next()) { String key = inputCursor.getKey(); Object value = normalize(inputCursor.getValue()); if (IDataKey.isFullyQualified(key)) { // normalize fully-qualified keys by using IDataHelper.put() rather than IDataUtil.put() put(output, key, value); outputCursorDirty = true; } else { // reuse cursor unless it has been marked dirty by a fully-qualified put if (outputCursorDirty) { outputCursor.destroy(); outputCursor = output.getCursor(); outputCursor.last(); outputCursorDirty = false; } // support multiple occurrences of same key by using IDataCursor.insertAfter() outputCursor.insertAfter(key, value); } } inputCursor.destroy(); outputCursor.destroy(); return output; } /** * Converts a java.util.Map to an IData object. * * @param map A java.util.Map to be converted to an IData object. * @return An IData representation of the given java.util.Map object. */ private static IData normalize(Map map) { return normalize(toIData(map)); } /** * Normalizes a java.util.Collection to an Object[]. * * @param collection A java.util.Collection to be converted to an Object[]. * @return An Object[] representation of the given java.util.Collection object. */ private static Object[] normalize(Collection collection) { return normalize(ArrayHelper.toArray(collection)); } /** * Normalizes an IDataCodable object to an IData representation. * * @param document An IDataCodable object to be normalized. * @return An IData representation for the given IDataCodable object. */ public static IData normalize(IDataCodable document) { return normalize(toIData(document)); } /** * Normalizes an IDataCodable[] where all items are converted to IData documents implemented with the same class, * and all fully-qualified keys are replaced with their representative nested structure. * * @param array An IDataCodable[] list to be normalized. * @return A new normalized IData[] version of the given IDataCodable[] list. */ public static IData[] normalize(IDataCodable[] array) { return normalize(toIDataArray(array)); } /** * Normalizes an IDataPortable object to an IData representation. * * @param document An IDataPortable object to be normalized. * @return An IData representation for the given IDataPortable object. */ public static IData normalize(IDataPortable document) { return normalize(toIData(document)); } /** * Normalizes an IDataPortable[] where all items are converted to IData documents implemented with the same class, * and all fully-qualified keys are replaced with their representative nested structure. * * @param array An IDataPortable[] list to be normalized. * @return A new normalized IData[] version of the given IDataPortable[] list. */ public static IData[] normalize(IDataPortable[] array) { return normalize(toIDataArray(array)); } /** * Normalizes an ValuesCodable object to an IData representation. * * @param document An ValuesCodable object to be normalized. * @return An IData representation for the given ValuesCodable object. */ public static IData normalize(ValuesCodable document) { return normalize(toIData(document)); } /** * Normalizes an ValuesCodable[] where all items are converted to IData documents implemented with the same class, * and all fully-qualified keys are replaced with their representative nested structure. * * @param array An ValuesCodable[] list to be normalized. * @return A new normalized IData[] version of the given ValuesCodable[] list. */ public static IData[] normalize(ValuesCodable[] array) { return normalize(toIDataArray(array)); } /** * Normalizes an IData[] where all IData objects are implemented with the same class, and all fully-qualified keys * are replaced with their representative nested structure. * * @param array An IData[] document list to be normalized. * @return A new normalized version of the given IData[] document list. */ public static IData[] normalize(IData[] array) { if (array == null) return null; IData[] output = new IData[array.length]; for (int i = 0; i < array.length; i++) { output[i] = normalize(array[i]); } return output; } /** * Normalizes a com.wm.util.Table object to an IData[] representation. * * @param table A com.wm.util.Table object to be normalized. * @return An IData[] representation of the given com.wm.util.Table object. */ public static IData[] normalize(Table table) { return normalize(toIDataArray(table)); } /** * Normalizes a Map[] object to an IData[] representation. * * @param array A Map[] object to be normalized. * @return An IData[] representation of the given Map[] object. */ public static IData[] normalize(Map[] array) { return normalize(toIDataArray(array)); } /** * Removes all key value pairs from the given IData document. * * @param document An IData document to be cleared. */ public static void clear(IData document) { clear(document, (String)null); } /** * Removes all key value pairs from the given IData document except those with a specified key. * * @param document An IData document to be cleared. * @param keysToBePreserved List of simple or fully-qualified keys identifying items that should not be removed. */ public static void clear(IData document, String... keysToBePreserved) { if (document == null) return; IData saved = IDataFactory.create(); if (keysToBePreserved != null) { for (String key : keysToBePreserved) { if (key != null) put(saved, key, get(document, key), false, false); } } IDataCursor cursor = document.getCursor(); cursor.first(); while (cursor.delete()); cursor.destroy(); if (keysToBePreserved != null) IDataUtil.merge(saved, document); } /** * Returns the value associated with the given key as a one-dimensional array; if the value is a * multi-dimensional array it is first flattened. * * @param document The IData document which contains the values to be flattened. * @param keys One or more fully-qualified keys identifying the values to be flattened. * @return A one-dimensional flattened array containing the values associated with the given keys. */ public static Object[] flatten(IData document, String... keys) { return flatten(document, false, keys); } /** * Returns the value associated with the given key as a one-dimensional array; if the value is a * multi-dimensional array it is first flattened. * * @param document The IData document which contains the values to be flattened. * @param includeNulls If true null values will be included in the returned array. * @param keys One or more fully-qualified keys identifying the values to be flattened. * @return A one-dimensional flattened array containing the values associated with the given keys. */ public static Object[] flatten(IData document, boolean includeNulls, String... keys) { if (document == null || keys == null) return null; ArrayList<Object> list = new ArrayList<Object>(); for (String key : keys) { Object value = get(document, key); if (value instanceof Object[]) { ArrayHelper.flatten((Object[])value, list, includeNulls); } else if (includeNulls || value != null) { list.add(value); } } return list.size() == 0 ? null : ArrayHelper.normalize(list); } /** * Returns the value associated with the given key from the given IData document. * * @param document An IData document. * @param key A simple or fully-qualified key identifying the value in the given IData document to be * returned. * @return The value associated with the given key in the given IData document. */ public static Object get(IData document, String key) { return get(null, document, key, Object.class); } /** * Returns the value associated with the given key from the given IData document. * * @param document An IData document. * @param key A simple or fully-qualified key identifying the value in the given IData document to be * returned. * @param klass The class of the value to be returned. * @param <T> The type of value to be returned. * @return The value associated with the given key in the given IData document. */ public static <T> T get(IData document, String key, Class<T> klass) { return get(null, document, key, klass); } /** * Returns the value associated with the given key from the given scope (if relative) or pipeline (if absolute). * * @param pipeline The pipeline, required if the key is an absolute path. * @param scope An IData document used to scope the key if it is relative. * @param key A simple or fully-qualified key identifying the value in the given IData document to be * returned. * @return The value associated with the given key in the given IData document. */ public static Object get(IData pipeline, IData scope, String key) { return get(pipeline, scope, key, false, Object.class); } /** * Returns the value associated with the given key from the given scope (if relative) or pipeline (if absolute). * * @param pipeline The pipeline, required if the key is an absolute path. * @param scope An IData document used to scope the key if it is relative. * @param key A simple or fully-qualified key identifying the value in the given IData document to be * returned. * @param klass The class of the value to be returned. * @param <T> The type of value to be returned. * @return The value associated with the given key in the given IData document. */ public static <T> T get(IData pipeline, IData scope, String key, Class<T> klass) { return get(pipeline, scope, key, false, klass); } /** * Returns the value associated with the given key from the given IData document. * * @param document An IData document. * @param key A simple or fully-qualified key identifying the value in the given IData document to be * returned. * @param literal If true, the key will be treated as a literal key, rather than potentially as a fully-qualified * key. * @return The value associated with the given key in the given IData document. */ public static Object get(IData document, String key, boolean literal) { return get(null, document, key, literal, Object.class); } /** * Returns the value associated with the given key from the given IData document. * * @param document An IData document. * @param key A simple or fully-qualified key identifying the value in the given IData document to be * returned. * @param literal If true, the key will be treated as a literal key, rather than potentially as a fully-qualified * key. * @param klass The class of the value to be returned. * @param <T> The type of value to be returned. * @return The value associated with the given key in the given IData document. */ public static <T> T get(IData document, String key, boolean literal, Class<T> klass) { return get(null, document, key, literal, klass); } /** * Returns the value associated with the given key from the given IData document, or if null the specified default * value. * * @param document An IData document. * @param key A simple or fully-qualified key identifying the value in the given IData document to be * returned. * @param defaultValue A default value to be returned if the existing value associated with the given key is null. * @return Either the value associated with the given key in the given IData document, or the given * defaultValue if null. */ public static Object get(IData document, String key, Object defaultValue) { return get(document, key, defaultValue, false, Object.class); } /** * Returns the value associated with the given key from the given IData document, or if null the specified default * value. * * @param document An IData document. * @param key A simple or fully-qualified key identifying the value in the given IData document to be * returned. * @param defaultValue A default value to be returned if the existing value associated with the given key is null. * @param klass The class of the value to be returned. * @param <T> The type of value to be returned. * @return Either the value associated with the given key in the given IData document, or the given * defaultValue if null. */ public static <T> T get(IData document, String key, T defaultValue, Class<T> klass) { return get(document, key, defaultValue, false, klass); } /** * Returns the value associated with the given key from the given scope (if relative) or pipeline (if absolute). * * @param pipeline The pipeline, required if the key is an absolute path. * @param scope An IData document used to scope the key if it is relative. * @param key A simple or fully-qualified key identifying the value in the given IData document to be * returned. * @param defaultValue A default value to be returned if the existing value associated with the given key is null. * @return Either the value associated with the given key in the given IData document, or the given * defaultValue if null. */ public static Object get(IData pipeline, IData scope, String key, Object defaultValue) { return get(pipeline, scope, key, defaultValue, false, Object.class); } /** * Returns the value associated with the given key from the given scope (if relative) or pipeline (if absolute). * * @param pipeline The pipeline, required if the key is an absolute path. * @param scope An IData document used to scope the key if it is relative. * @param key A simple or fully-qualified key identifying the value in the given IData document to be * returned. * @param defaultValue A default value to be returned if the existing value associated with the given key is null. * @param klass The class of the value to be returned. * @param <T> The type of value to be returned. * @return Either the value associated with the given key in the given IData document, or the given * defaultValue if null. */ public static <T> T get(IData pipeline, IData scope, String key, T defaultValue, Class<T> klass) { return get(pipeline, scope, key, defaultValue, false, klass); } /** * Returns the value associated with the given key from the given IData document, or if null the specified default * value. * * @param document An IData document. * @param key A simple or fully-qualified key identifying the value in the given IData document to be * returned. * @param defaultValue A default value to be returned if the existing value associated with the given key is null. * @param literal If true, the key will be treated as a literal key, rather than potentially as a * fully-qualified key. * @return Either the value associated with the given key in the given IData document, or the given * defaultValue if null. */ public static Object get(IData document, String key, Object defaultValue, boolean literal) { return get(null, document, key, defaultValue, literal, Object.class); } /** * Returns the value associated with the given key from the given IData document, or if null the specified default * value. * * @param document An IData document. * @param key A simple or fully-qualified key identifying the value in the given IData document to be * returned. * @param defaultValue A default value to be returned if the existing value associated with the given key is null. * @param literal If true, the key will be treated as a literal key, rather than potentially as a * fully-qualified key. * @param klass The class of the value to be returned. * @param <T> The type of value to be returned. * @return Either the value associated with the given key in the given IData document, or the given * defaultValue if null. */ public static <T> T get(IData document, String key, T defaultValue, boolean literal, Class<T> klass) { return get(null, document, key, defaultValue, literal, klass); } /** * Returns the value associated with the given key from the given IData document, or if null the specified default * value. * * @param pipeline An IData document against which absolute variables are resolved. * @param scope An IData document against which relative variables are resolved. * @param key A simple or fully-qualified key identifying the value in the given IData document to be * returned. * @param defaultValue A default value to be returned if the existing value associated with the given key is null. * @param literal If true, the key will be treated as a literal key, rather than potentially as a * fully-qualified key. * @return Either the value associated with the given key in the given IData document, or the given * defaultValue if null. */ public static Object get(IData pipeline, IData scope, String key, Object defaultValue, boolean literal) { return get(pipeline, scope, key, defaultValue, literal, Object.class); } /** * Returns the value associated with the given key from the given IData document, or if null the specified default * value. * * @param pipeline An IData document against which absolute variables are resolved. * @param scope An IData document against which relative variables are resolved. * @param key A simple or fully-qualified key identifying the value in the given IData document to be * returned. * @param defaultValue A default value to be returned if the existing value associated with the given key is null. * @param literal If true, the key will be treated as a literal key, rather than potentially as a * fully-qualified key. * @param klass The class of the value to be returned. * @param <T> The type of value to be returned. * @return Either the value associated with the given key in the given IData document, or the given * defaultValue if null. */ public static <T> T get(IData pipeline, IData scope, String key, T defaultValue, boolean literal, Class<T> klass) { T value = get(pipeline, scope, key, literal, klass); if (value == null) value = defaultValue; return value; } /** * Returns the value associated with the given key from the given scope (if relative) or pipeline (if absolute). * * @param pipeline The pipeline, required if the key is an absolute path. * @param scope An IData document used to scope the key if it is relative. * @param key A simple or fully-qualified key identifying the value in the given IData document to be * returned. * @param literal If true, the key will be treated as a literal key, rather than potentially as a fully- * qualified key. * @return The value associated with the given key in the given IData document. */ public static Object get(IData pipeline, IData scope, String key, boolean literal) { return get(pipeline, scope, key, literal, null, Object.class); } /** * Returns the value associated with the given key from the given scope (if relative) or pipeline (if absolute). * * @param pipeline The pipeline, required if the key is an absolute path. * @param scope An IData document used to scope the key if it is relative. * @param key A simple or fully-qualified key identifying the value in the given IData document to be * returned. * @param literal If true, the key will be treated as a literal key, rather than potentially as a fully- * qualified key. * @param klass The class of the value to be returned. * @param <T> The type of value to be returned. * @return The value associated with the given key in the given IData document. */ public static <T> T get(IData pipeline, IData scope, String key, boolean literal, Class<T> klass) { return get(pipeline, scope, key, literal, null, klass); } /** * Returns the value associated with the given key from the given scope (if relative) or pipeline (if absolute). * * @param pipeline The pipeline, required if the key is an absolute path. * @param scope An IData document used to scope the key if it is relative. * @param key A simple or fully-qualified key identifying the value in the given IData document to be * returned. * @param literal If true, the key will be treated as a literal key, rather than potentially as a fully- * qualified key. * @param namespaceContext The namespace context used when resolving XPath expressions against nodes. * @return The value associated with the given key in the given IData document. */ public static Object get(IData pipeline, IData scope, String key, boolean literal, NamespaceContext namespaceContext) { return get(pipeline, scope, key, literal, namespaceContext, Object.class); } /** * Returns the value associated with the given key from the given scope (if relative) or pipeline (if absolute). * * @param pipeline The pipeline, required if the key is an absolute path. * @param scope An IData document used to scope the key if it is relative. * @param key A simple or fully-qualified key identifying the value in the given IData document to be * returned. * @param literal If true, the key will be treated as a literal key, rather than potentially as a fully- * qualified key. * @param namespaceContext The namespace context used when resolving XPath expressions against nodes. * @param klass The class of the value to be returned. * @param <T> The type of value to be returned. * @return The value associated with the given key in the given IData document. */ @SuppressWarnings("unchecked") public static <T> T get(IData pipeline, IData scope, String key, boolean literal, NamespaceContext namespaceContext, Class<T> klass) { if (klass == null) throw new NullPointerException("class must not be null"); if (key == null) return null; Object value = null; IDataCursor cursor = null; try { if (scope != null) cursor = scope.getCursor(); // try finding a value that matches the literal key, and if not found try finding a value // associated with the leaf key if the key is considered fully-qualified if (scope != null && cursor != null && cursor.first(key)) { value = cursor.getValue(); } else if (pipeline != null && IDataKey.isAbsolute(key, literal)) { value = get(null, pipeline, key.substring(1), literal, namespaceContext, klass); } else if (scope != null && IDataKey.isFullyQualified(key, literal)) { // support resolving XPath expressions against nodes Matcher matcher = KEY_NODE_XPATH_REGULAR_EXPRESSION_PATTERN.matcher(key); if (matcher.matches()) { String variable = matcher.group(1); String expression = matcher.group(2); Object node = get(pipeline, scope, variable, true); if (node instanceof Node) { try { XPathExpression compiledExpression = XPathHelper.compile(expression, namespaceContext); Nodes nodes = XPathHelper.get((Node)node, compiledExpression); if (nodes != null && nodes.size() > 0) { value = NodeHelper.getValue(nodes.get(0)); } } catch (XPathExpressionException ex) { // do nothing, assume a normal IData fully-qualified key was specified rather than an XPath expression } } else { value = get(scope, IDataKey.of(key, literal), klass); } } else { value = get(scope, IDataKey.of(key, literal), klass); } } } finally { if (cursor != null) cursor.destroy(); } return klass.isInstance(value) ? (T)value : null; } /** * Returns the value associated with the given fully-qualified key from the given IData document. * * @param document An IData document. * @param key A fully-qualified key identifying the value in the given IData document to be returned. * @return The value associated with the given key in the given IData document. */ private static Object get(IData document, IDataKey key) { return get(document, key, Object.class); } /** * Returns the value associated with the given fully-qualified key from the given IData document. * * @param document An IData document. * @param key A fully-qualified key identifying the value in the given IData document to be returned. * @param klass The class of the value to be returned. * @param <T> The type of value to be returned. * @return The value associated with the given key in the given IData document. */ @SuppressWarnings("unchecked") private static <T> T get(IData document, IDataKey key, Class<T> klass) { if (klass == null) throw new NullPointerException("class must not be null"); Object value = null; if (document != null && key != null && key.size() > 0) { IDataCursor cursor = document.getCursor(); IDataKey.Part keyPart = key.remove(); if (key.size() > 0) { if (keyPart.hasArrayIndex()) { value = get(ArrayHelper.get(toIDataArray(IDataUtil.get(cursor, keyPart.getKey())), keyPart.getIndex()), key, klass); } else if (keyPart.hasKeyIndex()) { value = get(toIData(get(document, keyPart.getKey(), keyPart.getIndex())), key, klass); } else { Object object = IDataUtil.get(cursor, keyPart.getKey()); IData parent = toIData(object); if (parent != null) { value = get(parent, key, klass); } else { IData[] array = toIDataArray(object); if (array != null) { List<Object> values = new ArrayList<Object>(array.length); // if we are referencing an IData[], create a new array of values from the individual values in each IData for (IData item : array) { values.add(get(item, key.clone(), klass)); } value = ArrayHelper.normalize(values); } } } } else { if (keyPart.hasArrayIndex()) { value = IDataUtil.get(cursor, keyPart.getKey()); if (value != null) { if (value instanceof Object[] || value instanceof Table) { Object[] array = value instanceof Object[] ? (Object[])value : ((Table)value).getValues(); value = ArrayHelper.get(array, keyPart.getIndex()); } else { value = null; } } } else if (keyPart.hasKeyIndex()) { value = get(document, keyPart.getKey(), keyPart.getIndex(), klass); } else { value = IDataUtil.get(cursor, keyPart.getKey()); } } cursor.destroy(); } return klass.isInstance(value) ? (T)value : null; } /** * Returns the nth value associated with the given key. * * @param document The IData document to return the value from. * @param key The key whose associated value is to be returned. * @param n Determines which occurrence of the key to return the value for. * @return The value associated with the nth occurrence of the given key in the given IData document. */ private static Object get(IData document, String key, int n) { return get(document, key, n, Object.class); } /** * * @param document The IData document to return the value from. * @param key The key whose associated value is to be returned. * @param n Determines which occurrence of the key to return the value for. * @param klass The class of the value to be returned. * @param <T> The type of value to be returned. * @return The value associated with the nth occurrence of the given key in the given IData document. */ @SuppressWarnings("unchecked") private static <T> T get(IData document, String key, int n, Class<T> klass) { if (klass == null) throw new NullPointerException("class must not be null"); if (document == null || key == null || n < 0) return null; Object value = null; int i = 0; IDataCursor cursor = document.getCursor(); while (cursor.next(key) && i++ < n) ; if (i > n) value = cursor.getValue(); cursor.destroy(); return klass.isInstance(value) ? (T)value : null; } /** * Returns the value associated with the given key from the given IData document as an array. * * @param document An IData document. * @param key A simple or fully-qualified key identifying the value in the given IData document to be * returned. * @return The value associated with the given key in the given IData document as an array. */ public static Object[] getAsArray(IData document, String key) { return getAsArray(document, key, false); } /** * Returns the value associated with the given key from the given IData document as an array. * * @param document An IData document. * @param key A simple or fully-qualified key identifying the value in the given IData document to be * returned. * @param literal If true, the key will be treated as a literal key, rather than potentially as a fully-qualified * key. * @return The value associated with the given key in the given IData document as an array. */ public static Object[] getAsArray(IData document, String key, boolean literal) { if (document == null || key == null) return null; Object[] output = null; IDataCursor cursor = document.getCursor(); // try finding a value that matches the literal key, and if not found try finding a value // associated with the leaf key if the key is considered fully-qualified if (cursor.next(key)) { List<Object> list = new ArrayList<Object>(); do { list.addAll(ObjectHelper.listify(cursor.getValue())); } while (cursor.next(key)); output = ArrayHelper.toArray(list); } else if (IDataKey.isFullyQualified(key, literal)) { output = getAsArray(document, IDataKey.of(key, literal)); } cursor.destroy(); return output; } /** * Returns the value associated with the given fully-qualified key from the given IData document as an array. * * @param document An IData document. * @param key A fully-qualified key identifying the value in the given IData document to be returned. * @return The value associated with the given key in the given IData document as an array. */ private static Object[] getAsArray(IData document, IDataKey key) { Object[] output = null; if (document != null && key != null && key.size() > 0) { IDataCursor cursor = document.getCursor(); IDataKey.Part keyPart = key.remove(); if (key.size() > 0) { if (keyPart.hasArrayIndex()) { output = getAsArray(ArrayHelper.get(toIDataArray(IDataUtil.get(cursor, keyPart.getKey())), keyPart.getIndex()), key); } else if (keyPart.hasKeyIndex()) { output = getAsArray(toIData(get(document, keyPart.getKey(), keyPart.getIndex())), key); } else { output = getAsArray(IDataUtil.getIData(cursor, keyPart.getKey()), key); } } else { List<Object> list = new ArrayList<Object>(); if (keyPart.hasArrayIndex()) { Object value = IDataUtil.get(cursor, keyPart.getKey()); if (value != null) { if (value instanceof Object[] || value instanceof Table) { Object[] array = value instanceof Object[] ? (Object[])value : ((Table)value).getValues(); value = ArrayHelper.get(array, keyPart.getIndex()); } else { value = null; } } list.addAll(ObjectHelper.listify(value)); } else if (keyPart.hasKeyIndex()) { list.addAll(ObjectHelper.listify(get(document, keyPart.getKey(), keyPart.getIndex()))); } else { while (cursor.next(keyPart.getKey())) { list.addAll(ObjectHelper.listify(cursor.getValue())); } } output = ArrayHelper.toArray(list); } cursor.destroy(); } return output; } /** * Removes the given key and its associated value from the given cursor. * * @param cursor The cursor to remove the key from. * @param key The key to be removed. * @return The value that was associated with the key. */ public static Object remove(IDataCursor cursor, String key) { return remove(cursor, key, false); } /** * Removes the given key and its associated value from the given cursor. * * @param cursor The cursor to remove the key from. * @param key The key to be removed. * @param required Throws an exception if true and a non-null value is not associated with the given key. * @return The value that was associated with the key. */ public static Object remove(IDataCursor cursor, String key, boolean required) { if (cursor == null || key == null) return null; Object value = null; if (cursor.first(key)) { value = cursor.getValue(); cursor.delete(); } if (value == null && required) { throw new RuntimeException(new NoSuchFieldException(MessageFormat.format("Key \"{0}\" either does not exist or is associated with a null value", key))); } return value; } /** * Removes the given key and its associated value from the given cursor. * * @param cursor The cursor to remove the key from. * @param key The key to be removed. * @param klass The class the associated value is required to be an instance of. * @param <T> The class the associated value is required to be an instance of. * @return The value that was associated with the key. */ @SuppressWarnings("unchecked") public static <T> T remove(IDataCursor cursor, String key, Class<T> klass) { return remove(cursor, key, klass, false); } /** * Removes the given key and its associated value from the given cursor. * * @param cursor The cursor to remove the key from. * @param key The key to be removed. * @param klass The class the associated value is required to be an instance of. * @param required Throws an exception if true and a value with the required class is not associated with the * given key. * @param <T> The class the associated value is required to be an instance of. * @return The value that was associated with the key. */ @SuppressWarnings("unchecked") public static <T> T remove(IDataCursor cursor, String key, Class<T> klass, boolean required) { if (cursor == null || key == null) return null; T value = null; if (cursor.first(key)) { do { value = ObjectHelper.convert(cursor.getValue(), klass); } while(value == null && cursor.next(key)); cursor.delete(); } if (value == null && required) { throw new RuntimeException(new NoSuchFieldException(MessageFormat.format("Key \"{0}\" either does not exist or is not associated with a value compatible with the required class {1}", key, klass == null ? "null" : klass.getName()))); } return value; } /** * Removes the given key and its associated value from the given cursor. * * @param cursor The cursor to remove the key from. * @param key The key to be removed. * @param defaultValue The value to return if the associated value is null or the key does not exist. * @return The value that was associated with the key. */ @SuppressWarnings("unchecked") public static Object removeOrDefault(IDataCursor cursor, String key, Object defaultValue) { Object value = remove(cursor, key, false); return value == null ? defaultValue : value; } /** * Removes the given key and its associated value from the given cursor. * * @param cursor The cursor to remove the key from. * @param key The key to be removed. * @param klass The class the associated value is required to be an instance of. * @param defaultValue The value to return if the associated value is null or the key does not exist. * @param <T> The class the associated value is required to be an instance of. * @return The value that was associated with the key. */ @SuppressWarnings("unchecked") public static <T> T removeOrDefault(IDataCursor cursor, String key, Class<T> klass, T defaultValue) { T value = remove(cursor, key, klass, false); return value == null ? defaultValue : value; } /** * Returns the value associated with the given key from the IDataCursor. * * @param cursor The IDataCursor to add the key value association to. * @param key The key literal to be added. * @return The value associated with the given key, if one exists that is an instance of the given * class. */ public static Object get(IDataCursor cursor, String key) { return get(cursor, key, false); } /** * Returns the value associated with the given key from the IDataCursor. * * @param cursor The IDataCursor to add the key value association to. * @param key The key literal to be added. * @return The value associated with the given key, if one exists that is an instance of the given * class. */ public static Object get(IDataCursor cursor, String key, boolean required) { if (cursor == null || key == null) return null; Object value = null; if (cursor.first(key)) { value = cursor.getValue(); } if (value == null && required) { throw new RuntimeException(new NoSuchFieldException(MessageFormat.format("Key \"{0}\" either does not exist or is associated with null value", key))); } return value; } /** * Returns the value associated with the given key from the IDataCursor, if it is an instance of the given class. * * @param cursor The IDataCursor to add the key value association to. * @param key The key literal to be added. * @param klass The class the returned value is required to be an instance of. * @param <T> The class the returned value is required to be an instance of. * @return The value associated with the given key, if one exists that is an instance of the given * class. */ @SuppressWarnings("unchecked") public static <T> T get(IDataCursor cursor, String key, Class<T> klass) { return get(cursor, key, klass, false); } /** * Returns the value associated with the given key from the IDataCursor, if it is an instance of the given class. * * @param cursor The IDataCursor to add the key value association to. * @param key The key literal to be added. * @param klass The class the returned value is required to be an instance of. * @param required Throws an exception if true and a value with the required class is not associated with the * given key. * @param <T> The class the returned value is required to be an instance of. * @return The value associated with the given key, if one exists that is an instance of the given * class. */ @SuppressWarnings("unchecked") public static <T> T get(IDataCursor cursor, String key, Class<T> klass, boolean required) { if (cursor == null || key == null || klass == null) return null; T value = null; if (cursor.first(key)) { do { value = ObjectHelper.convert(cursor.getValue(), klass); } while(value == null && cursor.next(key)); } if (value == null && required) { throw new RuntimeException(new NoSuchFieldException(MessageFormat.format("Key \"{0}\" either does not exist or is not associated with a value compatible with the required class {1}", key, klass.getName()))); } return value; } /** * Returns the value associated with the given key from the IDataCursor, if it is an instance of the given class. * * @param cursor The IDataCursor to add the key value association to. * @param key The key literal to be added. * @param klass The class the returned value is required to be an instance of. * @param defaultValue The value to return if the associated value is null or the key does not exist. * @param <T> The class the returned value is required to be an instance of. * @return The value associated with the given key, if one exists that is an instance of the given * class. */ @SuppressWarnings("unchecked") public static <T> T getOrDefault(IDataCursor cursor, String key, Class<T> klass, T defaultValue) { T value = get(cursor, key, klass, false); return value == null ? defaultValue : value; } /** * Associates the given key with the given value in an IDataCursor. * * @param cursor The IDataCursor to add the key value association to. * @param key The key literal to be added. * @param value The value to be associated with the given key. */ public static <T> void put(IDataCursor cursor, String key, Class<T> klass, Object value) { put(cursor, key, ObjectHelper.convert(value, klass), true, true); } /** * Associates the given key with the given value in an IDataCursor. * * @param cursor The IDataCursor to add the key value association to. * @param key The key literal to be added. * @param value The value to be associated with the given key. */ public static void put(IDataCursor cursor, String key, Object value) { put(cursor, key, value, true); } /** * Associates the given key with the given value in an IDataCursor. * * @param cursor The IDataCursor to add the key value association to. * @param key The key literal to be added. * @param value The value to be associated with the given key. If null, no change is made to the cursor. * @param includeNullValue If true and the given value is null, no change is made to the cursor. In all other cases * the given value will be associated with the given key. */ public static void put(IDataCursor cursor, String key, Object value, boolean includeNullValue) { put(cursor, key, value, includeNullValue, true); } /** * Associates the given key with the given value in an IDataCursor. * * @param cursor The IDataCursor to add the key value association to. * @param key The key literal to be added. * @param value The value to be associated with the given key. If null, no change is made to the cursor. * @param includeNullValue If false and the given value is null, no change is made to the cursor. In all other * cases the given value will be associated with the given key. * @param includeEmptyValue If false and the given value is an empty array or empty string, no change is made to the * cursor. In all other cases the given value will be associated with the given key. */ public static void put(IDataCursor cursor, String key, Object value, boolean includeNullValue, boolean includeEmptyValue) { put(cursor, key, value, includeNullValue, includeEmptyValue, true); } /** * Associates the given key with the given value in an IDataCursor. * * @param cursor The IDataCursor to add the key value association to. * @param key The key literal to be added. * @param value The value to be associated with the given key. If null, no change is made to the cursor. * @param includeNullValue If false and the given value is null, no change is made to the cursor. In all other * cases the given value will be associated with the given key. * @param includeEmptyValue If false and the given value is an empty array or empty string, no change is made to the * cursor. In all other cases the given value will be associated with the given key. * @param replace If a value is already associated with the given key, replace it, rather than add a new * instance of the key. */ public static void put(IDataCursor cursor, String key, Object value, boolean includeNullValue, boolean includeEmptyValue, boolean replace) { if (!includeNullValue && value == null) return; if (!includeEmptyValue && ObjectHelper.isEmpty(value)) return; if (replace && cursor.first(key)) { cursor.setValue(value); } else { cursor.insertAfter(key, value); } } /** * Associates the given key with the given value in an IDataCursor. * * @param cursor The IDataCursor to add the key value association to. * @param key The key literal to be added. * @param value The value to be associated with the given key. If null, no change is made to the cursor. * @param klass The required class of the value; the value will be coerced to this class if possible. */ public static <T> void put(IDataCursor cursor, String key, Object value, Class<T> klass) { put(cursor, key, value, klass, true); } /** * Associates the given key with the given value in an IDataCursor. * * @param cursor The IDataCursor to add the key value association to. * @param key The key literal to be added. * @param value The value to be associated with the given key. If null, no change is made to the cursor. * @param klass The required class of the value; the value will be coerced to this class if possible. * @param includeNullValue If false and the given value is null, no change is made to the cursor. In all other * cases the given value will be associated with the given key. */ public static <T> void put(IDataCursor cursor, String key, Object value, Class<T> klass, boolean includeNullValue) { put(cursor, key, value, klass, includeNullValue, true); } /** * Associates the given key with the given value in an IDataCursor. * * @param cursor The IDataCursor to add the key value association to. * @param key The key literal to be added. * @param value The value to be associated with the given key. If null, no change is made to the cursor. * @param klass The required class of the value; the value will be coerced to this class if possible. * @param includeNullValue If false and the given value is null, no change is made to the cursor. In all other * cases the given value will be associated with the given key. * @param includeEmptyValue If false and the given value is an empty array or empty string, no change is made to the * cursor. In all other cases the given value will be associated with the given key. */ public static <T> void put(IDataCursor cursor, String key, Object value, Class<T> klass, boolean includeNullValue, boolean includeEmptyValue) { put(cursor, key, value, klass, includeNullValue, includeEmptyValue, true); } /** * Associates the given key with the given value in an IDataCursor. * * @param cursor The IDataCursor to add the key value association to. * @param key The key literal to be added. * @param value The value to be associated with the given key. If null, no change is made to the cursor. * @param klass The required class of the value; the value will be coerced to this class if possible. * @param includeNullValue If false and the given value is null, no change is made to the cursor. In all other * cases the given value will be associated with the given key. * @param includeEmptyValue If false and the given value is an empty array or empty string, no change is made to the * cursor. In all other cases the given value will be associated with the given key. * @param replace If a value is already associated with the given key, replace it, rather than add a new * instance of the key. */ public static <T> void put(IDataCursor cursor, String key, Object value, Class<T> klass, boolean includeNullValue, boolean includeEmptyValue, boolean replace) { put(cursor, key, ObjectHelper.convert(value, klass, false), includeNullValue, includeEmptyValue, replace); } /** * Associates the given key with the given value in an IDataCursor. * * @param cursor The IDataCursor to add the key value association to. * @param key The key literal to be added. * @param value The value to be associated with the given key. */ public static void putOrDefault(IDataCursor cursor, String key, Object value, Object defaultValue) { put(cursor, key, value == null ? defaultValue : value, true, true); } /** * Associates the given key with the given value in an IDataCursor. * * @param cursor The IDataCursor to add the key value association to. * @param key The key literal to be added. * @param value The value to be associated with the given key. */ public static <T> void putOrDefault(IDataCursor cursor, String key, Object value, Class<T> klass, T defaultValue) { put(cursor, key, value == null ? defaultValue : value, klass, true, true, true); } /** * Sets the value associated with the given key in the given IData document. Note that this method mutates the given * IData document in place. * * @param document An IData document. * @param key A simple or fully-qualified key identifying the value to be set. * @param value The value to be set. * @return The input IData document with the value set. */ public static IData put(IData document, String key, Object value) { return put(document, key, value, false); } /** * Sets the value associated with the given key in the given IData document. Note that this method mutates the given * IData document in place. * * @param document An IData document. * @param key A simple or fully-qualified key identifying the value to be set. * @param value The value to be set. * @param literal If true, the key will be treated as a literal key, rather than potentially as a fully-qualified * key. * @return The input IData document with the value set. */ public static IData put(IData document, String key, Object value, boolean literal) { return put(document, key, value, literal, true); } /** * Sets the value associated with the given key in the given IData document. Note that this method mutates the given * IData document in place. * * @param document An IData document. * @param key A simple or fully-qualified key identifying the value to be set. * @param value The value to be set. * @param literal If true, the key will be treated as a literal key, rather than potentially as a * fully-qualified key. * @param includeNull When true the value is set even when null, otherwise the value is only set when it is not * null. * @return The input IData document with the value set. */ public static IData put(IData document, String key, Object value, boolean literal, boolean includeNull) { return put(document, IDataKey.of(key, literal), value, includeNull); } /** * Sets the value associated with the given key in the given IData document. Note that this method mutates the given * IData document in place. * * @param document An IData document. * @param key A fully-qualified key identifying the value to be set. * @param value The value to be set. * @param includeNull When true the value is set even when null, otherwise the value is only set when it is * not null. * @return The input IData document with the value set. */ private static IData put(IData document, IDataKey key, Object value, boolean includeNull) { if (!includeNull && value == null) return document; if (key != null && key.size() > 0) { if (document == null) document = IDataFactory.create(); IDataCursor cursor = document.getCursor(); IDataKey.Part keyPart = key.remove(); if (key.size() > 0) { if (keyPart.hasArrayIndex()) { IData[] array = IDataUtil.getIDataArray(cursor, keyPart.getKey()); IData child = null; try { child = ArrayHelper.get(array, keyPart.getIndex()); } catch(ArrayIndexOutOfBoundsException ex) { // ignore exception } value = ArrayHelper.put(array, put(child, key, value, includeNull), keyPart.getIndex(), IData.class); } else if (keyPart.hasKeyIndex()) { value = put(toIData(get(document, keyPart.getKey(), keyPart.getIndex())), key, value, includeNull); } else { value = put(IDataUtil.getIData(cursor, keyPart.getKey()), key, value, includeNull); } } else if (keyPart.hasArrayIndex()) { Class klass = Object.class; if (value != null) { if (value instanceof String) { klass = String.class; } else if (value instanceof IData) { klass = IData.class; } } value = ArrayHelper.put(IDataUtil.getObjectArray(cursor, keyPart.getKey()), value, keyPart.getIndex(), klass); } if (keyPart.hasKeyIndex()) { put(document, keyPart.getKey(), keyPart.getIndex(), value); } else { IDataUtil.put(cursor, keyPart.getKey(), value); } cursor.destroy(); } return document; } /** * Sets the value associated with the given nth key in the given IData document. Note that this method mutates the * given IData document in place. * * @param document The IData document to set the key's associated value in. * @param key The key whose value is to be set. * @param n Determines which occurrence of the key to set the value for. * @param value The value to be set. * @return The IData document with the given nth key set to the given value. */ private static IData put(IData document, String key, int n, Object value) { if (document == null || key == null || n < 0) return null; IDataCursor cursor = document.getCursor(); for (int i = 0; i < n; i++) { if (!cursor.next(key)) cursor.insertAfter(key, null); } cursor.insertAfter(key, value); cursor.destroy(); return document; } /** * Converts the given object to a Map object, if possible. * * @param object The object to be converted. * @return A Map representation of the given object if its type is compatible (IData, IDataCodable, * IDataPortable, ValuesCodable), otherwise null. */ private static Map<String, Object> toMap(Object object) { if (object == null) return null; Map<String, Object> output = null; if (object instanceof IData) { output = toMap((IData)object); } else if (object instanceof IDataCodable) { output = toMap((IDataCodable)object); } else if (object instanceof IDataPortable) { output = toMap((IDataPortable)object); } else if (object instanceof ValuesCodable) { output = toMap((ValuesCodable)object); } return output; } /** * Converts an IData object to a Map object. * * @param document An IData object to be converted. * @return A Map representation of the given IData object. */ public static Map<String, Object> toMap(IData document) { if (document == null) return null; IDataCursor cursor = document.getCursor(); int size = IDataUtil.size(cursor); cursor.destroy(); Map<String, Object> output = new java.util.LinkedHashMap<String, Object>(size); for (Map.Entry<String, Object> entry : IDataMap.of(document)) { String key = entry.getKey(); Object value = entry.getValue(); if (value instanceof IData[] || value instanceof Table || value instanceof IDataCodable[] || value instanceof IDataPortable[] || value instanceof ValuesCodable[]) { value = toList(value); } else if (value instanceof IData || value instanceof IDataCodable || value instanceof IDataPortable || value instanceof ValuesCodable) { value = toMap(value); } output.put(key, value); } cursor.destroy(); return output; } /** * Converts an IDataCodable object to a Map object. * * @param document An IDataCodable object to be converted. * @return A Map representation of the given IDataCodable object. */ public static Map<String, Object> toMap(IDataCodable document) { return toMap(toIData(document)); } /** * Converts an IDataPortable object to a Map object. * * @param document An IDataPortable object to be converted. * @return A Map representation of the given IDataPortable object. */ public static Map<String, Object> toMap(IDataPortable document) { return toMap(toIData(document)); } /** * Converts an ValuesCodable object to a Map object. * * @param document An ValuesCodable object to be converted. * @return A Map representation of the given ValuesCodable object. */ public static Map<String, Object> toMap(ValuesCodable document) { return toMap(toIData(document)); } /** * Converts an object to a List object, if possible. * * @param object An object to be converted. * @return A List representation of the given object, if the object was a compatible type (IData[], * Table, IDataCodable[], IDataPortable[], ValuesCodable[]), otherwise null. */ private static List<Map<String, Object>> toList(Object object) { if (object == null) return null; List<Map<String, Object>> output = null; if (object instanceof IData[]) { output = toList((IData[])object); } else if (object instanceof Table) { output = toList((Table)object); } else if (object instanceof IDataCodable[]) { output = toList((IDataCodable[])object); } else if (object instanceof IDataPortable[]) { output = toList((IDataPortable[])object); } else if (object instanceof ValuesCodable[]) { output = toList((ValuesCodable[])object); } return output; } /** * Converts an IData[] object to a List object. * * @param array An IData[] object to be converted. * @return A List representation of the given IData[] object. */ public static List<Map<String, Object>> toList(IData[] array) { if (array == null) return null; List<Map<String, Object>> output = new java.util.ArrayList<Map<String, Object>>(array.length); for (IData item : array) { output.add(toMap(item)); } return output; } /** * Converts a Table object to a List object. * * @param table An Table object to be converted. * @return A List representation of the given Table object. */ public static List<Map<String, Object>> toList(Table table) { return toList(toIDataArray(table)); } /** * Converts an IDataCodable[] object to a List object. * * @param array An IDataCodable[] object to be converted. * @return A List representation of the given IDataCodable[] object. */ public static List<Map<String, Object>> toList(IDataCodable[] array) { return toList(toIDataArray(array)); } /** * Converts an IDataPortable[] object to a List object. * * @param array An IDataPortable[] object to be converted. * @return A List representation of the given IDataPortable[] object. */ public static List<Map<String, Object>> toList(IDataPortable[] array) { return toList(toIDataArray(array)); } /** * Converts an ValuesCodable[] object to a java.util.List object. * * @param array An ValuesCodable[] object to be converted. * @return A List representation of the given ValuesCodable[] object. */ public static List<Map<String, Object>> toList(ValuesCodable[] array) { return toList(toIDataArray(array)); } /** * Returns an IData representation of the given object, if possible. * * @param object The object to convert. * @return An IData representing the given object if its type is compatible (IData, IDataCodable, * IDataPortable, ValuesCodable), otherwise null. */ public static IData toIData(Object object) { if (object == null) return null; IData output = null; if (object instanceof IData) { output = (IData)object; } else if (object instanceof IDataCodable) { output = toIData((IDataCodable)object); } else if (object instanceof IDataPortable) { output = toIData((IDataPortable)object); } else if (object instanceof ValuesCodable) { output = toIData((ValuesCodable)object); } else if (object instanceof Map) { output = toIData((Map)object); } return output; } /** * Returns an IData representation of the given IDataCodable object. * * @param document The IDataCodable object to be converted to an IData object. * @return An IData representation of the give IDataCodable object. */ public static IData toIData(IDataCodable document) { if (document == null) return null; return document.getIData(); } /** * Returns an IData representation of the given IDataPortable object. * * @param document The IDataPortable object to be converted to an IData object. * @return An IData representation of the give IDataPortable object. */ public static IData toIData(IDataPortable document) { if (document == null) return null; return document.getAsData(); } /** * Returns an IData representation of the given ValuesCodable object. * * @param document The ValuesCodable object to be converted to an IData object. * @return An IData representation of the give ValuesCodable object. */ public static IData toIData(ValuesCodable document) { if (document == null) return null; return document.getValues(); } /** * Returns an IData representation of the given Map. * * @param map The Map to be converted. * @return An IData representation of the given map. */ public static IData toIData(Map map) { if (map == null) return null; IData output = IDataFactory.create(); IDataCursor cursor = output.getCursor(); for (Object key : map.keySet()) { if (key != null) { put(output, key.toString(), normalize(map.get(key)), true); } } cursor.destroy(); return output; } /** * Returns an IData[] representation of the given object, if possible. * * @param object The Table object to be converted to an IData[] object. * @return An IData[] representation of the give object if the object was a compatible type (IData[], * Table, IDataCodable[], IDataPortable[], ValuesCodable[]), otherwise null. */ public static IData[] toIDataArray(Object object) { if (object == null) return null; IData[] output = null; if (object instanceof Table) { output = toIDataArray((Table)object); } else if (object instanceof IDataCodable[]) { output = toIDataArray((IDataCodable[])object); } else if (object instanceof IDataPortable[]) { output = toIDataArray((IDataPortable[])object); } else if (object instanceof ValuesCodable[]) { output = toIDataArray((ValuesCodable[])object); } else if (object instanceof Map[]) { output = toIDataArray((Map[])object); } else if (object instanceof IData[]) { output = (IData[])object; } return output; } /** * Returns an IData[] representation of the given Table object. * * @param table The Table object to be converted to an IData[] object. * @return An IData[] representation of the give Table object. */ public static IData[] toIDataArray(Table table) { if (table == null) return null; return table.getValues(); } /** * Returns an IData[] representation of the given IDataCodable[] object. * * @param array The IDataCodable[] object to be converted to an IData[] object. * @return An IData[] representation of the give IDataCodable[] object. */ public static IData[] toIDataArray(IDataCodable[] array) { if (array == null) return null; IData[] output = new IData[array.length]; for (int i = 0; i < array.length; i++) { output[i] = toIData(array[i]); } return output; } /** * Returns an IData[] representation of the given IDataPortable[] object. * * @param array The IDataPortable[] object to be converted to an IData[] object. * @return An IData[] representation of the give IDataPortable[] object. */ public static IData[] toIDataArray(IDataPortable[] array) { if (array == null) return null; IData[] output = new IData[array.length]; for (int i = 0; i < array.length; i++) { output[i] = toIData(array[i]); } return output; } /** * Returns an IData[] representation of the given ValuesCodable[] object. * * @param array The ValuesCodable[] object to be converted to an IData[] object. * @return An IData[] representation of the give ValuesCodable[] object. */ public static IData[] toIDataArray(ValuesCodable[] array) { if (array == null) return null; IData[] output = new IData[array.length]; for (int i = 0; i < array.length; i++) { output[i] = toIData(array[i]); } return output; } /** * Returns an IData[] representation of the given Map[] object. * * @param array The Map[] object to be converted to an IData[] object. * @return An IData[] representation of the give Map[] object. */ public static IData[] toIDataArray(Map[] array) { if (array == null) return null; IData[] output = new IData[array.length]; for (int i = 0; i < array.length; i++) { output[i] = toIData(array[i]); } return output; } /** * Returns the union set of keys present in every item in the given IData[] document list. * * @param array An IData[] to retrieve the union set of keys from. * @return The union set of keys from the given IData[]. */ public static String[] getKeys(IData[] array) { return getKeys(array, (Pattern)null); } /** * Returns the union set of keys present in every item in the given IData[] document list that match the given * regular expression pattern. * * @param array An IData[] to retrieve the union set of keys from. * @param patternString A regular expression pattern the returned keys must match. * @return The union set of keys from the given IData[]. */ public static String[] getKeys(IData[] array, String patternString) { return getKeys(array, patternString == null ? null : Pattern.compile(patternString)); } /** * Returns the union set of keys present in every item in the given IData[] document list that match the given * regular expression pattern. * * @param array An IData[] to retrieve the union set of keys from. * @param pattern A regular expression pattern the returned keys must match. * @return The union set of keys from the given IData[]. */ public static String[] getKeys(IData[] array, Pattern pattern) { java.util.Set<String> keys = new java.util.LinkedHashSet<String>(); if (array != null) { for (IData document : array) { if (document != null) { for (Map.Entry<String, Object> entry : IDataMap.of(document)) { String key = entry.getKey(); if (pattern == null) { keys.add(key); } else { Matcher matcher = pattern.matcher(key); if (matcher.matches()) keys.add(key); } } } } } return keys.toArray(new String[keys.size()]); } /** * Converts an IData document to an IData[] document list with each item representing each key value tuple from the * given document. * * @param document An IData document to pivot. * @param recurse Whether to recursively pivot embedded IData objects. * @return The given IData document pivoted. */ public static IData[] pivot(IData document, boolean recurse) { if (document == null) return null; IDataCursor cursor = document.getCursor(); List<IData> pivot = new ArrayList<IData>(); while (cursor.next()) { String key = cursor.getKey(); Object value = cursor.getValue(); if (recurse && value != null) { if (value instanceof IData[] || value instanceof Table || value instanceof IDataCodable[] || value instanceof IDataPortable[] || value instanceof ValuesCodable[]) { IData[] array = toIDataArray(value); if (array != null) { List<IData[]> list = new ArrayList<IData[]>(array.length); for (int i = 0; i < array.length; i++) { list.add(pivot(array[i], recurse)); } value = list.toArray(new IData[0][0]); } } else if (value instanceof IData || value instanceof IDataCodable || value instanceof IDataPortable || value instanceof ValuesCodable) { value = pivot(toIData(value), recurse); } } IData item = IDataFactory.create(); IDataCursor ic = item.getCursor(); IDataUtil.put(ic, "key", key); IDataUtil.put(ic, "value", value); ic.destroy(); pivot.add(item); } return pivot.toArray(new IData[pivot.size()]); } /** * Returns an IData document where the keys are the values associated with given pivot key from the given IData[] * document list, and the values are the IData[] document list items associated with each pivot key. * * @param array The IData[] to be pivoted. * @param delimiter The delimiter to use when building a compound key. * @param pivotKeys The keys to pivot on. * @return The IData document representing the pivoted IData[]. */ public static IData pivot(IData[] array, String delimiter, String... pivotKeys) { if (array == null || pivotKeys == null || pivotKeys.length == 0) return null; if (delimiter == null) delimiter = "/"; IData output = IDataFactory.create(); outer: for (IData item : array) { if (item != null) { StringBuilder buffer = new StringBuilder(); for (int i = 0; i < pivotKeys.length; i++) { Object value = get(item, pivotKeys[i]); if (value == null) { continue outer; } else { buffer.append(value.toString()); } if (i < (pivotKeys.length - 1)) buffer.append(delimiter); } String key = buffer.toString(); if (get(output, key) == null) put(output, key, item); } } return output; } /** * Returns a new IData document containing all denormalized items from the given input IData document. * * Array items are denormalized to individual items in the output IData document with their keys suffixed * with "[n]" where n is the item's array index. * * Items in nested IData documents are denormalized to items included directly in the output IData document * with their keys prefixed with the associated fully-qualified nested path. * * @param input An IData document to be denormalized. * @return The denormalized IData document. */ public static IData denormalize(IData input) { if (input == null) return null; IData output = IDataFactory.create(); IDataCursor inputCursor = input.getCursor(); IDataCursor outputCursor = output.getCursor(); denormalize(inputCursor, outputCursor, null); inputCursor.destroy(); outputCursor.destroy(); return output; } /** * Inserts each item of the given input IDataCursor to the end of the given output IDataCursor with the keys * denormalized to a fully-qualified key. * * @param inputCursor The cursor to source items to be denormalized from. * @param outputCursor The cursor to insert the denormalized items into. * @param path The original path to the IData document being denormalized from the inputCursor, or null. */ private static void denormalize(IDataCursor inputCursor, IDataCursor outputCursor, String path) { if (inputCursor == null || outputCursor == null) return; while(inputCursor.next()) { String key = inputCursor.getKey(); Object value = inputCursor.getValue(); if (value instanceof IData[] || value instanceof Table || value instanceof IDataCodable[] || value instanceof IDataPortable[] || value instanceof ValuesCodable[]) { denormalize(toIDataArray(value), outputCursor, path == null ? key : path + "/" + key); } else if (value instanceof IData || value instanceof IDataCodable || value instanceof IDataPortable || value instanceof ValuesCodable) { IData child = toIData(value); IDataCursor childCursor = child.getCursor(); denormalize(childCursor, outputCursor, path == null ? key : path + "/" + key); childCursor.destroy(); } else if (value instanceof Object[][]) { denormalize((Object[][])value, outputCursor, path == null ? key : path + "/" + key); } else if (value instanceof Object[]) { denormalize((Object[])value, outputCursor, path == null ? key : path + "/" + key); } else { outputCursor.insertAfter(path == null ? key : path + "/" + key, value); } } inputCursor.destroy(); outputCursor.destroy(); } /** * Inserts each item of the given array to the end of the given IDataCursor with the given * key suffixed with "[n]" where n is the item's array index. * * @param array An array to be denormalized into the given IDataCursor. * @param outputCursor The cursor to insert the denormalized array items into. * @param key The original key associated with this array in the IData document being denormalized. */ private static void denormalize(IData[] array, IDataCursor outputCursor, String key) { if (array == null || array.length == 0 || outputCursor == null || key == null) return; for (int i = 0; i < array.length; i++) { IData child = array[i]; if (child != null) { IDataCursor inputCursor = child.getCursor(); denormalize(inputCursor, outputCursor, key + "[" + i + "]"); inputCursor.destroy(); } } } /** * Inserts each item of the given two dimensional array to the end of the given IDataCursor with the given * key suffixed with "[n][m]" where n and m are the item's array indexes. * * @param array An array to be denormalized into the given IDataCursor. * @param outputCursor The cursor to insert the denormalized array items into. * @param key The original key associated with this array in the IData document being denormalized. */ private static void denormalize(Object[][] array, IDataCursor outputCursor, String key) { if (array == null || array.length == 0 || outputCursor == null || key == null) return; for (int i = 0; i < array.length; i++) { denormalize(array[i], outputCursor, key + "[" + i + "]"); } } /** * Inserts each item of the given array to the end of the given IDataCursor with the given * key suffixed with "[n]" where n is the item's array index. * * @param array An array to be denormalized into the given IDataCursor. * @param outputCursor The cursor to insert the denormalized array items into. * @param key The original key associated with this array in the IData document being denormalized. */ private static void denormalize(Object[] array, IDataCursor outputCursor, String key) { if (array == null || array.length == 0 || outputCursor == null || key == null) return; for (int i = 0; i < array.length; i++) { outputCursor.insertAfter(key + "[" + i + "]", array[i]); } outputCursor.destroy(); } /** * Sorts the given IData document by its keys in natural ascending order. * * @param document An IData document to be sorted by its keys. * @return A new IData document which is duplicate of the given input IData document but with its keys * sorted in natural ascending order. */ public static IData sort(IData document) { return sort(document, true); } /** * Sorts the given IData document by its keys in natural ascending order. * * @param document An IData document to be sorted by its keys. * @param recurse A boolean which when true will also recursively sort nested IData document and IData[] document * lists. * @return A new IData document which is duplicate of the given input IData document but with its keys * sorted in natural ascending order. */ public static IData sort(IData document, boolean recurse) { return sort(document, recurse, false); } /** * Sorts the given IData document by its keys in natural ascending or descending order. * * @param document An IData document to be sorted by its keys. * @param recurse A boolean which when true will also recursively sort nested IData document and IData[] * document lists. * @param descending Whether to sort in descending or ascending order. * @return A new IData document which is duplicate of the given input IData document but with its keys * sorted in natural ascending order. */ public static IData sort(IData document, boolean recurse, boolean descending) { if (document == null) return null; String[] keys = ArrayHelper.sort(getKeys(document), descending); IData output = IDataFactory.create(); IDataCursor ic = document.getCursor(); IDataCursor oc = output.getCursor(); for (int i = 0; i < keys.length; i++) { boolean result; if (i > 0 && keys[i].equals(keys[i - 1])) { result = ic.next(keys[i]); } else { result = ic.first(keys[i]); } if (result) { Object value = ic.getValue(); if (recurse) { if (value instanceof IData[] || value instanceof Table || value instanceof IDataCodable[] || value instanceof IDataPortable[] || value instanceof ValuesCodable[]) { IData[] array = toIDataArray(value); for (int j = 0; j < array.length; j++) { array[j] = sort(array[j], recurse); } value = array; } else if (value instanceof IData || value instanceof IDataCodable || value instanceof IDataPortable || value instanceof ValuesCodable) { value = sort(toIData(value), recurse); } } oc.insertAfter(keys[i], value); } } ic.destroy(); oc.destroy(); return output; } /** * Returns a new IData[] array with all elements sorted in ascending order by the values associated with the given * key. * * @param array An IData[] array to be sorted. * @param key The key to use to sort the array. * @return A new IData[] array sorted by the given key. */ public static IData[] sort(IData[] array, String key) { return sort(array, key, true); } /** * Returns a new IData[] array with all elements sorted in either ascending or descending order by the values * associated with the given key. * * @param array An IData[] array to be sorted. * @param key The key to use to sort the array. * @param ascending When true, the array will be sorted in ascending order, otherwise it will be sorted in * descending order. * @return A new IData[] array sorted by the given key. */ public static IData[] sort(IData[] array, String key, boolean ascending) { String[] keys = null; if (key != null) { keys = new String[1]; keys[0] = key; } return sort(array, keys, ascending); } /** * Returns a new IData[] array with all elements sorted in ascending order by the values associated with the given * keys. * * @param array An IData[] array to be sorted. * @param keys The list of keys in order of precedence to use to sort the array. * @return A new IData[] array sorted by the given keys. */ public static IData[] sort(IData[] array, String[] keys) { return sort(array, keys, true); } /** * Returns a new IData[] array with all elements sorted in either ascending or descending order by the values * associated with the given keys. * * @param array An IData[] array to be sorted. * @param keys The list of keys in order of precedence to use to sort the array. * @param ascending When true, the array will be sorted in ascending order, otherwise it will be sorted in * descending order. * @return A new IData[] array sorted by the given keys. */ public static IData[] sort(IData[] array, String[] keys, boolean ascending) { if (array == null || array.length < 2 || keys == null || keys.length == 0) return array; IDataComparisonCriterion[] criteria = new IDataComparisonCriterion[keys.length]; for (int i = 0; i < keys.length; i++) { criteria[i] = new IDataComparisonCriterion(keys[i], !ascending); } return sort(array, criteria); } /** * Returns a new IData[] array with all elements sorted according to the specified criteria. * * @param array An IData[] array to be sorted. * @param criteria One or more sort criteria. * @return A new IData[] array sorted by the given criteria. */ public static IData[] sort(IData[] array, IDataComparisonCriterion... criteria) { if (array == null) return null; if (criteria != null && criteria.length > 0) { array = ArrayHelper.sort(array, new CriteriaBasedIDataComparator(criteria)); } else { array = Arrays.copyOf(array, array.length); } return array; } /** * Returns a new IData[] array with all elements sorted according to the specified criteria. * * @param array An IData[] array to be sorted. * @param criteria One or more sort criteria specified as an IData[]. * @return A new IData[] array sorted by the given criteria. */ public static IData[] sort(IData[] array, IData[] criteria) { return sort(array, IDataComparisonCriterion.of(criteria)); } /** * Returns a new IData[] array with all elements sorted according to the specified criteria. * * @param array An IData[] array to be sorted. * @param comparator An IDataComparator object used to determine element ordering. * @return A new IData[] array sorted by the given criteria. */ public static IData[] sort(IData[] array, IDataComparator comparator) { if (array == null) return null; return ArrayHelper.sort(array, comparator); } /** * Returns the values associated with the given key from each item in the given IData[] document list. * * @param array An IData[] array to return values from. * @param key A fully-qualified key identifying the values to return. * @param defaultValue The default value returned if the key does not exist. * @return The values associated with the given key from each IData item in the given array. */ public static Object[] getValues(IData[] array, String key, Object defaultValue) { if (array == null || key == null) return null; List<Object> list = new ArrayList<Object>(array.length); for (IData item : array) { list.add(get(item, key, defaultValue)); } return ArrayHelper.normalize(list); } /** * Converts all the keys in the given IData document to lower case. * * @param input The IData whose keys are to be converted to lower case. * @return The given IData duplicated with all keys converted to lower case. */ public static IData keysToLowerCase(IData input) { return keysToLowerCase(input, true); } /** * Converts all the keys in the given IData document to lower case. * * @param input The IData whose keys are to be converted to lower case. * @param recurse Whether child IData and IData[] objects should also have their keys converted to lower case. * @return The given IData duplicated with all keys converted to lower case. */ public static IData keysToLowerCase(IData input, boolean recurse) { if (input == null) return null; IData output = IDataFactory.create(); IDataCursor inputCursor = input.getCursor(); IDataCursor outputCursor = output.getCursor(); while(inputCursor.next()) { String key = inputCursor.getKey(); Object value = inputCursor.getValue(); if (recurse) { if (value instanceof IData[] || value instanceof Table || value instanceof IDataCodable[] || value instanceof IDataPortable[] || value instanceof ValuesCodable[]) { value = keysToLowerCase(toIDataArray(value), recurse); } else if (value instanceof IData || value instanceof IDataCodable || value instanceof IDataPortable || value instanceof ValuesCodable) { value = keysToLowerCase(toIData(value), recurse); } } outputCursor.insertAfter(key.toLowerCase(), value); } inputCursor.destroy(); outputCursor.destroy(); return output; } /** * Converts all the keys in the given IData[] document list to lower case. * * @param input The IData[] whose keys are to be converted to lower case. * @return The given IData[] duplicated with all keys converted to lower case. */ public static IData[] keysToLowerCase(IData[] input) { return keysToLowerCase(input, true); } /** * Converts all the keys in the given IData[] document list to lower case. * * @param input The IData[] whose keys are to be converted to lower case. * @param recurse Whether child IData and IData[] objects should also have their keys converted to lower case. * @return The given IData[] duplicated with all keys converted to lower case. */ public static IData[] keysToLowerCase(IData[] input, boolean recurse) { if (input == null) return null; IData[] output = new IData[input.length]; for (int i = 0; i < input.length; i++) { output[i] = keysToLowerCase(input[i], recurse); } return output; } /** * Groups the given IData[] by the given keys. * * @param array The IData[] to be grouped. * @param keys The keys to group items by. * @return The grouped IData[]. */ public static IData[] group(IData[] array, String... keys) { Map<CompoundKey, List<IData>> groups = group(array, IDataComparisonCriterion.of(keys)); List<IData> result; if (groups.size() == 0) { result = new ArrayList<IData>(1); IData document = IDataFactory.create(); IDataCursor cursor = document.getCursor(); IDataUtil.put(cursor, "group", IDataFactory.create()); IDataUtil.put(cursor, "items", array); cursor.destroy(); result.add(document); } else { result = new ArrayList<IData>(groups.size()); for (Map.Entry<CompoundKey, List<IData>> entry : groups.entrySet()) { CompoundKey key = entry.getKey(); List<IData> items = entry.getValue(); IData group = IDataFactory.create(); IDataCursor cursor = group.getCursor(); IDataUtil.put(cursor, "group", key.getIData()); IDataUtil.put(cursor, "items", items.toArray(new IData[items.size()])); cursor.destroy(); result.add(group); } } return result.toArray(new IData[result.size()]); } /** * Performs a multi-level grouping of the given IData[] by the given criteria. * * @param array The IData[] to be grouped. * @param criteria The multi-level grouping criteria. * @return The grouped IData[]. */ public static IData[] group(IData[] array, IData criteria) { if (array == null) return null; List<IData> result; if (criteria == null) { result = new ArrayList<IData>(1); IData document = IDataFactory.create(); IDataCursor cursor = document.getCursor(); IDataUtil.put(cursor, "by", IDataFactory.create()); IDataUtil.put(cursor, "items", array); cursor.destroy(); result.add(document); } else { IDataCursor criteriaCursor = criteria.getCursor(); IData[] by = IDataUtil.getIDataArray(criteriaCursor, "by"); IData then = IDataUtil.getIData(criteriaCursor, "then"); criteriaCursor.destroy(); Map<CompoundKey, List<IData>> groups = group(array, IDataComparisonCriterion.of(by)); result = new ArrayList<IData>(groups.size()); for (Map.Entry<CompoundKey, List<IData>> entry : groups.entrySet()) { CompoundKey key = entry.getKey(); List<IData> value = entry.getValue(); IData[] items = value.toArray(new IData[value.size()]); IData group = IDataFactory.create(); IDataCursor cursor = group.getCursor(); IDataUtil.put(cursor, "by", key.getIData()); IDataUtil.put(cursor, "items", items); if (then != null) IDataUtil.put(cursor, "then", group(items, then)); cursor.destroy(); result.add(group); } } return result.toArray(new IData[result.size()]); } /** * Groups the given IData[] by the given keys. * * @param array The IData[] to be grouped. * @param criteria The criteria to group items by. * @return A Map containing the groups and their items. */ public static Map<CompoundKey, List<IData>> group(IData[] array, IDataComparisonCriterion[] criteria) { Map<CompoundKey, List<IData>> groups = new TreeMap<CompoundKey, List<IData>>(); if (array != null && criteria != null || criteria.length == 0) { for (IData item : array) { if (item != null) { CompoundKey key = new CompoundKey(criteria, item); List<IData> list = groups.get(key); if (list == null) { list = new ArrayList<IData>(); groups.put(key, list); } list.add(item); } } } return groups; } /** * Returns a new IData[] document list that only contains unique IData objects from the input IData[] document * list. * * @param array The IData[] document list to find the unique set of. * @return A new IData[] document list only containing the first occurrence of each IData containing a * distinct set of values. */ public static IData[] unique(IData[] array) { return unique(array, (String[])null); } /** * Returns a new IData[] document list that only contains unique IData objects from the input IData[] document list, * where uniqueness is determined by the values associated with the given list of keys. * * @param array The IData[] document list to find the unique set of. * @param keys The keys whose associated values will be used to determine uniqueness. If not specified, all keys * will be used to determine uniqueness. * @return A new IData[] document list only containing the first occurrence of each IData containing a distinct * set of values associated with the given list of keys. */ public static IData[] unique(IData[] array, String... keys) { IData[] output = null; if (array != null) { if (array.length <= 1) { output = Arrays.copyOf(array, array.length); } else { if (keys == null || keys.length == 0) keys = getKeys(array); Map<CompoundKey, IData> set = new TreeMap<CompoundKey, IData>(); for (IData item : array) { if (item != null) { CompoundKey key = new CompoundKey(keys, item); if (!set.containsKey(key)) set.put(key, item); } } output = set.values().toArray(new IData[set.size()]); } } return output; } /** * Represents a compound key which can be used for grouping IData documents together. */ private static class CompoundKey implements Comparable<CompoundKey>, IDataCodable { /** * The comparator used for comparison with other compound keys. */ private CriteriaBasedIDataComparator comparator; /** * The IData document containing the values referenced by the compound key. */ private IData document; /** * Constructs a new compound key for the given list of keys and their associated values from the given IData * document. * * @param keys The keys which together form this compound key. * @param document The IData document containing the values associated with the given keys. */ public CompoundKey(String[] keys, IData document) { this(new CriteriaBasedIDataComparator(IDataComparisonCriterion.of(keys)), document); } /** * Constructs a new compound key for the given comparison criteria and their associated values from the given * IData document. * * @param criteria The comparison criteria which together form this compound key. * @param document The IData document containing the values associated with the given keys. */ public CompoundKey(IDataComparisonCriterion[] criteria, IData document) { this(new CriteriaBasedIDataComparator(criteria), document); } /** * Constructs a new compound key for the given comparison criteria and their associated values from the given * IData document. * * @param criteria The comparison criteria which together form this compound key. * @param document The IData document containing the values associated with the given keys. */ public CompoundKey(List<IDataComparisonCriterion> criteria, IData document) { this(new CriteriaBasedIDataComparator(criteria), document); } /** * Constructs a new compound key for the given comparison criteria and their associated values from the given * IData document. * * @param comparator The comparator used for comparison with other compound keys. * @param document The IData document containing the values associated with the given keys. */ private CompoundKey(CriteriaBasedIDataComparator comparator, IData document) { if (comparator == null) throw new NullPointerException("comparator must not be null"); if (document == null) throw new NullPointerException("document must not be null"); this.comparator = comparator; this.document = document; } /** * Returns the IData document containing the values used by this compound key. * * @return The IData document containing the values used by this compound key. */ public IData getDocument() { return this.document; } /** * Sets the IData document containing the values used for comparison by this compound key. * * @param document The IData document containing the values to be used for comparison by this compound key. */ public void setDocument(IData document) { if (document == null) throw new NullPointerException("document must not be null"); this.document = document; } /** * Returns the comparator used for comparisons by this compound key. * * @return The comparator used for comparisons by this compound key. */ public CriteriaBasedIDataComparator getComparator() { return this.comparator; } /** * Sets the comparator to be used by this compound key in comparisons. * * @param comparator The comparator to be used by this compound key in comparisons. */ public void setComparator(CriteriaBasedIDataComparator comparator) { this.comparator = comparator; } /** * Returns an IData representation of this compound key. * * @return An IData representation of this compound key. */ public IData getIData() { IData output = IDataFactory.create(); for (IDataComparisonCriterion criterion : comparator.getCriteria()) { IDataHelper.put(output, criterion.getKey(), IDataHelper.get(document, criterion.getKey())); } return output; } /** * This method is not implemented. * * @param document Not used. * @throws UnsupportedOperationException as this method is not implemented. */ public void setIData(IData document) { throw new UnsupportedOperationException("method not implemented"); } /** * Compares this compound key with another compound key. * * @param other The other key to be compared with. * @return 0 if the two keys are equal, less than 0 if this key is less than the other key, * greater than 0 if this key is greater than the other key. */ public int compareTo(CompoundKey other) { if (other == null) return 1; return comparator.compare(this.document, other.getIData()); } /** * Returns true if this object is equal to the other object. * * @param other The object to compare for equality with. * @return True if this object is equal to the other object. */ public boolean equals(Object other) { boolean result = false; if (other instanceof CompoundKey) { result = this.compareTo((CompoundKey)other) == 0; } return result; } } }