/* * The MIT License (MIT) * * Copyright (c) 2016 Lachlan Dowding * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package permafrost.tundra.flow.variable; 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 permafrost.tundra.data.IDataHelper; import java.util.EnumSet; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * A collection of convenience methods for performing dynamic variable substitution. */ public final class SubstitutionHelper { /** * A regular expression pattern for detecting variable substitution statements in strings. */ protected static final Pattern SUBSTITUTION_PATTERN = Pattern.compile("%([^%]+)%"); /** * Disallow instantiation of this class. */ private SubstitutionHelper() {} /** * Returns a regular expression matcher which matches percent-delimited variable substitution statements in the * given string. * * @param substitutionString The string to match against. * @return A regular expression matcher which returns match results for variable substitution * statements. */ public static Matcher matcher(String substitutionString) { return SUBSTITUTION_PATTERN.matcher(substitutionString); } /** * Performs variable substitution on the given string by replacing all occurrences of substrings matching "%key%" * with the associated value from the given scope. * * @param substitutionString A string to perform variable substitution on. * @param scope An IData document containing the variables being substituted. * @return The string after variable substitution has been performed. * @throws ServiceException If an error occurs retrieving a global variable. */ public static String substitute(String substitutionString, IData scope) throws ServiceException { return substitute(substitutionString, null, scope); } /** * Performs variable substitution on the given string by replacing all occurrences of substrings matching "%key%" * with the associated value from the given scope; if the key has no value, the given defaultValue (if not null) is * used instead. * * @param substitutionString A string to perform variable substitution on. * @param defaultValue A default value to be substituted when the variable being substituted has a value of * null. * @param scope An IData document containing the variables being substituted. * @return The string after variable substitution has been performed. * @throws ServiceException If an error occurs retrieving a global variable. */ public static String substitute(String substitutionString, String defaultValue, IData scope) throws ServiceException { return substitute(substitutionString, defaultValue, scope, SubstitutionType.DEFAULT_SUBSTITUTION_SET); } /** * Performs variable substitution on the given string by replacing all occurrences of substrings matching "%key%" * with the associated value from the given scope; if the key has no value, the given defaultValue (if not null) is * used instead. * * @param substitutionString A string to perform variable substitution on. * @param defaultValue A default value to be substituted when the variable being substituted has a value of * null. * @param scope An IData document containing the variables being substituted. * @param substitutionType The type of substitution to perform. * @return The string after variable substitution has been performed. * @throws ServiceException If an error occurs retrieving a global variable. */ public static String substitute(String substitutionString, String defaultValue, IData scope, SubstitutionType substitutionType) throws ServiceException { return substitute(substitutionString, defaultValue, scope, SubstitutionType.normalize(substitutionType)); } /** * Performs variable substitution on the given string by replacing all occurrences of substrings matching "%key%" * with the associated value from the given scope; if the key has no value, the given defaultValue (if not null) is * used instead. * * @param substitutionString A string to perform variable substitution on. * @param defaultValue A default value to be substituted when the variable being substituted has a value of * null. * @param scope An IData document containing the variables being substituted. * @param substitutionTypes The type of substitutions to perform. * @return The string after variable substitution has been performed. * @throws ServiceException If an error occurs retrieving a global variable. */ public static String substitute(String substitutionString, String defaultValue, IData scope, EnumSet<SubstitutionType> substitutionTypes) throws ServiceException { if (substitutionString == null || scope == null) return substitutionString; substitutionTypes = SubstitutionType.normalize(substitutionTypes); Matcher matcher = matcher(substitutionString); StringBuffer output = new StringBuffer(); while (matcher.find()) { String key = matcher.group(1); String value = null; if (substitutionTypes.contains(SubstitutionType.GLOBAL)) { String globalValue = GlobalVariableHelper.get(key); if (globalValue != null) value = globalValue; } if (substitutionTypes.contains(SubstitutionType.LOCAL)) { String localValue = IDataHelper.get(scope, key, String.class); if (localValue != null) value = localValue; } if (value != null) { matcher.appendReplacement(output, Matcher.quoteReplacement((String)value)); } else if (defaultValue != null) { matcher.appendReplacement(output, Matcher.quoteReplacement(defaultValue)); } else { matcher.appendReplacement(output, Matcher.quoteReplacement(matcher.group(0))); } } matcher.appendTail(output); return output.toString(); } /** * Performs variable substitution on the given String[] by replacing all occurrences of substrings matching "%key%" * with the associated value from the given scope; if the key has no value, the given defaultValue (if not null) is * used instead. * * @param array A String[] to perform variable substitution on. * @param defaultValue A default value to be substituted when the variable being substituted has a value * of null. * @param scope An IData document containing the variables being substituted. * @return The String[] after variable substitution has been performed. * @throws ServiceException If an error occurs retrieving a global variable. */ public static String[] substitute(String[] array, String defaultValue, IData scope) throws ServiceException { return substitute(array, defaultValue, scope, SubstitutionType.DEFAULT_SUBSTITUTION_SET); } /** * Performs variable substitution on the given String[] by replacing all occurrences of substrings matching "%key%" * with the associated value from the given scope; if the key has no value, the given defaultValue (if not null) is * used instead. * * @param array A String[] to perform variable substitution on. * @param defaultValue A default value to be substituted when the variable being substituted has a value * of null. * @param scope An IData document containing the variables being substituted. * @param substitutionType The type of substitution to be performed. * @return The String[] after variable substitution has been performed. * @throws ServiceException If an error occurs retrieving a global variable. */ public static String[] substitute(String[] array, String defaultValue, IData scope, SubstitutionType substitutionType) throws ServiceException { return substitute(array, defaultValue, scope, SubstitutionType.normalize(substitutionType)); } /** * Performs variable substitution on the given String[] by replacing all occurrences of substrings matching "%key%" * with the associated value from the given scope; if the key has no value, the given defaultValue (if not null) is * used instead. * * @param array A String[] to perform variable substitution on. * @param defaultValue A default value to be substituted when the variable being substituted has a value * of null. * @param scope An IData document containing the variables being substituted. * @param substitutionTypes The type of substitutions to be performed. * @return The String[] after variable substitution has been performed. * @throws ServiceException If an error occurs retrieving a global variable. */ public static String[] substitute(String[] array, String defaultValue, IData scope, EnumSet<SubstitutionType> substitutionTypes) throws ServiceException { if (array == null) return null; String[] output = new String[array.length]; for (int i = 0; i < array.length; i++) { output[i] = substitute(array[i], defaultValue, scope, substitutionTypes); } return output; } /** * Performs variable substitution on the given String[][] by replacing all occurrences of substrings matching * "%key%" with the associated value from the given scope; if the key has no value, the given defaultValue (if not * null) is used instead. * * @param table A String[][] to perform variable substitution on. * @param defaultValue A default value to be substituted when the variable being substituted has a value * of null. * @param scope An IData document containing the variables being substituted. * @return The String[][] after variable substitution has been performed. * @throws ServiceException If an error occurs retrieving a global variable. */ public static String[][] substitute(String[][] table, String defaultValue, IData scope) throws ServiceException { return substitute(table, defaultValue, scope, SubstitutionType.DEFAULT_SUBSTITUTION_SET); } /** * Performs variable substitution on the given String[][] by replacing all occurrences of substrings matching * "%key%" with the associated value from the given scope; if the key has no value, the given defaultValue (if not * null) is used instead. * * @param table A String[][] to perform variable substitution on. * @param defaultValue A default value to be substituted when the variable being substituted has a value of null. * @param scope An IData document containing the variables being substituted. * @param substitutionType The type of substitution to be performed. * @return The String[][] after variable substitution has been performed. * @throws ServiceException If an error occurs retrieving a global variable. */ public static String[][] substitute(String[][] table, String defaultValue, IData scope, SubstitutionType substitutionType) throws ServiceException { return substitute(table, defaultValue, scope, SubstitutionType.normalize(substitutionType)); } /** * Performs variable substitution on the given String[][] by replacing all occurrences of substrings matching * "%key%" with the associated value from the given scope; if the key has no value, the given defaultValue (if not * null) is used instead. * * @param table A String[][] to perform variable substitution on. * @param defaultValue A default value to be substituted when the variable being substituted has a value of null. * @param scope An IData document containing the variables being substituted. * @param substitutionTypes The type of substitutions to be performed. * @return The String[][] after variable substitution has been performed. * @throws ServiceException If an error occurs retrieving a global variable. */ public static String[][] substitute(String[][] table, String defaultValue, IData scope, EnumSet<SubstitutionType> substitutionTypes) throws ServiceException { if (table == null) return null; String[][] output = new String[table.length][]; for (int i = 0; i < table.length; i++) { output[i] = substitute(table[i], defaultValue, scope, substitutionTypes); } return output; } /** * Performs variable substitution on all elements of the given IData input document. * * @param document The IData document to perform variable substitution on. * @return The variable substituted IData. * @throws ServiceException If an error occurs. */ public static IData substitute(IData document) throws ServiceException { return substitute(document, null, null, true); } /** * Performs variable substitution on all elements of the given IData input document. * * @param document The IData document to perform variable substitution on. * @param recurse Whether embedded IData and IData[] should have variable substitution recursively * performed on them. * @return The variable substituted IData. * @throws ServiceException If an error occurs. */ public static IData substitute(IData document, boolean recurse) throws ServiceException { return substitute(document, null, null, recurse); } /** * Performs variable substitution on all elements of the given IData input document. * * @param document The IData document to perform variable substitution on. * @param scope The scope against which variables are are resolved. * @param recurse Whether embedded IData and IData[] should have variable substitution recursively * performed on them. * @return The variable substituted IData. * @throws ServiceException If an error occurs. */ public static IData substitute(IData document, IData scope, boolean recurse) throws ServiceException { return substitute(document, null, scope, recurse); } /** * Performs variable substitution on all elements of the given IData input document. * * @param document The IData document to perform variable substitution on. * @param defaultValue The value to substitute if a variable cannot be resolved. * @param scope The scope against which variables are are resolved. * @param recurse Whether embedded IData and IData[] should have variable substitution recursively * performed on them. * @return The variable substituted IData. * @throws ServiceException If an error occurs. */ public static IData substitute(IData document, String defaultValue, IData scope, boolean recurse) throws ServiceException { return substitute(document, defaultValue, scope, recurse, SubstitutionType.DEFAULT_SUBSTITUTION_SET); } /** * Performs variable substitution on all elements of the given IData input document. * * @param document The IData document to perform variable substitution on. * @param defaultValue The value to substitute if a variable cannot be resolved. * @param scope The scope against which variables are are resolved. * @param recurse Whether embedded IData and IData[] should have variable substitution recursively * performed on them. * @param substitutionType The type of substitution to be performed. * @return The variable substituted IData. * @throws ServiceException If an error occurs retrieving a global variable. */ public static IData substitute(IData document, String defaultValue, IData scope, boolean recurse, SubstitutionType substitutionType) throws ServiceException { return substitute(document, defaultValue, scope, recurse, SubstitutionType.normalize(substitutionType)); } /** * Performs variable substitution on all elements of the given IData input document. * * @param document The IData document to perform variable substitution on. * @param defaultValue The value to substitute if a variable cannot be resolved. * @param scope The scope against which variables are are resolved. * @param recurse Whether embedded IData and IData[] should have variable substitution recursively * performed on them. * @param substitutionTypes The type of substitutions to be performed. * @return The variable substituted IData. * @throws ServiceException If an error occurs retrieving a global variable. */ public static IData substitute(IData document, String defaultValue, IData scope, boolean recurse, EnumSet<SubstitutionType> substitutionTypes) throws ServiceException { if (document == null) return null; if (scope == null) scope = document; 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 && (value instanceof IData[] || value instanceof Table || value instanceof IDataCodable[] || value instanceof IDataPortable[] || value instanceof ValuesCodable[])) { value = substitute(IDataHelper.toIDataArray(value), defaultValue, scope, recurse, substitutionTypes); } else if (recurse && (value instanceof IData || value instanceof IDataCodable || value instanceof IDataPortable || value instanceof ValuesCodable)) { value = substitute(IDataHelper.toIData(value), defaultValue, scope, recurse, substitutionTypes); } else if (value instanceof String) { value = substitute((String)value, defaultValue, scope, substitutionTypes); } else if (value instanceof String[]) { value = substitute((String[])value, defaultValue, scope, substitutionTypes); } else if (value instanceof String[][]) { value = substitute((String[][])value, defaultValue, scope, substitutionTypes); } } IDataUtil.put(outputCursor, key, value); } inputCursor.destroy(); outputCursor.destroy(); return output; } /** * Performs variable substitution on all elements of the given IData[]. * * @param array The IData[] to perform variable substitution on. * @param defaultValue The value to substitute if a variable cannot be resolved. * @param scope The scope against which variables are are resolved. * @param recurse Whether embedded IData and IData[] should have variable substitution recursively * performed on them. * @return The variable substituted IData[]. * @throws ServiceException If an error occurs. */ public static IData[] substitute(IData[] array, String defaultValue, IData scope, boolean recurse) throws ServiceException { return substitute(array, defaultValue, scope, recurse, SubstitutionType.DEFAULT_SUBSTITUTION_SET); } /** * Performs variable substitution on all elements of the given IData[]. * * @param array The IData[] to perform variable substitution on. * @param defaultValue The value to substitute if a variable cannot be resolved. * @param scope The scope against which variables are are resolved. * @param recurse Whether embedded IData and IData[] should have variable substitution recursively * performed on them. * @param substitutionType The type of substitution to be performed. * @return The variable substituted IData[]. * @throws ServiceException If an error occurs retrieving a global variable. */ public static IData[] substitute(IData[] array, String defaultValue, IData scope, boolean recurse, SubstitutionType substitutionType) throws ServiceException { return substitute(array, defaultValue, scope, recurse, SubstitutionType.normalize(substitutionType)); } /** * Performs variable substitution on all elements of the given IData[]. * * @param array The IData[] to perform variable substitution on. * @param defaultValue The value to substitute if a variable cannot be resolved. * @param scope The scope against which variables are are resolved. * @param recurse Whether embedded IData and IData[] should have variable substitution recursively * performed on them. * @param substitutionTypes The type of substitutions to be performed. * @return The variable substituted IData[]. * @throws ServiceException If an error occurs retrieving a global variable. */ public static IData[] substitute(IData[] array, String defaultValue, IData scope, boolean recurse, EnumSet<SubstitutionType> substitutionTypes) throws ServiceException { if (array == null) return null; IData[] output = new IData[array.length]; for (int i = 0; i < array.length; i++) { output[i] = substitute(array[i], defaultValue, scope, recurse, substitutionTypes); } return output; } }