lombok.eclipse.TransformEclipseAST Maven / Gradle / Ivy
/*
* Copyright (C) 2009-2020 The Project Lombok Authors.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package lombok.eclipse;
import static lombok.eclipse.handlers.EclipseHandlerUtil.*;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.Map;
import java.util.WeakHashMap;
import lombok.ConfigurationKeys;
import lombok.core.LombokConfiguration;
import lombok.core.debug.DebugSnapshotStore;
import lombok.core.debug.HistogramTracker;
import lombok.patcher.Symbols;
import lombok.permit.Permit;
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.FieldDeclaration;
import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
import org.eclipse.jdt.internal.compiler.ast.TypeReference;
import org.eclipse.jdt.internal.compiler.parser.Parser;
/**
* Entry point for the Eclipse Parser patch that lets lombok modify the Abstract Syntax Tree as generated by
* Eclipse's parser implementations. This class is injected into the appropriate OSGi ClassLoader and can thus
* use any classes that belong to org.eclipse.jdt.(apt.)core.
*
* Note that, for any Method body, if Bit24 is set, the Eclipse parser has been patched to never attempt to
* (re)parse it. You should set Bit24 on any MethodDeclaration object you inject into the AST:
*
* {@code methodDeclaration.bits |= ASTNode.Bit24; //0x800000}
*/
public class TransformEclipseAST {
private final EclipseAST ast;
//The patcher hacks this field onto CUD. It's public.
private static final Field astCacheField;
private static final HandlerLibrary handlers;
public static boolean disableLombok = false;
private static final HistogramTracker lombokTracker;
private static Map transformationStates = Collections.synchronizedMap(new WeakHashMap());
static {
String v = System.getProperty("lombok.histogram");
if (v == null) lombokTracker = null;
else if (v.toLowerCase().equals("sysout")) lombokTracker = new HistogramTracker("lombok.histogram", System.out);
else lombokTracker = new HistogramTracker("lombok.histogram");
}
static {
Field f = null;
HandlerLibrary h = null;
if (System.getProperty("lombok.disable") != null) {
disableLombok = true;
astCacheField = null;
handlers = null;
} else {
try {
h = HandlerLibrary.load();
} catch (Throwable t) {
try {
error(null, "Problem initializing lombok", t);
} catch (Throwable t2) {
System.err.println("Problem initializing lombok");
t.printStackTrace();
}
disableLombok = true;
}
try {
f = Permit.getField(CompilationUnitDeclaration.class, "$lombokAST");
} catch (Throwable t) {
//I guess we're in an ecj environment; we'll just not cache stuff then.
}
astCacheField = f;
handlers = h;
}
}
public static void transform_swapped(CompilationUnitDeclaration ast, Parser parser) {
transform(parser, ast);
}
public static EclipseAST getAST(CompilationUnitDeclaration ast, boolean forceRebuild) {
EclipseAST existing = null;
if (astCacheField != null) {
try {
existing = (EclipseAST) astCacheField.get(ast);
} catch (Exception e) {
// existing remains null
}
}
if (existing == null) {
existing = new EclipseAST(ast);
if (astCacheField != null) try {
astCacheField.set(ast, existing);
} catch (Exception ignore) {
}
} else {
existing.rebuild(forceRebuild);
}
return existing;
}
/**
* Check if lombok already handled the given AST. This method will return
* true
once for diet mode and once for full mode.
*
* The reason for this is that Eclipse invokes the transform method multiple
* times during compilation and it is enough to transform it once and not
* repeat the whole thing over and over again.
*
* @param ast The AST node belonging to the compilation unit (java speak for a single source file).
* @return true
if this AST was already handled by lombok.
*/
public static boolean alreadyTransformed(CompilationUnitDeclaration ast) {
State state = transformationStates.get(ast);
if (state == State.FULL) return true;
if (state == State.DIET) {
if (!EclipseAST.isComplete(ast)) return true;
transformationStates.put(ast, State.FULL);
} else {
transformationStates.put(ast, State.DIET);
}
return false;
}
/**
* This method is called immediately after Eclipse finishes building a CompilationUnitDeclaration, which is
* the top-level AST node when Eclipse parses a source file. The signature is 'magic' - you should not
* change it!
*
* Eclipse's parsers often operate in diet mode, which means many parts of the AST have been left blank.
* Be ready to deal with just about anything being null, such as the Statement[] arrays of the Method AST nodes.
*
* @param parser The Eclipse parser object that generated the AST. Not actually used; mostly there to satisfy parameter rules for lombok.patcher scripts.
* @param ast The AST node belonging to the compilation unit (java speak for a single source file).
*/
public static void transform(Parser parser, CompilationUnitDeclaration ast) {
if (disableLombok) return;
if (Symbols.hasSymbol("lombok.disable")) return;
if (alreadyTransformed(ast)) return;
// Do NOT abort if (ast.bits & ASTNode.HasAllMethodBodies) != 0 - that doesn't work.
if (Boolean.TRUE.equals(LombokConfiguration.read(ConfigurationKeys.LOMBOK_DISABLE, EclipseAST.getAbsoluteFileLocation(ast)))) return;
try {
DebugSnapshotStore.INSTANCE.snapshot(ast, "transform entry");
long histoToken = lombokTracker == null ? 0L : lombokTracker.start();
EclipseAST existing = getAST(ast, false);
existing.setSource(parser.scanner.getSource());
new TransformEclipseAST(existing).go();
if (lombokTracker != null) lombokTracker.end(histoToken);
DebugSnapshotStore.INSTANCE.snapshot(ast, "transform exit");
} catch (Throwable t) {
DebugSnapshotStore.INSTANCE.snapshot(ast, "transform error: %s", t.getClass().getSimpleName());
try {
String message = "Lombok can't parse this source: " + t.toString();
EclipseAST.addProblemToCompilationResult(ast.getFileName(), ast.compilationResult, false, message, 0, 0);
t.printStackTrace();
} catch (Throwable t2) {
try {
error(ast, "Can't create an error in the problems dialog while adding: " + t.toString(), t2);
} catch (Throwable t3) {
//This seems risky to just silently turn off lombok, but if we get this far, something pretty
//drastic went wrong. For example, the eclipse help system's JSP compiler will trigger a lombok call,
//but due to class loader shenanigans we'll actually get here due to a cascade of
//ClassNotFoundErrors. This is the right action for the help system (no lombok needed for that JSP compiler,
//of course). 'disableLombok' is static, but each context classloader (e.g. each eclipse OSGi plugin) has
//it's own edition of this class, so this won't turn off lombok everywhere.
disableLombok = true;
}
}
}
}
public TransformEclipseAST(EclipseAST ast) {
this.ast = ast;
}
/**
* First handles all lombok annotations except PrintAST, then calls all non-annotation based handlers.
* then handles any PrintASTs.
*/
public void go() {
long nextPriority = Long.MIN_VALUE;
for (Long d : handlers.getPriorities()) {
if (nextPriority > d) continue;
AnnotationVisitor visitor = new AnnotationVisitor(d);
ast.traverse(visitor);
// if no visitor interested for this AST, nextPriority would be MAX_VALUE and we bail out immediatetly
nextPriority = visitor.getNextPriority();
nextPriority = Math.min(nextPriority, handlers.callASTVisitors(ast, d, ast.isCompleteParse()));
}
}
private static class AnnotationVisitor extends EclipseASTAdapter {
private final long priority;
// this is the next priority we continue to visit.
// Long.MAX_VALUE means never. Each visit method will potentially reduce the next priority
private long nextPriority = Long.MAX_VALUE;
public AnnotationVisitor(long priority) {
this.priority = priority;
}
public long getNextPriority() {
return nextPriority;
}
@Override public void visitAnnotationOnField(FieldDeclaration field, EclipseNode annotationNode, Annotation annotation) {
CompilationUnitDeclaration top = (CompilationUnitDeclaration) annotationNode.top().get();
nextPriority = Math.min(nextPriority, handlers.handleAnnotation(top, annotationNode, annotation, priority));
}
@Override public void visitAnnotationOnMethodArgument(Argument arg, AbstractMethodDeclaration method, EclipseNode annotationNode, Annotation annotation) {
CompilationUnitDeclaration top = (CompilationUnitDeclaration) annotationNode.top().get();
nextPriority = Math.min(nextPriority, handlers.handleAnnotation(top, annotationNode, annotation, priority));
}
@Override public void visitAnnotationOnLocal(LocalDeclaration local, EclipseNode annotationNode, Annotation annotation) {
CompilationUnitDeclaration top = (CompilationUnitDeclaration) annotationNode.top().get();
nextPriority = Math.min(nextPriority, handlers.handleAnnotation(top, annotationNode, annotation, priority));
}
@Override public void visitAnnotationOnMethod(AbstractMethodDeclaration method, EclipseNode annotationNode, Annotation annotation) {
CompilationUnitDeclaration top = (CompilationUnitDeclaration) annotationNode.top().get();
nextPriority = Math.min(nextPriority, handlers.handleAnnotation(top, annotationNode, annotation, priority));
}
@Override public void visitAnnotationOnType(TypeDeclaration type, EclipseNode annotationNode, Annotation annotation) {
CompilationUnitDeclaration top = (CompilationUnitDeclaration) annotationNode.top().get();
nextPriority = Math.min(nextPriority, handlers.handleAnnotation(top, annotationNode, annotation, priority));
}
@Override public void visitAnnotationOnTypeUse(TypeReference typeUse, EclipseNode annotationNode, Annotation annotation) {
CompilationUnitDeclaration top = (CompilationUnitDeclaration) annotationNode.top().get();
nextPriority = Math.min(nextPriority, handlers.handleAnnotation(top, annotationNode, annotation, priority));
}
}
private static enum State {
DIET,
FULL
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy