package org.jboss.windup.rules.apps.java.condition;
import com.thinkaurelius.titan.core.attribute.Text;
import com.tinkerpop.blueprints.Predicate;
import com.tinkerpop.blueprints.Vertex;
import com.tinkerpop.frames.structures.FramedVertexIterable;
import com.tinkerpop.gremlin.java.GremlinPipeline;
import org.apache.commons.lang3.StringUtils;
import org.jboss.forge.furnace.util.Assert;
import org.jboss.windup.ast.java.data.TypeReferenceLocation;
import org.jboss.windup.config.GraphRewrite;
import org.jboss.windup.config.Variables;
import org.jboss.windup.config.condition.EvaluationStrategy;
import org.jboss.windup.config.condition.GraphCondition;
import org.jboss.windup.config.condition.NoopEvaluationStrategy;
import org.jboss.windup.config.parameters.FrameContext;
import org.jboss.windup.config.parameters.FrameCreationContext;
import org.jboss.windup.config.parameters.ParameterizedGraphCondition;
import org.jboss.windup.config.query.Query;
import org.jboss.windup.config.query.QueryBuilderFrom;
import org.jboss.windup.config.query.QueryBuilderPiped;
import org.jboss.windup.config.query.QueryGremlinCriterion;
import org.jboss.windup.config.query.QueryPropertyComparisonType;
import org.jboss.windup.graph.TitanUtil;
import org.jboss.windup.graph.model.FileReferenceModel;
import org.jboss.windup.graph.model.WindupVertexFrame;
import org.jboss.windup.graph.model.resource.FileModel;
import org.jboss.windup.rules.apps.java.condition.annotation.AnnotationCondition;
import org.jboss.windup.rules.apps.java.condition.annotation.AnnotationTypeCondition;
import org.jboss.windup.rules.apps.java.model.AbstractJavaSourceModel;
import org.jboss.windup.rules.apps.java.model.JavaClassFileModel;
import org.jboss.windup.rules.apps.java.model.JavaClassModel;
import org.jboss.windup.rules.apps.java.model.JavaSourceFileModel;
import org.jboss.windup.rules.apps.java.scan.ast.JavaTypeReferenceModel;
import org.jboss.windup.rules.apps.java.scan.ast.TypeInterestFactory;
import org.jboss.windup.rules.apps.java.scan.ast.annotations.JavaAnnotationTypeReferenceModel;
import org.jboss.windup.rules.apps.java.scan.ast.annotations.JavaAnnotationTypeValueModel;
import org.jboss.windup.util.ExecutionStatistics;
import org.ocpsoft.rewrite.config.Condition;
import org.ocpsoft.rewrite.config.ConditionBuilder;
import org.ocpsoft.rewrite.context.EvaluationContext;
import org.ocpsoft.rewrite.param.DefaultParameterStore;
import org.ocpsoft.rewrite.param.ParameterStore;
import org.ocpsoft.rewrite.param.ParameterizedPatternResult;
import org.ocpsoft.rewrite.param.RegexParameterizedPatternParser;
import org.ocpsoft.rewrite.util.Maps;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Pattern;
/**
* {@link GraphCondition} that matches Vertices in the graph based upon the provided parameters.
*/
public class JavaClass extends ParameterizedGraphCondition implements JavaClassBuilder, JavaClassBuilderAt,
JavaClassBuilderInFile, JavaClassBuilderLineMatch
{
private static final AtomicInteger numberCreated = new AtomicInteger(0);
private final String uniqueID;
private List<TypeReferenceLocation> locations = Collections.emptyList();
private AnnotationTypeCondition annotationCondition;
private List<AnnotationTypeCondition> additionalAnnotationConditions = new ArrayList<>();
private final RegexParameterizedPatternParser referencePattern;
private RegexParameterizedPatternParser lineMatchPattern;
private RegexParameterizedPatternParser typeFilterPattern;
private JavaClass(String referencePattern)
{
this.referencePattern = new RegexParameterizedPatternParser(referencePattern);
this.uniqueID = numberCreated.incrementAndGet() + "_JavaClass";
}
/**
* Create a new {@link JavaClass} {@link Condition} based upon the provided Java regular expression.
*/
public static JavaClassBuilder references(String regex)
{
return new JavaClass(regex);
}
/**
* Create a new {@link JavaClass} {@link Condition} based upon the provided Java regular expression.
*/
public static JavaClassBuilderReferences from(String inputVarName)
{
return new JavaClassBuilderReferences(inputVarName);
}
/**
* Specify a Java type pattern pattern for which this condition should match.
*/
public JavaClassBuilderInFile inType(String typeFilterPattern)
{
this.typeFilterPattern = new RegexParameterizedPatternParser(typeFilterPattern);
return this;
}
public JavaClassBuilderLineMatch matchesSource(String lineMatchRegex)
{
this.lineMatchPattern = new RegexParameterizedPatternParser(lineMatchRegex);
return this;
}
@Override
public JavaClassBuilderAt annotationMatches(String element, AnnotationCondition condition)
{
if (this.annotationCondition == null)
this.annotationCondition = new AnnotationTypeCondition("{*}");
this.annotationCondition.addCondition(element, condition);
return this;
}
@Override
public JavaClassBuilderAt annotationMatches(AnnotationTypeCondition condition)
{
this.additionalAnnotationConditions.add(condition);
return this;
}
/**
* Only match if the TypeReference is at the specified location within the file.
*/
@Override
public JavaClassBuilderAt at(TypeReferenceLocation... locations)
{
if (locations != null)
this.locations = Arrays.asList(locations);
return this;
}
/**
* Optionally specify the variable name to use for the output of this condition
*/
@Override
public ConditionBuilder as(String variable)
{
Assert.notNull(variable, "Variable name must not be null.");
this.setOutputVariablesName(variable);
return this;
}
@Override
@SuppressWarnings("unchecked")
protected boolean evaluateAndPopulateValueStores(GraphRewrite event, EvaluationContext context,
final FrameCreationContext frameCreationContext)
{
return evaluate(event, context, new EvaluationStrategy()
{
private LinkedHashMap<String, List<WindupVertexFrame>> variables;
@Override
@SuppressWarnings("rawtypes")
public void modelMatched()
{
this.variables = new LinkedHashMap<>();
frameCreationContext.beginNew((Map) variables);
}
@Override
public void modelSubmitted(WindupVertexFrame model)
{
Maps.addListValue(this.variables, getVarname(), model);
}
@Override
public void modelSubmissionRejected()
{
frameCreationContext.rollback();
}
});
}
@Override
protected boolean evaluateWithValueStore(GraphRewrite event, EvaluationContext context,
final FrameContext frameContext)
{
boolean result = evaluate(event, context, new NoopEvaluationStrategy());
if (result == false)
frameContext.reject();
return result;
}
private boolean evaluate(GraphRewrite event, EvaluationContext context, EvaluationStrategy evaluationStrategy)
{
try
{
ExecutionStatistics.get().begin("JavaClass.evaluate");
final ParameterStore store = DefaultParameterStore.getInstance(context);
final Pattern compiledPattern = referencePattern.getCompiledPattern(store);
/*
* Only set in the case of a query with no "from" variable.
*/
String initialQueryID = null;
QueryBuilderFrom query;
initialQueryID = "iqi." + UUID.randomUUID().toString();
//prepare initialQueryID
if (!StringUtils.isBlank(getInputVariablesName()))
{
QueryBuilderFrom fromQuery = Query.from(getInputVariablesName());
QueryBuilderPiped piped = fromQuery.piped(new QueryGremlinCriterion()
{
@Override public void query(GraphRewrite event, GremlinPipeline<Vertex, Vertex> pipeline)
{
pipeline.out(FileReferenceModel.FILE_MODEL).in(FileReferenceModel.FILE_MODEL)
.has(JavaTypeReferenceModel.RESOLVED_SOURCE_SNIPPIT, Text.REGEX, compiledPattern.toString());
}
});
piped.as(initialQueryID).evaluate(event,context);
}
else
{
GremlinPipeline<Vertex, Vertex> resolvedTextSearch = new GremlinPipeline<>(event.getGraphContext().getGraph());
resolvedTextSearch.V();
resolvedTextSearch.has(JavaTypeReferenceModel.RESOLVED_SOURCE_SNIPPIT, Text.REGEX, TitanUtil.titanifyRegex(compiledPattern.pattern()));
if (!resolvedTextSearch.iterator().hasNext())
return false;
Variables.instance(event).setVariable(
initialQueryID,
new FramedVertexIterable<>(event.getGraphContext().getFramed(), resolvedTextSearch,
JavaTypeReferenceModel.class));
}
query = Query.from(initialQueryID);
if (lineMatchPattern != null)
{
final Pattern compiledLineMatchPattern = lineMatchPattern.getCompiledPattern(store);
query.withProperty(JavaTypeReferenceModel.SOURCE_SNIPPIT, QueryPropertyComparisonType.REGEX, compiledLineMatchPattern.pattern());
}
String uuid = UUID.randomUUID().toString();
query.as(uuid);
if (typeFilterPattern != null)
{
Pattern compiledTypeFilterPattern = typeFilterPattern.getCompiledPattern(store);
query.piped(new TypeFilterCriterion(compiledTypeFilterPattern));
}
if (!locations.isEmpty())
query.withProperty(JavaTypeReferenceModel.REFERENCE_TYPE, locations);
List<WindupVertexFrame> results = new ArrayList<>();
if (query.evaluate(event, context))
{
Iterable<? extends WindupVertexFrame> frames = Variables.instance(event).findVariable(uuid);
for (WindupVertexFrame frame : frames)
{
FileModel fileModel = ((FileReferenceModel) frame).getFile();
Iterable<JavaClassModel> javaClasses = null;
if (fileModel instanceof AbstractJavaSourceModel)
javaClasses = ((AbstractJavaSourceModel) fileModel).getJavaClasses();
else if (fileModel instanceof JavaClassFileModel)
javaClasses = Arrays.asList(((JavaClassFileModel) fileModel).getJavaClass());
for (JavaClassModel javaClassModel : javaClasses)
{
if (typeFilterPattern == null || typeFilterPattern.parse(javaClassModel
.getQualifiedName()).matches())
{
JavaTypeReferenceModel model = (JavaTypeReferenceModel) frame;
ParameterizedPatternResult referenceResult = referencePattern.parse(model
.getResolvedSourceSnippit());
if (referenceResult.matches())
{
evaluationStrategy.modelMatched();
if (referenceResult.submit(event, context)
&& (typeFilterPattern == null || typeFilterPattern.parse(javaClassModel
.getQualifiedName()).submit(event, context)))
{
boolean annotationMatched = matchAnnotationConditions(event, context, evaluationStrategy, model);
if (!annotationMatched)
{
evaluationStrategy.modelSubmissionRejected();
} else
{
results.add(model);
evaluationStrategy.modelSubmitted(model);
}
}
else
{
evaluationStrategy.modelSubmissionRejected();
}
}
}
}
}
Variables.instance(event).removeVariable(uuid);
if (initialQueryID != null)
Variables.instance(event).removeVariable(initialQueryID);
setResults(event, getVarname(), results);
return !results.isEmpty();
}
return false;
}
finally
{
ExecutionStatistics.get().end("JavaClass.evaluate");
}
}
private boolean matchAnnotationConditions(GraphRewrite event, EvaluationContext context, EvaluationStrategy evaluationStrategy, JavaTypeReferenceModel model)
{
boolean annotationMatched = true;
if (this.annotationCondition != null)
{
annotationMatched = model instanceof JavaAnnotationTypeValueModel;
annotationMatched &= annotationCondition.evaluate(event, context, evaluationStrategy, (JavaAnnotationTypeValueModel)model);
}
if (!additionalAnnotationConditions.isEmpty())
{
JavaTypeReferenceModel referencedTypeModel;
if (model.getReferenceLocation() == TypeReferenceLocation.ANNOTATION)
referencedTypeModel = ((JavaAnnotationTypeReferenceModel)model).getAnnotatedType();
else
referencedTypeModel = model;
// iterate the conditions and make sure there is at least one matching annotation for each
for (AnnotationCondition condition : this.additionalAnnotationConditions)
{
boolean oneMatches = false;
// now get the annotations
for (JavaAnnotationTypeReferenceModel annotationModel : referencedTypeModel.getAnnotations())
{
if (condition.evaluate(event, context, evaluationStrategy, annotationModel))
{
oneMatches = true;
}
}
if (!oneMatches)
annotationMatched = false;
}
}
return annotationMatched;
}
private final class TypeFilterCriterion implements QueryGremlinCriterion
{
private final Pattern compiledTypeFilterPattern;
private TypeFilterCriterion(Pattern compiledTypeFilterPattern)
{
this.compiledTypeFilterPattern = compiledTypeFilterPattern;
}
@Override
public void query(GraphRewrite event, GremlinPipeline<Vertex, Vertex> pipeline)
{
Predicate regexPredicate = new Predicate()
{
@Override
public boolean evaluate(Object first, Object second)
{
return ((String) first).matches((String) second);
}
};
pipeline.as("result")
.out(FileReferenceModel.FILE_MODEL)
.out(JavaSourceFileModel.JAVA_CLASS_MODEL)
.has(JavaClassModel.QUALIFIED_NAME,
regexPredicate,
compiledTypeFilterPattern.pattern())
.back("result");
}
}
@Override
public Set<String> getRequiredParameterNames()
{
Set<String> result = new HashSet<>(referencePattern.getRequiredParameterNames());
if (typeFilterPattern != null)
result.addAll(typeFilterPattern.getRequiredParameterNames());
if (lineMatchPattern != null)
result.addAll(lineMatchPattern.getRequiredParameterNames());
return result;
}
@Override
public void setParameterStore(ParameterStore store)
{
if (locations != null && !locations.isEmpty())
{
TypeInterestFactory.registerInterest(
this.uniqueID,
referencePattern.getCompiledPattern(store).pattern(),
referencePattern.getPattern(),
locations);
}
else
{
TypeInterestFactory.registerInterest(
this.uniqueID,
referencePattern.getCompiledPattern(store).pattern(),
referencePattern.getPattern());
}
referencePattern.setParameterStore(store);
if (typeFilterPattern != null)
typeFilterPattern.setParameterStore(store);
}
@Override
public String getVarname()
{
return getOutputVariablesName();
}
public String toString()
{
StringBuilder builder = new StringBuilder();
builder.append("JavaClass");
if (typeFilterPattern != null)
{
builder.append(".inType(" + typeFilterPattern + ")");
}
if (referencePattern != null)
{
builder.append(".references(" + referencePattern + ")");
}
if (!locations.isEmpty())
{
builder.append(".at(" + locations + ")");
}
if (annotationCondition != null)
{
builder.append(".annotationConditions(");
builder.append(annotationCondition.toString());
builder.append(")");
}
for (AnnotationTypeCondition condition : this.additionalAnnotationConditions)
{
builder.append(".annotationConditions(");
builder.append(condition.toString());
builder.append(")");
}
builder.append(".as(" + getVarname() + ")");
return builder.toString();
}
public RegexParameterizedPatternParser getReferences()
{
return referencePattern;
}
public RegexParameterizedPatternParser getMatchesSource()
{
return lineMatchPattern;
}
public List<TypeReferenceLocation> getLocations()
{
return locations;
}
public RegexParameterizedPatternParser getTypeFilterRegex()
{
return typeFilterPattern;
}
}