/** * 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.support.compiler.jdt; import org.eclipse.jdt.internal.compiler.ast.ASTNode; import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; import org.eclipse.jdt.internal.compiler.ast.Expression; import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; import org.eclipse.jdt.internal.compiler.ast.TypeReference; import org.eclipse.jdt.internal.compiler.lookup.FieldBinding; import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; import spoon.compiler.Environment; import spoon.reflect.code.CtCatchVariable; import spoon.reflect.code.CtConstructorCall; import spoon.reflect.code.CtExpression; import spoon.reflect.code.CtLocalVariable; import spoon.reflect.code.CtStatement; import spoon.reflect.cu.CompilationUnit; import spoon.reflect.declaration.CtElement; import spoon.reflect.declaration.CtExecutable; import spoon.reflect.declaration.CtField; import spoon.reflect.declaration.CtPackage; import spoon.reflect.declaration.CtType; import spoon.reflect.declaration.CtTypedElement; import spoon.reflect.declaration.CtVariable; import spoon.reflect.factory.ClassFactory; import spoon.reflect.factory.CoreFactory; import spoon.reflect.factory.FieldFactory; import spoon.reflect.factory.InterfaceFactory; import spoon.reflect.factory.TypeFactory; import spoon.reflect.reference.CtReference; import spoon.reflect.reference.CtTypeReference; import spoon.reflect.visitor.DefaultJavaPrettyPrinter; import spoon.reflect.visitor.EarlyTerminatingScanner; import spoon.support.SpoonClassNotFoundException; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; import java.util.List; import static spoon.reflect.ModelElementContainerDefaultCapacities.CASTS_CONTAINER_DEFAULT_CAPACITY; import static java.lang.String.format; public class ContextBuilder { Deque<String> annotationValueName = new ArrayDeque<>(); List<CtTypeReference<?>> casts = new ArrayList<>(CASTS_CONTAINER_DEFAULT_CAPACITY); CompilationUnitDeclaration compilationunitdeclaration; CompilationUnit compilationUnitSpoon; Deque<String> label = new ArrayDeque<>(); boolean isBuildLambda = false; boolean ignoreComputeImports = false; /** * Stack of all parents elements */ Deque<ASTPair> stack = new ArrayDeque<>(); private final JDTTreeBuilder jdtTreeBuilder; ContextBuilder(JDTTreeBuilder jdtTreeBuilder) { this.jdtTreeBuilder = jdtTreeBuilder; } @SuppressWarnings("unchecked") void enter(CtElement e, ASTNode node) { stack.push(new ASTPair(e, node)); if (!(e instanceof CtPackage) || (compilationUnitSpoon.getFile() != null && compilationUnitSpoon.getFile().getName().equals(DefaultJavaPrettyPrinter.JAVA_PACKAGE_DECLARATION))) { if (compilationunitdeclaration != null && !e.isImplicit()) { e.setPosition(this.jdtTreeBuilder.getPositionBuilder().buildPositionCtElement(e, node)); } } ASTPair pair = stack.peek(); CtElement current = pair.element; if (current instanceof CtExpression) { while (!casts.isEmpty()) { ((CtExpression<?>) current).addTypeCast(casts.remove(0)); } } if (current instanceof CtStatement && !this.label.isEmpty()) { ((CtStatement) current).setLabel(this.label.pop()); } try { if (e instanceof CtTypedElement && !(e instanceof CtConstructorCall) && node instanceof Expression) { if (((CtTypedElement<?>) e).getType() == null) { ((CtTypedElement<Object>) e).setType(this.jdtTreeBuilder.getReferencesBuilder().getTypeReference(((Expression) node).resolvedType)); } } } catch (UnsupportedOperationException ignore) { // For some element, we throw an UnsupportedOperationException when we call setType(). } } void exit(ASTNode node) { ASTPair pair = stack.pop(); if (pair.node != node) { throw new RuntimeException("Inconsistent Stack " + node + "\n" + pair.node); } CtElement current = pair.element; if (!stack.isEmpty()) { this.jdtTreeBuilder.getExiter().setChild(current); this.jdtTreeBuilder.getExiter().setChild(pair.node); this.jdtTreeBuilder.getExiter().scan(stack.peek().element); } } @SuppressWarnings("unchecked") <T> CtLocalVariable<T> getLocalVariableDeclaration(final String name) { final Class<CtLocalVariable<T>> clazz = (Class<CtLocalVariable<T>>) jdtTreeBuilder.getFactory().Core().createLocalVariable().getClass(); final CtLocalVariable<T> localVariable = this.<T, CtLocalVariable<T>>getVariableDeclaration(name, clazz); if (localVariable == null) { // note: this happens when using the new try(vardelc) structure this.jdtTreeBuilder.getLogger().error( format("Could not find declaration for local variable %s at %s", name, stack.peek().element.getPosition())); } return localVariable; } @SuppressWarnings("unchecked") <T> CtCatchVariable<T> getCatchVariableDeclaration(final String name) { final Class<CtCatchVariable<T>> clazz = (Class<CtCatchVariable<T>>) jdtTreeBuilder.getFactory().Core().createCatchVariable().getClass(); final CtCatchVariable<T> catchVariable = this.<T, CtCatchVariable<T>>getVariableDeclaration(name, clazz); if (catchVariable == null) { // note: this happens when using the new try(vardelc) structure this.jdtTreeBuilder.getLogger().error( format("Could not find declaration for catch variable %s at %s", name, stack.peek().element.getPosition())); } return catchVariable; } <T> CtVariable<T> getVariableDeclaration(final String name) { final CtVariable<T> variable = this.<T, CtVariable<T>>getVariableDeclaration(name, null); if (variable == null) { // note: this happens when using the new try(vardelc) structure this.jdtTreeBuilder.getLogger().error( format("Could not find declaration for variable %s at %s", name, stack.peek().element.getPosition())); } return variable; } @SuppressWarnings("unchecked") private <T, U extends CtVariable<T>> U getVariableDeclaration( final String name, final Class<U> clazz) { final CoreFactory coreFactory = jdtTreeBuilder.getFactory().Core(); final TypeFactory typeFactory = jdtTreeBuilder.getFactory().Type(); final ClassFactory classFactory = jdtTreeBuilder.getFactory().Class(); final InterfaceFactory interfaceFactory = jdtTreeBuilder.getFactory().Interface(); final FieldFactory fieldFactory = jdtTreeBuilder.getFactory().Field(); final ReferenceBuilder referenceBuilder = jdtTreeBuilder.getReferencesBuilder(); final Environment environment = jdtTreeBuilder.getFactory().getEnvironment(); // there is some extra work to do if we are looking for CtFields (and subclasses) final boolean lookingForFields = clazz == null || coreFactory.createField().getClass().isAssignableFrom(clazz); // try to find the variable on stack beginning with the most recent element for (final ASTPair astPair : stack) { // the variable may have been declared directly by one of these elements final ScopeRespectingVariableScanner<U> scanner = new ScopeRespectingVariableScanner(name, clazz); astPair.element.accept(scanner); if (scanner.getResult() != null) { return scanner.getResult(); } // the variable may have been declared in a super class/interface if (lookingForFields && astPair.node instanceof TypeDeclaration) { final TypeDeclaration nodeDeclaration = (TypeDeclaration) astPair.node; final Deque<ReferenceBinding> referenceBindings = new ArrayDeque<>(); // add super class if any if (nodeDeclaration.superclass != null && nodeDeclaration.superclass.resolvedType instanceof ReferenceBinding) { referenceBindings.push((ReferenceBinding) nodeDeclaration.superclass.resolvedType); } // add interfaces if any if (nodeDeclaration.superInterfaces != null) { for (final TypeReference tr : nodeDeclaration.superInterfaces) { if (tr.resolvedType instanceof ReferenceBinding) { referenceBindings.push((ReferenceBinding) tr.resolvedType); } } } while (!referenceBindings.isEmpty()) { final ReferenceBinding referenceBinding = referenceBindings.pop(); for (final FieldBinding fieldBinding : referenceBinding.fields()) { if (name.equals(new String(fieldBinding.readableName()))) { final String qualifiedNameOfParent = new String(referenceBinding.readableName()); final CtType parentOfField = referenceBinding.isClass() ? classFactory.create(qualifiedNameOfParent) : interfaceFactory.create(qualifiedNameOfParent); return (U) fieldFactory.create(parentOfField, JDTTreeBuilderQuery.getModifiers(fieldBinding.modifiers), referenceBuilder.getTypeReference(fieldBinding.type), name); } } // add super class if any final ReferenceBinding superclass = referenceBinding.superclass(); if (superclass != null) { referenceBindings.push(superclass); } // add interfaces if any final ReferenceBinding[] interfaces = referenceBinding.superInterfaces(); if (interfaces != null) { for (ReferenceBinding rb : interfaces) { referenceBindings.push(rb); } } } } } // the variable may have been imported statically from another class/interface if (lookingForFields) { final CtReference potentialReferenceToField = referenceBuilder.getDeclaringReferenceFromImports(name.toCharArray()); if (potentialReferenceToField != null && potentialReferenceToField instanceof CtTypeReference) { final CtTypeReference typeReference = (CtTypeReference) potentialReferenceToField; try { final Class classOfType = typeReference.getActualClass(); if (classOfType != null) { final CtType declaringTypeOfField = typeReference.isInterface() ? interfaceFactory.get(classOfType) : classFactory.get(classOfType); final CtField field = declaringTypeOfField.getField(name); if (field != null) { return (U) field; } } } catch (final SpoonClassNotFoundException scnfe) { // in noclasspath mode we do some heuristics to determine if `name` could be a // field that has been imported statically from another class (or interface). if (environment.getNoClasspath()) { // if `potentialReferenceToField` is a `CtTypeReference` then `name` must // have been imported statically. Otherwise, `potentialReferenceToField` // would be a CtPackageReference! // if `name` consists only of upper case characters separated by '_', we // assume a constant value according to JLS. if (name.toUpperCase().equals(name)) { final CtType parentOfField = classFactory.create(typeReference.getQualifiedName()); // it is the best thing we can do final CtField field = coreFactory.createField(); field.setParent(parentOfField); field.setSimpleName(name); // it is the best thing we can do field.setType(typeFactory.nullType()); return (U) field; } } } } } return null; } /** * An {@link EarlyTerminatingScanner} that is supposed to find a {@link CtVariable} with * specific name respecting the current scope given by {@link ContextBuilder#stack}. * @param <T> The actual type of the {@link CtVariable} we are looking for. Examples include * {@link CtLocalVariable}, {@link CtField}, and so on. */ private class ScopeRespectingVariableScanner<T extends CtVariable> extends EarlyTerminatingScanner<T> { /** * The class object of {@link T} that is required to filter particular elements in * {@link #scan(CtElement)}. */ private final Class<T> clazz; /** * The name of the variable we are looking for ({@link CtVariable#getSimpleName()}). */ final String name; /** * Creates a new {@link EarlyTerminatingScanner} that tries to find a {@link CtVariable} * with name {@code pName} (using {@link CtVariable#getSimpleName()}) and upper type bound * {@code pType}. * * @param pName The name of the variable we are looking for. * @param pType {@link T}'s class object ({@link Object#getClass()}). {@link null} values * are permitted and indicate that we are looking for any subclass of * {@link CtVariable} (including {@link CtVariable} itself). */ ScopeRespectingVariableScanner(final String pName, final Class<T> pType) { clazz = (Class<T>) (pType == null ? CtVariable.class : pType); name = pName; } @Override public void scan(final CtElement element) { if (element != null && clazz.isAssignableFrom(element.getClass())) { final T potentialVariable = (T) element; if (name.equals(potentialVariable.getSimpleName())) { // Since the AST is not completely available yet, we can not check if element's // parent (ep) contains the innermost element of `stack` (ie). Therefore, we // have to check if one of the following condition holds: // // 1) Does `stack` contain `ep`? // 2) Is `ep` the body of one of `stack`'s CtExecutable elements? // // The first condition is easy to see. If `stack` contains `ep` then `ep` and // all it's declared variables are in scope of `ie`. Unfortunately, there is a // special case in which a variable (a CtLocalVariable) has been declared in a // block (CtBlock) of, for instance, a method. Such a block is not contained in // `stack`. This peculiarity calls for the second condition. final CtElement parentOfPotentialVariable = potentialVariable.getParent(); for (final ASTPair astPair : stack) { if (astPair.element == parentOfPotentialVariable) { finish(potentialVariable); return; } else if (astPair.element instanceof CtExecutable) { final CtExecutable executable = (CtExecutable) astPair.element; if (executable.getBody() == parentOfPotentialVariable) { finish(potentialVariable); return; } } } } } super.scan(element); } private void finish(final T element) { setResult(element); terminate(); } } }