/**
* Copyright (C) 2006-2017 INRIA and contributors
* Spoon - http://spoon.gforge.inria.fr/
*
* This software is governed by the CeCILL-C License under French law and
* abiding by the rules of distribution of free software. You can use, modify
* and/or redistribute the software under the terms of the CeCILL-C license as
* circulated by CEA, CNRS and INRIA at http://www.cecill.info.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the CeCILL-C License for more details.
*
* The fact that you are presently reading this means that you have had
* knowledge of the CeCILL-C license and that you accept its terms.
*/
package spoon.reflect.visitor.filter;
import spoon.SpoonException;
import spoon.reflect.code.CtCatchVariable;
import spoon.reflect.code.CtLocalVariable;
import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtParameter;
import spoon.reflect.declaration.CtType;
import spoon.reflect.declaration.CtVariable;
import spoon.reflect.reference.CtLocalVariableReference;
import spoon.reflect.reference.CtVariableReference;
import spoon.reflect.visitor.CtAbstractVisitor;
import spoon.reflect.visitor.Filter;
import spoon.reflect.visitor.chain.CtConsumableFunction;
import spoon.reflect.visitor.chain.CtConsumer;
import spoon.reflect.visitor.chain.CtQuery;
import spoon.reflect.visitor.chain.CtScannerListener;
import spoon.reflect.visitor.chain.ScanningMode;
/**
* This mapping function expects a {@link CtLocalVariable} as input
* and returns all {@link CtLocalVariableReference}s, which refers this input.
* <br>
* Usage:<br>
* <pre> {@code
* CtLocalVariable var = ...;
* var
* .map(new LocalVariableReferenceFunction())
* .forEach((CtLocalVariableReference ref)->...process references...);
* }
* </pre>
*/
public class LocalVariableReferenceFunction implements CtConsumableFunction<CtElement> {
final CtVariable<?> targetVariable;
final Class<?> variableClass;
final Class<?> variableReferenceClass;
public LocalVariableReferenceFunction() {
this(CtLocalVariable.class, CtLocalVariableReference.class);
}
/**
* This constructor allows to define input local variable - the one for which this function will search for.
* In such case the input of mapping function represents the scope
* where this local variable is searched for.
* @param localVariable - the local variable declaration which is searched in scope of input element of this mapping function.
*/
public LocalVariableReferenceFunction(CtLocalVariable<?> localVariable) {
this(CtLocalVariable.class, CtLocalVariableReference.class, localVariable);
}
LocalVariableReferenceFunction(Class<?> variableClass, Class<?> variableReferenceClass) {
this.variableClass = variableClass;
this.variableReferenceClass = variableReferenceClass;
this.targetVariable = null;
}
LocalVariableReferenceFunction(Class<?> variableClass, Class<?> variableReferenceClass, CtVariable<?> variable) {
this.variableClass = variableClass;
this.variableReferenceClass = variableReferenceClass;
this.targetVariable = variable;
}
@Override
public void apply(final CtElement scope, CtConsumer<Object> outputConsumer) {
CtVariable<?> var = targetVariable;
if (var == null) {
if (variableClass.isInstance(scope)) {
var = (CtVariable<?>) scope;
} else {
throw new SpoonException("The input of " + getClass().getSimpleName() + " must be a " + variableClass.getSimpleName() + " but is " + scope.getClass().getSimpleName());
}
}
final CtVariable<?> variable = var;
final String simpleName = variable.getSimpleName();
//the context which knows whether we are scanning in scope of local type or not
final Context context = new Context();
CtQuery scopeQuery;
if (scope == variable) {
//we are starting search from local variable declaration
scopeQuery = createScopeQuery(variable, scope, context);
} else {
//we are starting search later, somewhere deep in scope of variable declaration
final CtElement variableParent = variable.getParent();
/*
* search in parents of searching scope for the variableParent
* 1) to check that scope is a child of variableParent
* 2) to detect if there is an local class between variable declaration and scope
*/
if (scope.map(new ParentFunction()).select(new Filter<CtElement>() {
@Override
public boolean matches(CtElement element) {
if (element instanceof CtType) {
//detected that the search scope is in local class declared in visibility scope of variable
context.nrTypes++;
}
return variableParent == element;
}
}).first() == null) {
//the scope is not under children of localVariable
throw new SpoonException("Cannot search for references of variable in wrong scope.");
}
//search in all children of the scope element
scopeQuery = scope.map(new CtScannerFunction().setListener(context));
}
scopeQuery.select(new Filter<CtElement>() {
@Override
public boolean matches(CtElement element) {
if (variableReferenceClass.isInstance(element)) {
CtVariableReference<?> varRef = (CtVariableReference<?>) element;
if (simpleName.equals(varRef.getSimpleName())) {
//we have found a variable reference of required type in visibility scope of targetVariable
if (context.hasLocalType()) {
//there exists a local type in visibility scope of this variable declaration
//another variable declarations in scope of this local class may shadow input localVariable
//so finally check that found variable reference is really a reference to target variable
return variable == varRef.getDeclaration();
}
//else we can be sure that found reference is reference to variable
return true;
}
}
return false;
}
})
.forEach(outputConsumer);
}
private static class Context implements CtScannerListener {
int nrTypes = 0;
@Override
public ScanningMode enter(CtElement element) {
if (element instanceof CtType) {
nrTypes++;
}
return ScanningMode.NORMAL;
}
@Override
public void exit(CtElement element) {
if (element instanceof CtType) {
nrTypes--;
}
}
boolean hasLocalType() {
return nrTypes > 0;
}
}
private static final class QueryCreator extends CtAbstractVisitor {
CtElement scope;
CtScannerListener listener;
CtQuery query;
QueryCreator(CtElement scope, CtScannerListener listener) {
super();
this.scope = scope;
this.listener = listener;
}
@Override
public <T> void visitCtLocalVariable(CtLocalVariable<T> localVariable) {
query = scope.map(new LocalVariableScopeFunction(listener));
}
@Override
public <T> void visitCtCatchVariable(CtCatchVariable<T> catchVariable) {
query = scope.map(new CatchVariableScopeFunction(listener));
}
@Override
public <T> void visitCtParameter(CtParameter<T> parameter) {
query = scope.map(new ParameterScopeFunction(listener));
}
}
private CtQuery createScopeQuery(CtVariable<?> variable, CtElement scope, Context context) {
QueryCreator qc = new QueryCreator(scope, context);
variable.accept(qc);
if (qc.query == null) {
throw new SpoonException("Unexpected type of variable: " + variable.getClass().getName());
}
return qc.query;
}
}