package com.intuit.tank.harness.functions; /* * #%L * Intuit Tank Agent (apiharness) * %% * Copyright (C) 2011 - 2015 Intuit Inc. * %% * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * #L% */ import java.util.ArrayList; import java.util.Collections; import java.util.Hashtable; import java.util.List; import java.util.Random; import java.util.Stack; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import com.intuit.tank.harness.APITestHarness; import com.intuit.tank.harness.logging.LogUtil; import com.intuit.tank.harness.test.data.Variables; import com.intuit.tank.logging.LogEventType; import com.intuit.tank.vm.common.util.ValidationUtil; /** * * StringFunctions functions useful for String info. Functions start with #function and use dot notation to pass * parameters. The first parameter is the class of function. string in this case. The next value is the function name * Valid values are: * <ul> * <li>userid - @see {@link StringFunctions#userIdDate(int, String)}</li> * <li>alphalower - @see {@link StringFunctions#randomString(String[], int)}</li> * <li>alphaupper - @see {@link StringFunctions#randomString(String[], int)}</li> * <li>alphamixed - @see {@link StringFunctions#randomString(String[], int)}</li> * <li>numeric - @see {@link StringFunctions#randomString(String[], int)}</li> * <li>special - @see {@link StringFunctions#randomString(String[], int)}</li> * <li>unicode - @see {@link StringFunctions#randomString(String[], int)}</li> * <li>alphamixednumeric - @see {@link StringFunctions#randomString(String[], int)}</li> * <li>alphamixedspecial - @see {@link StringFunctions#randomString(String[], int)}</li> * <li>alphamixedunicode - @see {@link StringFunctions#randomString(String[], int)}</li> * <li>alphamixednumericspecial - @see {@link StringFunctions#randomString(String[], int)}</li> * <li>alphamixednumericspecialunicode - @see {@link StringFunctions#randomString(String[], int)}</li> * <li>concat - @see {@link StringFunctions#concat(String[], Variables)}</li> * <li>useridFromRange - @see {@link StringFunctions#userIdFromRange(String[])}</li> * <li>substring - @see {@link StringFunctions#getSubstring(String, int, int)}</li> * </ul> * * * @author dangleton * */ class StringFunctions { private static final Logger LOG = LogManager.getLogger(StringFunctions.class); /** * Preset values */ static final String[] alphaUpper = { "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z" }; static final String[] alphaLower = { "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z" }; static final String[] numeric = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" }; static final String[] special = { "!", "@", "#", "$", "%", "^", "*", "{", "[", "}", "]", ":", ";", ",", ".", "?", "~", "`" }; static final String[] unicode = { "\u00a1", "\u00a2", "\u00a3", "\u00a4", "\u00a5", "\u00A9", "\u00ae", "\u00bc", "\u00bd", "\u00be", "\u00bf", "\u00e0", "\u00e1", "\u00e2", "\u00e3", "\u00e4", "\u00e5", "\u00e6" }; static Random rnd = new Random(); static private Hashtable<String, Stack<Integer>> stackMap = new Hashtable<String, Stack<Integer>>(); /** * Is this a valid String function request * * @param values * The command * @return TRUE if valid format; FALSE otherwise */ static public boolean isValid(String[] values) { try { if (values[3] == "") return false; if (values[2].equalsIgnoreCase("userid") || values[2].equalsIgnoreCase("randomalphalower") || values[2].equalsIgnoreCase("randomalphaupper") || values[2].equalsIgnoreCase("randomalphamixed") || values[2].equalsIgnoreCase("randomnumeric") || values[2].equalsIgnoreCase("randomspecial") || values[2].equalsIgnoreCase("randomalphamixednumeric") || values[2].equalsIgnoreCase("randomalphamixedspecial") || values[2].equalsIgnoreCase("randomalphamixednumericspecial") || values[2].equalsIgnoreCase("concat") || values[2].equalsIgnoreCase("useridFromRange") || values[2].equalsIgnoreCase("useridFromRangeExclude") || values[2].equalsIgnoreCase("useridFromRangeInclude") || values[2].equalsIgnoreCase("useridFromRangeWithMod") || values[2].equalsIgnoreCase("useridFromRangeWithModExclude") || values[2].equalsIgnoreCase("useridFromRangeWithModInclude") || values[2].equalsIgnoreCase("substring")) return true; return false; } catch (Exception ex) { return false; } } /** * Process the numeric request * * @param values * The command line * @return The requested value; "" if there was an error */ static public String executeFunction(String[] values, Variables variables, String addtlString) { try { if (values[2].equalsIgnoreCase("userid")) { return StringFunctions.userIdDate(Integer.valueOf(values[3]), values[4]); } else if (values[2].equalsIgnoreCase("randomalphalower")) { return StringFunctions.randomString(StringFunctions.alphaLower, Integer.valueOf(values[3])); } else if (values[2].equalsIgnoreCase("randomalphaupper")) { return StringFunctions.randomString(StringFunctions.alphaUpper, Integer.valueOf(values[3])); } else if (values[2].equalsIgnoreCase("randomalphamixed")) { return StringFunctions.randomString(StringFunctions.alphaMixed(), Integer.valueOf(values[3])); } else if (values[2].equalsIgnoreCase("randomnumeric")) { return StringFunctions.randomString(StringFunctions.numeric, Integer.valueOf(values[3])); } else if (values[2].equalsIgnoreCase("randomspecial")) { return StringFunctions.randomString(StringFunctions.special, Integer.valueOf(values[3])); } else if (values[2].equalsIgnoreCase("randomalphamixednumeric")) { return StringFunctions.randomString(StringFunctions.alphaMixedNumeric(), Integer.valueOf(values[3])); } else if (values[2].equalsIgnoreCase("randomalphamixedspecial")) { return StringFunctions.randomString(StringFunctions.alphaMixedSpecial(), Integer.valueOf(values[3])); } else if (values[2].equalsIgnoreCase("randomalphaMixedNumericSpecial")) { return StringFunctions.randomString(StringFunctions.alphaMixedNumericSpecial(), Integer.valueOf(values[3])); } else if (values[2].equalsIgnoreCase("concat")) { return StringFunctions.concat(values, variables); } else if (values[2].equalsIgnoreCase("useridFromRange")) { return StringFunctions.userIdFromRange(values, false); } else if (values[2].equalsIgnoreCase("useridFromRangeInclude")) { return StringFunctions.userIdFromRange(values, true); } else if (values[2].equalsIgnoreCase("useridFromRangeExclude")) { return StringFunctions.userIdFromRange(values, false); } else if (values[2].equalsIgnoreCase("useridFromRangeWithMod")) { return StringFunctions.userIdFromRangeWithMod(values, true); } else if (values[2].equalsIgnoreCase("useridFromRangeWithModInclude")) { return StringFunctions.userIdFromRangeWithMod(values, true); } else if (values[2].equalsIgnoreCase("useridFromRangeWithModExclude")) { return StringFunctions.userIdFromRangeWithMod(values, false); } else if (values[2].equalsIgnoreCase("substring")) { if (values.length == 5) { return StringFunctions.getSubstring(values[3], Integer.valueOf(values[4]), -1); } else if (values.length == 6) try { return StringFunctions.getSubstring(values[3], Integer.valueOf(values[4]), Integer.valueOf(values[5])); } catch (NumberFormatException nfe) { return StringFunctions.getSubstring(addtlString, values[3], values[4]); } } return ""; } catch (Exception ex) { return ""; } } private static String getSubstring(String string, String start, String end) { if (string == null) return ""; int beginIndex = string.indexOf(start); if (beginIndex < 0) return ""; String newString = string.substring(beginIndex + start.length()); if (!end.equals("")) { int endIndex = newString.indexOf(end); if (endIndex > 0) newString = newString.substring(0, endIndex); } return newString; } /** * Combine the alpha lower and alpha upper collections into one * * @return The combined collection */ static private String[] alphaMixed() { return StringFunctions.combineStringArrays(StringFunctions.alphaLower, StringFunctions.alphaUpper); } /** * Combine the alpha mixed and numeric collections into one * * @return The combined collection */ static private String[] alphaMixedNumeric() { return StringFunctions.combineStringArrays(StringFunctions.alphaMixed(), StringFunctions.numeric); } /** * Combine the alpha mixed and special collections into one * * @return The combined collection */ static private String[] alphaMixedSpecial() { return StringFunctions.combineStringArrays(StringFunctions.alphaMixed(), StringFunctions.special); } // /** // * Combine the alpha mixed and unicode collections into one // * // * @return The combined collection // */ // static private String[] alphaMixedUnicode() { // return StringFunctions.combineStringArrays(StringFunctions.alphaMixed(), StringFunctions.unicode); // } /** * Combine the alpha mixed, numeric and special collections into one * * @return The combined collection */ static private String[] alphaMixedNumericSpecial() { String[] step1 = StringFunctions.combineStringArrays(StringFunctions.alphaMixed(), StringFunctions.numeric); return StringFunctions.combineStringArrays(step1, StringFunctions.special); } // // /** // * Combine the alpha mixed, numeric, special and unicode collections into one // * // * @return The combined collection // */ // static private String[] alphaMixedNumericSpecialUnicode() { // String[] step1 = StringFunctions.combineStringArrays(StringFunctions.alphaMixed(), StringFunctions.numeric); // String[] step2 = StringFunctions.combineStringArrays(step1, StringFunctions.special); // return StringFunctions.combineStringArrays(step2, StringFunctions.unicode); // } /** * Combine two String arrays into one * * @param value1 * String[] One * @param value2 * String[] Two * @return One String array containing both sets of data */ static private String[] combineStringArrays(String[] value1, String[] value2) { String[] output = new String[value1.length + value2.length]; int counter = 0; for (int i = 0; i < value1.length; i++) { output[counter] = value1[i]; ++counter; } for (int i = 0; i < value2.length; i++) { output[counter] = value2[i]; ++counter; } return output; } /** * Get a random, positive whole number * * * * @param length * The length of the full numbers * @return A random whole number */ static private String randomString(String[] values, int length) { StringBuilder output = new StringBuilder(length); for (int i = 0; i < length; i++) output.append(values[rnd.nextInt(values.length)]); return output.toString(); } /** * Generate a random user date consisting of a prefix and a date * * e.g. #function.string.userid.2.06-01-2011 * * @param prefixLength * The length of the random prefix * @param dateFormat * The format of the response (MM-dd-yyyy, MMddyyyy, etc) * @return The random user id */ static private String userIdDate(int prefixLength, String dateFormat) { String[] dateRequest = { "#function", "date", "currentDate", dateFormat }; String prefix = StringFunctions.randomString(StringFunctions.alphaMixedNumeric(), prefixLength); String date = DateFunctions.executeFunction(dateRequest); return prefix + date; } /** * Generate a random user id as an integer. * * e.g. #function.string.useridFromRange.25.100 * * @param values * Array values - [3] = minId, [4] = maxId, [5] = inclusions/inclusions list comma seperated regex to * test the value * @param include * The format for the date * @return The random user id */ static private String userIdFromRange(String[] values, boolean include) { int minId = Integer.parseInt(values[3]); int maxId = Integer.parseInt(values[4]); String exclusions = values.length > 5 ? values[5] : null; Stack<Integer> stack = getStack(minId, maxId, exclusions, include); if (stack.size() > 0) { return Integer.toString(stack.pop()); } throw new IllegalArgumentException("Exhausted random User Ids. Range not large enough for the number of calls."); } /** * Generate a random user id in the range provided * * e.g. #function.string.useridFromRange.25.100 * * @param prefixLength * The length of the random prefix * @param dateFormat * The format for the date * @return The random user id */ static private String userIdFromRangeWithMod(String[] values, boolean include) { int minId = Integer.parseInt(values[3]); int maxId = Integer.parseInt(values[4]); int mod = Integer.parseInt(values[5]); Stack<Integer> stack = getStackWithMods(minId, maxId, mod, include); if (stack.size() > 0) { return Integer.toString(stack.pop()); } throw new IllegalArgumentException("Exhausted random User Ids. Range not large enough for the number of calls."); } /** * @param minId * @param maxId * @return */ public synchronized static Stack<Integer> getStack(Integer minId, Integer maxId, String exclusions, boolean include) { String key = getStackKey(minId, maxId, exclusions, include); Stack<Integer> stack = stackMap.get(key); if (stack == null) { int blockSize = (maxId - minId) / APITestHarness.getInstance().getAgentRunData().getTotalAgents(); int offset = APITestHarness.getInstance().getAgentRunData().getAgentInstanceNum() * blockSize; LOG.info(LogUtil.getLogMessage("Creating userId Block starting at " + (offset + minId) + " and containing " + blockSize + " entries with " + (include ? "inclusion" : "exclusion") + " pattern(s) of " + exclusions, LogEventType.System)); List<Integer> list = new ArrayList<Integer>(); List<String> exclusionList = parseExclusions(exclusions); for (int i = 0; i < blockSize; i++) { int nextNum = i + minId + offset; if (nextNum < maxId) { if (shouldInclude(Integer.toString(nextNum), exclusionList, include)) { list.add(nextNum); } } } Collections.shuffle(list); // Collections.reverse(list); stack = new Stack<Integer>(); stack.addAll(list); stackMap.put(key, stack); } return stack; } /** * @param minId * @param maxId * @return */ private synchronized static Stack<Integer> getStackWithMods(Integer minId, Integer maxId, Integer mod, boolean include) { String key = getStackKey(minId, maxId, mod, include); Stack<Integer> stack = stackMap.get(key); if (stack == null) { int blockSize = (maxId - minId) / APITestHarness.getInstance().getAgentRunData().getTotalAgents(); int offset = APITestHarness.getInstance().getAgentRunData().getAgentInstanceNum() * blockSize; LOG.info(LogUtil.getLogMessage("Creating userId Block starting at " + offset + " and containing " + blockSize + " entries.", LogEventType.System)); List<Integer> list = new ArrayList<Integer>(); for (int i = 0; i < blockSize; i++) { int nextNum = i + minId + offset; if (include && nextNum < maxId && nextNum % mod == 0) { list.add(nextNum); } else if (!include && nextNum < maxId && nextNum % mod != 0) { list.add(nextNum); } } Collections.shuffle(list); // Collections.reverse(list); stack = new Stack<Integer>(); stack.addAll(list); stackMap.put(key, stack); } return stack; } private static String getStackKey(Object minId, Object maxId, Object mod, boolean include) { return minId + "-" + maxId + "%" + mod + "-" + (include ? "include" : "exclude"); } private static List<String> parseExclusions(String exclusions) { List<String> ret = new ArrayList<String>(); if (!StringUtils.isBlank(exclusions)) { String[] terms = exclusions.split(","); for (String term : terms) { ret.add(term.trim()); } } return ret; } public static boolean shouldInclude(String value, List<String> expressions, boolean include) { boolean isMatch = false; for (String exp : expressions) { isMatch = isMatch | value.matches(exp); } return include ? isMatch : !isMatch; } /** * Generates a string that is a concatenation of the strings, 0-n, passed in. Evaluates strings for variables and * will concat the variable value, not name, to string. * * @param values * List of Strings to concat starting at index 3 in array * @return Concatenation of strings */ static private String concat(String[] values, Variables variables) { String generatedStr = ""; for (int str = 3; str < values.length; str++) { String strToAdd = values[str]; if (strToAdd != null) { // check if string is variable if (ValidationUtil.isVariable(strToAdd)) { strToAdd = variables.getVariable(strToAdd); } generatedStr = generatedStr.concat(strToAdd); } } return generatedStr; } /** * Get a substring from a string * * @param subject * The original string * @param start * The location to start at * @param stop * The location to end at; -1 if end of string * @return */ static private String getSubstring(String subject, int start, int stop) { if (stop == -1 || stop >= subject.length()) { return subject.substring(start); } return subject.substring(start, stop); } }