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 + "'";
}
}