package lombok.eclipse;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import lombok.Lombok;
import lombok.core.AST;
import lombok.core.LombokImmutableList;
import lombok.eclipse.handlers.EclipseHandlerUtil;
import lombok.permit.Permit;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.internal.compiler.CompilationResult;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.Annotation;
import org.eclipse.jdt.internal.compiler.ast.Argument;
import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration;
import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
import org.eclipse.jdt.internal.compiler.ast.ImportReference;
import org.eclipse.jdt.internal.compiler.ast.Initializer;
import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration;
import org.eclipse.jdt.internal.compiler.ast.ParameterizedQualifiedTypeReference;
import org.eclipse.jdt.internal.compiler.ast.ParameterizedSingleTypeReference;
import org.eclipse.jdt.internal.compiler.ast.Statement;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
import org.eclipse.jdt.internal.compiler.ast.TypeReference;
import org.eclipse.jdt.internal.compiler.ast.Wildcard;
public class EclipseAST extends AST<EclipseAST, EclipseNode, ASTNode> {
public EclipseAST(CompilationUnitDeclaration ast) {
super(toFileName(ast), packageDeclaration(ast), new EclipseImportList(ast), statementTypes());
this.compilationUnitDeclaration = ast;
setTop(buildCompilationUnit(ast));
this.completeParse = isComplete(ast);
clearChanged();
}
private static volatile boolean skipEclipseWorkspaceBasedFileResolver = false;
private static final URI NOT_CALCULATED_MARKER = URI.create("https://projectlombok.org/not/calculated");
private URI memoizedAbsoluteFileLocation = NOT_CALCULATED_MARKER;
public static URI getAbsoluteFileLocation(CompilationUnitDeclaration ast) {
return getAbsoluteFileLocation0(ast);
}
public URI getAbsoluteFileLocation() {
if (memoizedAbsoluteFileLocation != NOT_CALCULATED_MARKER) return memoizedAbsoluteFileLocation;
memoizedAbsoluteFileLocation = getAbsoluteFileLocation0(this.compilationUnitDeclaration);
return memoizedAbsoluteFileLocation;
}
private static URI getAbsoluteFileLocation0(CompilationUnitDeclaration ast) {
String fileName = toFileName(ast);
if (fileName != null && (fileName.startsWith("file:") || fileName.startsWith("sourcecontrol:"))) {
return URI.create(fileName);
}
if (!skipEclipseWorkspaceBasedFileResolver) {
try {
try {
return EclipseWorkspaceBasedFileResolver.resolve(fileName);
} catch (IllegalArgumentException e) {
EclipseHandlerUtil.warning("Finding 'lombok.config' file failed for '" + fileName + "'", e);
}
} catch (NoClassDefFoundError e) {
skipEclipseWorkspaceBasedFileResolver = true;
}
}
try {
return new File(fileName).getAbsoluteFile().toURI();
} catch (Exception e) {
return null;
}
}
private static class EclipseWorkspaceBasedFileResolver {
public static URI resolve(String path) {
if (path == null || path.indexOf('/', 1) == -1) {
return null;
}
try {
return ResourcesPlugin.getWorkspace().getRoot().getFile(new Path(path)).getLocationURI();
} catch (Exception e) {
return null;
}
}
}
private static String packageDeclaration(CompilationUnitDeclaration cud) {
ImportReference pkg = cud.currentPackage;
return pkg == null ? null : Eclipse.toQualifiedName(pkg.getImportName());
}
@Override public int getSourceVersion() {
long sl = compilationUnitDeclaration.problemReporter.options.sourceLevel;
long cl = compilationUnitDeclaration.problemReporter.options.complianceLevel;
sl >>= 16;
cl >>= 16;
if (sl == 0) sl = cl;
if (cl == 0) cl = sl;
return Math.min((int)(sl - 44), (int)(cl - 44));
}
@Override public int getLatestJavaSpecSupported() {
return Eclipse.getEcjCompilerVersion();
}
public void traverse(EclipseASTVisitor visitor) {
top().traverse(visitor);
}
void traverseChildren(EclipseASTVisitor visitor, EclipseNode node) {
LombokImmutableList<EclipseNode> children = node.down();
int len = children.size();
for (int i = 0; i < len; i++) {
children.get(i).traverse(visitor);
}
}
public boolean isCompleteParse() {
return completeParse;
}
class ParseProblem {
final boolean isWarning;
final String message;
final int sourceStart;
final int sourceEnd;
ParseProblem(boolean isWarning, String message, int sourceStart, int sourceEnd) {
this.isWarning = isWarning;
this.message = message;
this.sourceStart = sourceStart;
this.sourceEnd = sourceEnd;
}
void addToCompilationResult() {
CompilationUnitDeclaration cud = (CompilationUnitDeclaration) top().get();
addProblemToCompilationResult(cud.getFileName(), cud.compilationResult,
isWarning, message, sourceStart, sourceEnd);
}
}
private void propagateProblems() {
if (queuedProblems.isEmpty()) return;
CompilationUnitDeclaration cud = (CompilationUnitDeclaration) top().get();
if (cud.compilationResult == null) return;
for (ParseProblem problem : queuedProblems) problem.addToCompilationResult();
queuedProblems.clear();
}
private final List<ParseProblem> queuedProblems = new ArrayList<ParseProblem>();
void addProblem(ParseProblem problem) {
queuedProblems.add(problem);
propagateProblems();
}
public static void addProblemToCompilationResult(char[] fileNameArray, CompilationResult result,
boolean isWarning, String message, int sourceStart, int sourceEnd) {
try {
EcjReflectionCheck.addProblemToCompilationResult.invoke(null, fileNameArray, result, isWarning, message, sourceStart, sourceEnd);
} catch (NoClassDefFoundError e) {
} catch (IllegalAccessException e) {
throw Lombok.sneakyThrow(e);
} catch (InvocationTargetException e) {
throw Lombok.sneakyThrow(e);
} catch (NullPointerException e) {
if (!"false".equals(System.getProperty("lombok.debug.reflection", "false"))) {
e.initCause(EcjReflectionCheck.problemAddProblemToCompilationResult);
throw e;
}
}
}
public static Annotation[] getTopLevelTypeReferenceAnnotations(TypeReference tr) {
Method m = EcjReflectionCheck.typeReferenceGetAnnotationsOnDimensions;
if (m == null) return null;
Annotation[][] annss = null;
try {
annss = (Annotation[][]) m.invoke(tr);
if (annss != null) return annss[0];
} catch (Throwable ignore) {}
try {
Field f = EcjReflectionCheck.typeReferenceAnnotations;
if (f == null) return null;
annss = (Annotation[][]) f.get(tr);
if (annss == null) return null;
return annss[annss.length - 1];
} catch (Throwable t) {
return null;
}
}
private final CompilationUnitDeclaration compilationUnitDeclaration;
private boolean completeParse;
private static String toFileName(CompilationUnitDeclaration ast) {
return ast.compilationResult.fileName == null ? null : new String(ast.compilationResult.fileName);
}
public void rebuild(boolean force) {
propagateProblems();
if (completeParse && !force) return;
boolean changed = isChanged();
boolean newCompleteParse = isComplete(compilationUnitDeclaration);
if (!newCompleteParse && !force) return;
top().rebuild();
this.completeParse = newCompleteParse;
if (!changed) clearChanged();
}
public static boolean isComplete(CompilationUnitDeclaration unit) {
return (unit.bits & ASTNode.HasAllMethodBodies) != 0;
}
@Override protected EclipseNode buildTree(ASTNode node, Kind kind) {
switch (kind) {
case COMPILATION_UNIT:
return buildCompilationUnit((CompilationUnitDeclaration) node);
case TYPE:
return buildType((TypeDeclaration) node);
case FIELD:
return buildField((FieldDeclaration) node);
case INITIALIZER:
return buildInitializer((Initializer) node);
case METHOD:
return buildMethod((AbstractMethodDeclaration) node);
case ARGUMENT:
return buildLocal((Argument) node, kind);
case LOCAL:
return buildLocal((LocalDeclaration) node, kind);
case STATEMENT:
return buildStatement((Statement) node);
case ANNOTATION:
return buildAnnotation((Annotation) node, false);
case TYPE_USE:
return buildTypeUse((TypeReference) node);
default:
throw new AssertionError("Did not expect to arrive here: " + kind);
}
}
private EclipseNode buildCompilationUnit(CompilationUnitDeclaration top) {
if (setAndGetAsHandled(top)) return null;
List<EclipseNode> children = buildTypes(top.types);
return putInMap(new EclipseNode(this, top, children, Kind.COMPILATION_UNIT));
}
private void addIfNotNull(Collection<EclipseNode> collection, EclipseNode n) {
if (n != null) collection.add(n);
}
private List<EclipseNode> buildTypes(TypeDeclaration[] children) {
List<EclipseNode> childNodes = new ArrayList<EclipseNode>();
if (children != null) for (TypeDeclaration type : children) addIfNotNull(childNodes, buildType(type));
return childNodes;
}
private EclipseNode buildType(TypeDeclaration type) {
if (setAndGetAsHandled(type)) return null;
List<EclipseNode> childNodes = new ArrayList<EclipseNode>();
childNodes.addAll(buildFields(type.fields));
childNodes.addAll(buildTypes(type.memberTypes));
childNodes.addAll(buildMethods(type.methods));
childNodes.addAll(buildAnnotations(type.annotations, false));
return putInMap(new EclipseNode(this, type, childNodes, Kind.TYPE));
}
private Collection<EclipseNode> buildFields(FieldDeclaration[] children) {
List<EclipseNode> childNodes = new ArrayList<EclipseNode>();
if (children != null) for (FieldDeclaration child : children) addIfNotNull(childNodes, buildField(child));
return childNodes;
}
private static <T> List<T> singleton(T item) {
List<T> list = new ArrayList<T>();
if (item != null) list.add(item);
return list;
}
private EclipseNode buildField(FieldDeclaration field) {
if (field instanceof Initializer) return buildInitializer((Initializer)field);
if (setAndGetAsHandled(field)) return null;
List<EclipseNode> childNodes = new ArrayList<EclipseNode>();
addIfNotNull(childNodes, buildTypeUse(field.type));
addIfNotNull(childNodes, buildStatement(field.initialization));
childNodes.addAll(buildAnnotations(field.annotations, true));
return putInMap(new EclipseNode(this, field, childNodes, Kind.FIELD));
}
private EclipseNode buildInitializer(Initializer initializer) {
if (setAndGetAsHandled(initializer)) return null;
return putInMap(new EclipseNode(this, initializer, singleton(buildStatement(initializer.block)), Kind.INITIALIZER));
}
private Collection<EclipseNode> buildMethods(AbstractMethodDeclaration[] children) {
List<EclipseNode> childNodes = new ArrayList<EclipseNode>();
if (children != null) for (AbstractMethodDeclaration method : children) addIfNotNull(childNodes, buildMethod(method));
return childNodes;
}
private EclipseNode buildMethod(AbstractMethodDeclaration method) {
if (setAndGetAsHandled(method)) return null;
List<EclipseNode> childNodes = new ArrayList<EclipseNode>();
childNodes.addAll(buildArguments(method.arguments));
if (method instanceof ConstructorDeclaration) {
ConstructorDeclaration constructor = (ConstructorDeclaration) method;
addIfNotNull(childNodes, buildStatement(constructor.constructorCall));
}
childNodes.addAll(buildStatements(method.statements));
childNodes.addAll(buildAnnotations(method.annotations, false));
return putInMap(new EclipseNode(this, method, childNodes, Kind.METHOD));
}
private Collection<EclipseNode> buildArguments(Argument[] children) {
List<EclipseNode> childNodes = new ArrayList<EclipseNode>();
if (children != null) for (LocalDeclaration local : children) {
addIfNotNull(childNodes, buildLocal(local, Kind.ARGUMENT));
}
return childNodes;
}
private EclipseNode buildLocal(LocalDeclaration local, Kind kind) {
if (setAndGetAsHandled(local)) return null;
List<EclipseNode> childNodes = new ArrayList<EclipseNode>();
addIfNotNull(childNodes, buildTypeUse(local.type));
addIfNotNull(childNodes, buildStatement(local.initialization));
childNodes.addAll(buildAnnotations(local.annotations, true));
return putInMap(new EclipseNode(this, local, childNodes, kind));
}
private EclipseNode buildTypeUse(TypeReference tr) {
if (setAndGetAsHandled(tr)) return null;
if (tr == null) return null;
List<EclipseNode> childNodes = new ArrayList<EclipseNode>();
Annotation[] anns = getTopLevelTypeReferenceAnnotations(tr);
if (anns != null) for (Annotation ann : anns) addIfNotNull(childNodes, buildAnnotation(ann, false));
if (tr instanceof ParameterizedQualifiedTypeReference) {
ParameterizedQualifiedTypeReference pqtr = (ParameterizedQualifiedTypeReference) tr;
int len = pqtr.tokens.length;
for (int i = 0; i < len; i++) {
TypeReference[] typeArgs = pqtr.typeArguments[i];
if (typeArgs != null) for (TypeReference tArg : typeArgs) addIfNotNull(childNodes, buildTypeUse(tArg));
}
} else if (tr instanceof ParameterizedSingleTypeReference) {
ParameterizedSingleTypeReference pstr = (ParameterizedSingleTypeReference) tr;
if (pstr.typeArguments != null) for (TypeReference tArg : pstr.typeArguments) {
addIfNotNull(childNodes, buildTypeUse(tArg));
}
} else if (tr instanceof Wildcard) {
TypeReference bound = ((Wildcard) tr).bound;
if (bound != null) addIfNotNull(childNodes, buildTypeUse(bound));
}
return putInMap(new EclipseNode(this, tr, childNodes, Kind.TYPE_USE));
}
private Collection<EclipseNode> buildAnnotations(Annotation[] annotations, boolean varDecl) {
List<EclipseNode> elements = new ArrayList<EclipseNode>();
if (annotations != null) for (Annotation an : annotations) addIfNotNull(elements, buildAnnotation(an, varDecl));
return elements;
}
private EclipseNode buildAnnotation(Annotation annotation, boolean field) {
if (annotation == null) return null;
boolean handled = setAndGetAsHandled(annotation);
if (!field && handled) {
return null;
}
return putInMap(new EclipseNode(this, annotation, null, Kind.ANNOTATION));
}
private Collection<EclipseNode> buildStatements(Statement[] children) {
List<EclipseNode> childNodes = new ArrayList<EclipseNode>();
if (children != null) for (Statement child : children) addIfNotNull(childNodes, buildStatement(child));
return childNodes;
}
private EclipseNode buildStatement(Statement child) {
if (child == null) return null;
if (child instanceof TypeDeclaration) return buildType((TypeDeclaration) child);
if (child instanceof LocalDeclaration) return buildLocal((LocalDeclaration) child, Kind.LOCAL);
if (setAndGetAsHandled(child)) return null;
return drill(child);
}
private EclipseNode drill(Statement statement) {
List<EclipseNode> childNodes = new ArrayList<EclipseNode>();
for (FieldAccess fa : fieldsOf(statement.getClass())) childNodes.addAll(buildWithField(EclipseNode.class, statement, fa));
return putInMap(new EclipseNode(this, statement, childNodes, Kind.STATEMENT));
}
private static Collection<Class<? extends ASTNode>> statementTypes() {
return Collections.<Class<? extends ASTNode>>singleton(Statement.class);
}
private static class EcjReflectionCheck {
private static final String COMPILATIONRESULT_TYPE = "org.eclipse.jdt.internal.compiler.CompilationResult";
public static final Method addProblemToCompilationResult;
public static final Throwable problemAddProblemToCompilationResult;
public static final Method typeReferenceGetAnnotationsOnDimensions;
public static final Field typeReferenceAnnotations;
static {
Throwable problem_ = null;
Method m1 = null, m2;
Field f;
try {
m1 = Permit.getMethod(EclipseAstProblemView.class, "addProblemToCompilationResult", char[].class, Class.forName(COMPILATIONRESULT_TYPE), boolean.class, String.class, int.class, int.class);
} catch (Throwable t) {
problem_ = t;
}
try {
m2 = Permit.getMethod(TypeReference.class, "getAnnotationsOnDimensions");
} catch (Throwable t) {
m2 = null;
}
try {
f = Permit.getField(TypeReference.class, "annotations");
} catch (Throwable t) {
f = null;
}
addProblemToCompilationResult = m1;
problemAddProblemToCompilationResult = problem_;
typeReferenceGetAnnotationsOnDimensions = m2;
typeReferenceAnnotations = f;
}
}
}