package org.jboss.windup.config.operation.iteration; import java.lang.reflect.InvocationTargetException; import java.util.Arrays; import java.util.Iterator; import java.util.LinkedList; import java.util.Queue; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.jboss.windup.config.GraphRewrite; import org.jboss.windup.config.Variables; import org.jboss.windup.config.operation.GraphOperation; import org.jboss.windup.config.operation.Iteration; import org.jboss.windup.graph.model.WindupVertexFrame; import org.ocpsoft.rewrite.context.EvaluationContext; import com.tinkerpop.frames.Property; /** * Simplified operation having method that already accepts the found payload. */ public abstract class AbstractIterationOperation<T extends WindupVertexFrame> extends GraphOperation { /** * When the variable name is not specified, let the Iteration to set the current payload variable name. */ public AbstractIterationOperation() { } public AbstractIterationOperation(String variableName) { this.variableName = variableName; } private String variableName; public String getVariableName() { if (variableName == null) { return null; } return new VariableNameIterator(variableName).next(); } public void setVariableName(String variableName) { this.variableName = variableName; } public boolean hasVariableNameSet() { return getVariableName() != null; } @Override public void perform(GraphRewrite event, EvaluationContext context) { checkVariableName(event, context); WindupVertexFrame payload = resolveVariable(event, variableName); perform(event, context, resolvePayload(event, context, payload)); } @SuppressWarnings("unchecked") public T resolvePayload(GraphRewrite event, EvaluationContext context, WindupVertexFrame payload) { return (T) payload; } /** * Check the variable name and if not set, set it with the singleton variable name being on the top of the stack. */ protected void checkVariableName(GraphRewrite event, EvaluationContext context) { if (variableName == null) { setVariableName(Iteration.getPayloadVariableName(event, context)); } } /** * Resolves variable/property name expressions of the form ` * <code>#{initialModelVar.customProperty.anotherProp}</code>`, where the `initialModelVar` has a {@link Property} * method of the form `<code>@Property public X getCustomProperty()</code>` and `X` has a {@link Property} method of * the form `<code>@Property public X getAnotherProp()</code>` */ protected WindupVertexFrame resolveVariable(GraphRewrite event, String variableName) { Variables variables = Variables.instance(event); Iterator<String> tokenizer = new VariableNameIterator(variableName); WindupVertexFrame payload; String initialName = tokenizer.next(); try { payload = Iteration.getCurrentPayload(variables, initialName); } catch (IllegalArgumentException e1) { payload = variables.findSingletonVariable(initialName); } while (tokenizer.hasNext()) { String propertyName = tokenizer.next(); propertyName = "get" + camelCase(propertyName, true); try { payload = (WindupVertexFrame) payload.getClass().getMethod(propertyName).invoke(payload); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) { throw new IllegalArgumentException("Invalid variable expression: " + variableName, e); } } return payload; } public abstract void perform(GraphRewrite event, EvaluationContext context, T payload); // TODO Replace all this lame hacky junk with a real Variables resolving EL implementation. private static class VariableNameIterator implements Iterator<String> { final Queue<String> queue; public VariableNameIterator(String name) { String result = name; if (name.trim().startsWith("#{")) { result = result.replaceAll("\\s*#\\{\\s*([a-zA-Z0-9.]+)\\s*\\}\\s*", "$1"); result = result.replaceAll("([a-zA-Z0-9]+\\..*)", "$1"); } queue = new LinkedList<>(Arrays.asList(result.split("\\."))); } @Override public boolean hasNext() { return !queue.isEmpty(); } @Override public String next() { return queue.remove(); } @Override public void remove() { queue.poll(); } } /** * By default, this method converts strings to UpperCamelCase. If the <code>uppercaseFirstLetter</code> argument to * false, then this method produces lowerCamelCase. This method will also use any extra delimiter characters to * identify word boundaries. * <p> * Examples: * * <pre> * inflector.camelCase("active_record",false) #=> "activeRecord" * inflector.camelCase("active_record",true) #=> "ActiveRecord" * inflector.camelCase("first_name",false) #=> "firstName" * inflector.camelCase("first_name",true) #=> "FirstName" * inflector.camelCase("name",false) #=> "name" * inflector.camelCase("name",true) #=> "Name" * </pre> * * </p> * * @param lowerCaseAndUnderscoredWord the word that is to be converted to camel case * @param uppercaseFirstLetter true if the first character is to be uppercased, or false if the first character is * to be lowercased * @param delimiterChars optional characters that are used to delimit word boundaries * @return the camel case version of the word */ public String camelCase(String lowerCaseAndUnderscoredWord, boolean uppercaseFirstLetter, char... delimiterChars) { if (lowerCaseAndUnderscoredWord == null) return null; lowerCaseAndUnderscoredWord = lowerCaseAndUnderscoredWord.trim(); if (lowerCaseAndUnderscoredWord.length() == 0) return ""; if (uppercaseFirstLetter) { String result = lowerCaseAndUnderscoredWord; // Replace any extra delimiters with underscores (before the underscores are converted in the next step)... if (delimiterChars != null) { for (char delimiterChar : delimiterChars) { result = result.replace(delimiterChar, '_'); } } // Change the case at the beginning at after each underscore ... return replaceAllWithUppercase(result, "(^|_)(.)", 2); } if (lowerCaseAndUnderscoredWord.length() < 2) return lowerCaseAndUnderscoredWord; return "" + Character.toLowerCase(lowerCaseAndUnderscoredWord.charAt(0)) + camelCase(lowerCaseAndUnderscoredWord, true, delimiterChars).substring(1); } /** * Utility method to replace all occurrences given by the specific backreference with its uppercased form, and * remove all other backreferences. * <p> * The Java {@link Pattern regular expression processing} does not use the preprocessing directives <code>\l</code>, * <code>\u</code>, <code>\L</code>, and <code>\U</code>. If so, such directives could be used in the * replacement string to uppercase or lowercase the backreferences. For example, <code>\L1</code> would lowercase * the first backreference, and <code>\u3</code> would uppercase the 3rd backreference. * </p> * * @param input * @param regex * @param groupNumberToUppercase * @return the input string with the appropriate characters converted to upper-case */ String replaceAllWithUppercase(String input, String regex, int groupNumberToUppercase) { Pattern underscoreAndDotPattern = Pattern.compile(regex); Matcher matcher = underscoreAndDotPattern.matcher(input); StringBuffer sb = new StringBuffer(); while (matcher.find()) { matcher.appendReplacement(sb, matcher.group(groupNumberToUppercase).toUpperCase()); } matcher.appendTail(sb); return sb.toString(); } @Override public String toString() { return this.getClass().getSimpleName() + " with var '" + variableName + "'"; } }