All Downloads are FREE. Search and download functionalities are using the official Maven repository.

lombok.javac.handlers.JavacHandlerUtil Maven / Gradle / Ivy

Go to download

Spice up your java: Automatic Resource Management, automatic generation of getters, setters, equals, hashCode and toString, and more!

There is a newer version: 1.18.32
Show newest version
/*
 * Copyright (C) 2009-2022 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.javac.handlers;

import static com.sun.tools.javac.code.Flags.GENERATEDCONSTR;
import static lombok.core.handlers.HandlerUtil.*;
import static lombok.javac.Javac.*;
import static lombok.javac.JavacAugments.JCTree_generatedNode;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;

import com.sun.tools.javac.code.Attribute;
import com.sun.tools.javac.code.BoundKind;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.code.Scope;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symbol.ClassSymbol;
import com.sun.tools.javac.code.Symbol.TypeSymbol;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.comp.Annotate;
import com.sun.tools.javac.comp.AttrContext;
import com.sun.tools.javac.comp.Enter;
import com.sun.tools.javac.comp.Env;
import com.sun.tools.javac.comp.MemberEnter;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCAnnotation;
import com.sun.tools.javac.tree.JCTree.JCArrayTypeTree;
import com.sun.tools.javac.tree.JCTree.JCAssign;
import com.sun.tools.javac.tree.JCTree.JCBlock;
import com.sun.tools.javac.tree.JCTree.JCClassDecl;
import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
import com.sun.tools.javac.tree.JCTree.JCExpression;
import com.sun.tools.javac.tree.JCTree.JCExpressionStatement;
import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
import com.sun.tools.javac.tree.JCTree.JCIdent;
import com.sun.tools.javac.tree.JCTree.JCImport;
import com.sun.tools.javac.tree.JCTree.JCLiteral;
import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
import com.sun.tools.javac.tree.JCTree.JCModifiers;
import com.sun.tools.javac.tree.JCTree.JCNewArray;
import com.sun.tools.javac.tree.JCTree.JCPrimitiveTypeTree;
import com.sun.tools.javac.tree.JCTree.JCStatement;
import com.sun.tools.javac.tree.JCTree.JCTypeApply;
import com.sun.tools.javac.tree.JCTree.JCTypeParameter;
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
import com.sun.tools.javac.tree.JCTree.JCWildcard;
import com.sun.tools.javac.tree.JCTree.TypeBoundKind;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.tree.TreeScanner;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.ListBuffer;
import com.sun.tools.javac.util.Name;
import com.sun.tools.javac.util.Options;

import lombok.AccessLevel;
import lombok.ConfigurationKeys;
import lombok.Data;
import lombok.Getter;
import lombok.core.AST.Kind;
import lombok.core.AnnotationValues;
import lombok.core.AnnotationValues.AnnotationValue;
import lombok.core.CleanupTask;
import lombok.core.LombokImmutableList;
import lombok.core.TypeResolver;
import lombok.core.configuration.CheckerFrameworkVersion;
import lombok.core.configuration.NullAnnotationLibrary;
import lombok.core.configuration.NullCheckExceptionType;
import lombok.core.configuration.TypeName;
import lombok.core.handlers.HandlerUtil;
import lombok.core.handlers.HandlerUtil.FieldAccess;
import lombok.delombok.LombokOptionsFactory;
import lombok.experimental.Accessors;
import lombok.experimental.Tolerate;
import lombok.javac.Javac;
import lombok.javac.JavacAugments;
import lombok.javac.JavacNode;
import lombok.javac.JavacTreeMaker;
import lombok.javac.JavacTreeMaker.TypeTag;
import lombok.permit.Permit;

/**
 * Container for static utility methods useful to handlers written for javac.
 */
public class JavacHandlerUtil {
	private JavacHandlerUtil() {
		//Prevent instantiation
	}
	
	private static class MarkingScanner extends TreeScanner {
		private final JavacNode source;
		
		MarkingScanner(JavacNode source) {
			this.source = source;
		}
		
		@Override public void scan(JCTree tree) {
			if (tree == null) return;
			setGeneratedBy(tree, source);
			super.scan(tree);
		}
	}
	
	/**
	 * Contributed by Jan Lahoda; many lombok transformations should not be run (or a lite version should be run) when the netbeans editor
	 * is running javac on the open source file to find inline errors and such. As class files are compiled separately this does not affect
	 * actual runtime behaviour or file output of the netbeans IDE.
	 */
	public static boolean inNetbeansEditor(JavacNode node) {
		return inNetbeansEditor(node.getContext());
	}
	
	public static boolean inNetbeansEditor(Context context) {
		Options options = Options.instance(context);
		return (options.keySet().contains("ide") && !options.keySet().contains("backgroundCompilation"));
	}
	
	public static boolean inNetbeansCompileOnSave(Context context) {
		Options options = Options.instance(context);
		return (options.keySet().contains("ide") && options.keySet().contains("backgroundCompilation"));
	}
	
	public static JCTree getGeneratedBy(JCTree node) {
		return JCTree_generatedNode.get(node);
	}
	
	public static boolean isGenerated(JCTree node) {
		return getGeneratedBy(node) != null;
	}
	
	public static  T recursiveSetGeneratedBy(T node, JavacNode source) {
		if (node == null) return null;
		setGeneratedBy(node, source);
		node.accept(new MarkingScanner(source));
		return node;
	}
	
	public static  T setGeneratedBy(T node, JavacNode sourceNode) {
		if (node == null) return null;
		if (sourceNode == null) {
			JCTree_generatedNode.clear(node);
			return node;
		}
		JCTree_generatedNode.set(node, sourceNode.get());
		
		if (!inNetbeansEditor(sourceNode.getContext()) || isParameter(node)) {
			node.pos = sourceNode.getStartPos();
			storeEnd(node, sourceNode.getEndPosition(), (JCCompilationUnit) sourceNode.top().get());
		}
		return node;
	}

	public static boolean isParameter(JCTree node) {
		return node instanceof JCVariableDecl && (((JCVariableDecl) node).mods.flags & Flags.PARAMETER) != 0;
	}
	
	public static boolean hasAnnotation(String type, JavacNode node) {
		return hasAnnotation(type, node, false);
	}
	
	public static boolean hasAnnotation(Class type, JavacNode node) {
		return hasAnnotation(type, node, false);
	}
	
	public static boolean hasAnnotationAndDeleteIfNeccessary(Class type, JavacNode node) {
		return hasAnnotation(type, node, true);
	}
	
	private static boolean hasAnnotation(Class type, JavacNode node, boolean delete) {
		if (node == null) return false;
		if (type == null) return false;
		switch (node.getKind()) {
		case ARGUMENT:
		case FIELD:
		case LOCAL:
		case TYPE:
		case METHOD:
			for (JavacNode child : node.down()) {
				if (annotationTypeMatches(type, child)) {
					if (delete) deleteAnnotationIfNeccessary(child, type);
					return true;
				}
			}
			// intentional fallthrough
		default:
			return false;
		}
	}
	
	private static boolean hasAnnotation(String type, JavacNode node, boolean delete) {
		if (node == null) return false;
		if (type == null) return false;
		switch (node.getKind()) {
		case ARGUMENT:
		case FIELD:
		case LOCAL:
		case TYPE:
		case METHOD:
			for (JavacNode child : node.down()) {
				if (annotationTypeMatches(type, child)) {
					if (delete) deleteAnnotationIfNeccessary(child, type);
					return true;
				}
			}
			// intentional fallthrough
		default:
			return false;
		}
	}
	
	public static JavacNode findInnerClass(JavacNode parent, String name) {
		for (JavacNode child : parent.down()) {
			if (child.getKind() != Kind.TYPE) continue;
			JCClassDecl td = (JCClassDecl) child.get();
			if (td.name.contentEquals(name)) return child;
		}
		return null;
	}
	
	public static JavacNode findAnnotation(Class type, JavacNode node) {
		return findAnnotation(type, node, false);
	}
	
	public static JavacNode findAnnotation(Class type, JavacNode node, boolean delete) {
		if (node == null) return null;
		if (type == null) return null;
		switch (node.getKind()) {
		case ARGUMENT:
		case FIELD:
		case LOCAL:
		case TYPE:
		case METHOD:
			for (JavacNode child : node.down()) {
				if (annotationTypeMatches(type, child)) {
					if (delete) deleteAnnotationIfNeccessary(child, type);
					return child;
				}
			}
			// intentional fallthrough
		default:
			return null;
		}
	}
	
	/**
	 * Checks if the Annotation AST Node provided is likely to be an instance of the provided annotation type.
	 * 
	 * @param type An actual annotation type, such as {@code lombok.Getter.class}.
	 * @param node A Lombok AST node representing an annotation in source code.
	 */
	public static boolean annotationTypeMatches(Class type, JavacNode node) {
		if (node.getKind() != Kind.ANNOTATION) return false;
		return typeMatches(type, node, ((JCAnnotation) node.get()).annotationType);
	}
	
	/**
	 * Checks if the Annotation AST Node provided is likely to be an instance of the provided annotation type.
	 * 
	 * @param type An actual annotation type, such as {@code lombok.Getter.class}.
	 * @param node A Lombok AST node representing an annotation in source code.
	 */
	public static boolean annotationTypeMatches(String type, JavacNode node) {
		if (node.getKind() != Kind.ANNOTATION) return false;
		return typeMatches(type, node, ((JCAnnotation) node.get()).annotationType);
	}
	
	/**
	 * Checks if the given TypeReference node is likely to be a reference to the provided class.
	 * 
	 * @param type An actual type. This method checks if {@code typeNode} is likely to be a reference to this type.
	 * @param node A Lombok AST node. Any node in the appropriate compilation unit will do (used to get access to import statements).
	 * @param typeNode A type reference to check.
	 */
	public static boolean typeMatches(Class type, JavacNode node, JCTree typeNode) {
		return typeMatches(type.getName(), node, typeNode);
	}
	
	/**
	 * Checks if the given TypeReference node is likely to be a reference to the provided class.
	 * 
	 * @param type An actual type. This method checks if {@code typeNode} is likely to be a reference to this type.
	 * @param node A Lombok AST node. Any node in the appropriate compilation unit will do (used to get access to import statements).
	 * @param typeNode A type reference to check.
	 */
	public static boolean typeMatches(String type, JavacNode node, JCTree typeNode) {
		String typeName = getTypeName(typeNode);
		return typeMatches(type, node, typeName);
	}

	private static boolean typeMatches(String type, JavacNode node, String typeName) {
		if (typeName == null || typeName.length() == 0) return false;
		int lastIndexA = typeName.lastIndexOf('.') + 1;
		int lastIndexB = Math.max(type.lastIndexOf('.'), type.lastIndexOf('$')) + 1;
		int len = typeName.length() - lastIndexA;
		if (len != type.length() - lastIndexB) return false;
		for (int i = 0; i < len; i++) if (typeName.charAt(i + lastIndexA) != type.charAt(i + lastIndexB)) return false;
		TypeResolver resolver = node.getImportListAsTypeResolver();
		return resolver.typeMatches(node, type, typeName);
	}

	private static String getTypeName(JCTree typeNode) {
		return typeNode == null ? null : typeNode.toString();
	}

	/**
	 * Returns if a field is marked deprecated, either by {@code @Deprecated} or in javadoc
	 * @param field the field to check
	 * @return {@code true} if a field is marked deprecated, either by {@code @Deprecated} or in javadoc, otherwise {@code false}
	 */
	public static boolean isFieldDeprecated(JavacNode field) {
		if (!(field.get() instanceof JCVariableDecl)) return false;
		JCVariableDecl fieldNode = (JCVariableDecl) field.get();
		if ((fieldNode.mods.flags & Flags.DEPRECATED) != 0) {
			return true;
		}
		for (JavacNode child : field.down()) {
			if (annotationTypeMatches(Deprecated.class, child)) {
				return true;
			}
		}
		return false;
	}
	
	public static CheckerFrameworkVersion getCheckerFrameworkVersion(JavacNode node) {
		CheckerFrameworkVersion cfv = node.getAst().readConfiguration(ConfigurationKeys.CHECKER_FRAMEWORK);
		return cfv == null ? CheckerFrameworkVersion.NONE : cfv;
	}
	
	/**
	 * Returns if a node is marked deprecated (as picked up on by the parser).
	 * @param node the node to check (type, method, or field decl).
	 */
	public static boolean nodeHasDeprecatedFlag(JCTree node) {
		if (node instanceof JCVariableDecl) return (((JCVariableDecl) node).mods.flags & Flags.DEPRECATED) != 0;
		if (node instanceof JCMethodDecl) return (((JCMethodDecl) node).mods.flags & Flags.DEPRECATED) != 0;
		if (node instanceof JCClassDecl) return (((JCClassDecl) node).mods.flags & Flags.DEPRECATED) != 0;
		return false;
	}
	
	/**
	 * Creates an instance of {@code AnnotationValues} for the provided AST Node.
	 * 
	 * @param type An annotation class type, such as {@code lombok.Getter.class}.
	 * @param node A Lombok AST node representing an annotation in source code.
	 */
	public static  AnnotationValues createAnnotation(Class type, final JavacNode node) {
		return createAnnotation(type, (JCAnnotation) node.get(), node);
	}
	
	/**
	 * Creates an instance of {@code AnnotationValues} for the provided AST Node
	 * and Annotation expression.
	 *
	 * @param type An annotation class type, such as {@code lombok.Getter.class}.
	 * @param anno the annotation expression
	 * @param node A Lombok AST node representing an annotation in source code.
	 */
	public static  AnnotationValues createAnnotation(Class type, JCAnnotation anno, final JavacNode node) {
		Map values = new HashMap();
		List arguments = anno.getArguments();
		
		for (JCExpression arg : arguments) {
			String mName;
			JCExpression rhs;
			java.util.List raws = new ArrayList();
			java.util.List guesses = new ArrayList();
			java.util.List expressions = new ArrayList();
			final java.util.List positions = new ArrayList();
			
			if (arg instanceof JCAssign) {
				JCAssign assign = (JCAssign) arg;
				mName = assign.lhs.toString();
				rhs = assign.rhs;
			} else {
				rhs = arg;
				mName = "value";
			}
			
			if (rhs instanceof JCNewArray) {
				List elems = ((JCNewArray) rhs).elems;
				for (JCExpression inner : elems) {
					raws.add(inner.toString());
					expressions.add(inner);
					if (inner instanceof JCAnnotation) {
						try {
							@SuppressWarnings("unchecked")
							Class innerClass = (Class) Class.forName(inner.type.toString());
							
							guesses.add(createAnnotation(innerClass, (JCAnnotation) inner, node));
						} catch (ClassNotFoundException ex) {
							guesses.add(calculateGuess(inner));
						}
					} else {
						guesses.add(calculateGuess(inner));
					}
					positions.add(inner.pos());
				}
			} else {
				raws.add(rhs.toString());
				expressions.add(rhs);
				if (rhs instanceof JCAnnotation) {
					try {
						@SuppressWarnings("unchecked")
						Class innerClass = (Class) Class.forName(rhs.type.toString());
						
						guesses.add(createAnnotation(innerClass, (JCAnnotation) rhs, node));
					} catch (ClassNotFoundException ex) {
						guesses.add(calculateGuess(rhs));
					}
				} else {
					guesses.add(calculateGuess(rhs));
				}
				positions.add(rhs.pos());
			}
			
			values.put(mName, new AnnotationValue(node, raws, expressions, guesses, true) {
				@Override public void setError(String message, int valueIdx) {
					if (valueIdx < 0) node.addError(message);
					else node.addError(message, positions.get(valueIdx));
				}
				
				@Override public void setWarning(String message, int valueIdx) {
					if (valueIdx < 0) node.addWarning(message);
					else node.addWarning(message, positions.get(valueIdx));
				}
			});
		}
		
		for (Method m : type.getDeclaredMethods()) {
			if (!Modifier.isPublic(m.getModifiers())) continue;
			String name = m.getName();
			if (!values.containsKey(name)) {
				values.put(name, new AnnotationValue(node, new ArrayList(), new ArrayList(), new ArrayList(), false) {
					@Override public void setError(String message, int valueIdx) {
						node.addError(message);
					}
					@Override public void setWarning(String message, int valueIdx) {
						node.addWarning(message);
					}
				});
			}
		}
		
		return new AnnotationValues(type, values, node);
	}
	
	/**
	 * Removes the annotation from javac's AST (it remains in lombok's AST),
	 * then removes any import statement that imports this exact annotation (not star imports).
	 * Only does this if the DeleteLombokAnnotations class is in the context.
	 */
	public static void deleteAnnotationIfNeccessary(JavacNode annotation, String annotationType) {
		deleteAnnotationIfNeccessary0(annotation, annotationType);
	}
	
	/**
	 * Removes the annotation from javac's AST (it remains in lombok's AST),
	 * then removes any import statement that imports this exact annotation (not star imports).
	 * Only does this if the DeleteLombokAnnotations class is in the context.
	 */
	public static void deleteAnnotationIfNeccessary(JavacNode annotation, Class annotationType) {
		deleteAnnotationIfNeccessary0(annotation, annotationType.getName());
	}
	
	/**
	 * Removes the annotation from javac's AST (it remains in lombok's AST),
	 * then removes any import statement that imports this exact annotation (not star imports).
	 * Only does this if the DeleteLombokAnnotations class is in the context.
	 */
	public static void deleteAnnotationIfNeccessary(JavacNode annotation, Class annotationType1, Class annotationType2) {
		deleteAnnotationIfNeccessary0(annotation, annotationType1.getName(), annotationType2.getName());
	}
	
	/**
	 * Removes the annotation from javac's AST (it remains in lombok's AST),
	 * then removes any import statement that imports this exact annotation (not star imports).
	 * Only does this if the DeleteLombokAnnotations class is in the context.
	 */
	public static void deleteAnnotationIfNeccessary(JavacNode annotation, Class annotationType1, String annotationType2) {
		deleteAnnotationIfNeccessary0(annotation, annotationType1.getName(), annotationType2);
	}
	
	private static void deleteAnnotationIfNeccessary0(JavacNode annotation, String... annotationTypes) {
		if (inNetbeansEditor(annotation)) return;
		if (!annotation.shouldDeleteLombokAnnotations()) return;
		JavacNode parentNode = annotation.directUp();
		switch (parentNode.getKind()) {
		case FIELD:
		case ARGUMENT:
		case LOCAL:
			JCVariableDecl variable = (JCVariableDecl) parentNode.get();
			variable.mods.annotations = filterList(variable.mods.annotations, annotation.get());
			
			if ((variable.mods.flags & GENERATED_MEMBER) != 0) {
				JavacNode typeNode = upToTypeNode(annotation);
				if (isRecord(typeNode)) {
					RecordComponentReflect.deleteAnnotation((JCClassDecl) typeNode.get(), variable, annotation.get());
				}
			}
			break;
		case METHOD:
			JCMethodDecl method = (JCMethodDecl) parentNode.get();
			method.mods.annotations = filterList(method.mods.annotations, annotation.get());
			break;
		case TYPE:
			try {
				JCClassDecl type = (JCClassDecl) parentNode.get();
				type.mods.annotations = filterList(type.mods.annotations, annotation.get());
			} catch (ClassCastException e) {
				//something rather odd has been annotated. Better to just break only delombok instead of everything.
			}
			break;
		default:
			//This really shouldn't happen, but if it does, better just break delombok instead of breaking everything.
			return;
		}
		
		parentNode.getAst().setChanged();
		for (String annotationType : annotationTypes) {
			deleteImportFromCompilationUnit(annotation, annotationType);
		}
	}
	
	public static void deleteImportFromCompilationUnit(JavacNode node, String name) {
		if (inNetbeansEditor(node)) return;
		if (!node.shouldDeleteLombokAnnotations()) return;
		
		JCCompilationUnit unit = (JCCompilationUnit) node.top().get();
		
		for (JCTree def : unit.defs) {
			if (!(def instanceof JCImport)) continue;
			JCImport imp0rt = (JCImport) def;
			if (imp0rt.staticImport) continue;
			if (!imp0rt.qualid.toString().equals(name)) continue;
			JavacAugments.JCImport_deletable.set(imp0rt, true);
		}
	}
	
	private static List filterList(List annotations, JCTree jcTree) {
		ListBuffer newAnnotations = new ListBuffer();
		for (JCAnnotation ann : annotations) {
			if (jcTree != ann) newAnnotations.append(ann);
		}
		return newAnnotations.toList();
	}
	
	private static List filterListByPos(List annotations, JCTree jcTree) {
		ListBuffer newAnnotations = new ListBuffer();
		for (JCAnnotation ann : annotations) {
			if (jcTree.pos != ann.pos) newAnnotations.append(ann);
		}
		return newAnnotations.toList();
	}
	
	
	static class RecordComponentReflect {
		
		private static final Field astField;
		private static final Method findRecordComponentToRemove;
		
		static {
			Field a = null;
			Method m = null;
			try {
				Class forName = Class.forName("com.sun.tools.javac.code.Symbol$RecordComponent");
				a = Permit.permissiveGetField(forName, "ast");
				m = Permit.permissiveGetMethod(ClassSymbol.class, "findRecordComponentToRemove", JCVariableDecl.class);
			} catch (Throwable e) {
				// Ignore
			}
			astField = a;
			findRecordComponentToRemove = m;
		}
		
		static void deleteAnnotation(JCClassDecl record, JCVariableDecl component, JCTree annotation) {
			if (astField == null || findRecordComponentToRemove == null) return;
			
			try {
				Object toRemove = Permit.invokeSneaky(findRecordComponentToRemove, record.sym, component);
				JCVariableDecl variable = (JCVariableDecl) Permit.get(astField, toRemove);
				variable.mods.annotations =  filterListByPos(variable.mods.annotations, annotation);
			} catch (Throwable e) {
				// Ignore
			}
		}
	}
	
	/** Serves as return value for the methods that check for the existence of fields and methods. */
	public enum MemberExistsResult {
		NOT_EXISTS, EXISTS_BY_LOMBOK, EXISTS_BY_USER;
	}
	
	/**
	 * Translates the given field into all possible getter names.
	 * Convenient wrapper around {@link HandlerUtil#toAllGetterNames(lombok.core.AnnotationValues, CharSequence, boolean)}.
	 */
	public static java.util.List toAllGetterNames(JavacNode field) {
		return HandlerUtil.toAllGetterNames(field.getAst(), getAccessorsForField(field), field.getName(), isBoolean(field));
	}
	
	/**
	 * Translates the given field into all possible getter names.
	 * Convenient wrapper around {@link HandlerUtil#toAllGetterNames(lombok.core.AnnotationValues, CharSequence, boolean)}.
	 */
	public static java.util.List toAllGetterNames(JavacNode field, AnnotationValues accessors) {
		return HandlerUtil.toAllGetterNames(field.getAst(), accessors, field.getName(), isBoolean(field));
	}
	
	/**
	 * @return the likely getter name for the stated field. (e.g. private boolean foo; to isFoo).
	 * 
	 * Convenient wrapper around {@link HandlerUtil#toGetterName(lombok.core.AnnotationValues, CharSequence, boolean)}.
	 */
	public static String toGetterName(JavacNode field) {
		return HandlerUtil.toGetterName(field.getAst(), getAccessorsForField(field), field.getName(), isBoolean(field));
	}
	
	/**
	 * @return the likely getter name for the stated field. (e.g. private boolean foo; to isFoo).
	 * 
	 * Convenient wrapper around {@link HandlerUtil#toGetterName(lombok.core.AnnotationValues, CharSequence, boolean)}.
	 */
	public static String toGetterName(JavacNode field, AnnotationValues accessors) {
		return HandlerUtil.toGetterName(field.getAst(), accessors, field.getName(), isBoolean(field));
	}
	
	/**
	 * Translates the given field into all possible setter names.
	 * Convenient wrapper around {@link HandlerUtil#toAllSetterNames(lombok.core.AnnotationValues, CharSequence, boolean)}.
	 */
	public static java.util.List toAllSetterNames(JavacNode field) {
		return HandlerUtil.toAllSetterNames(field.getAst(), getAccessorsForField(field), field.getName(), isBoolean(field));
	}
	
	/**
	 * Translates the given field into all possible setter names.
	 * Convenient wrapper around {@link HandlerUtil#toAllSetterNames(lombok.core.AnnotationValues, CharSequence, boolean)}.
	 */
	public static java.util.List toAllSetterNames(JavacNode field, AnnotationValues accessors) {
		return HandlerUtil.toAllSetterNames(field.getAst(), accessors, field.getName(), isBoolean(field));
	}
	
	/**
	 * @return the likely setter name for the stated field. (e.g. private boolean foo; to setFoo).
	 * 
	 * Convenient wrapper around {@link HandlerUtil#toSetterName(lombok.core.AnnotationValues, CharSequence, boolean)}.
	 */
	public static String toSetterName(JavacNode field) {
		return HandlerUtil.toSetterName(field.getAst(), getAccessorsForField(field), field.getName(), isBoolean(field));
	}
	
	/**
	 * @return the likely setter name for the stated field. (e.g. private boolean foo; to setFoo).
	 * 
	 * Convenient wrapper around {@link HandlerUtil#toSetterName(lombok.core.AnnotationValues, CharSequence, boolean)}.
	 */
	public static String toSetterName(JavacNode field, AnnotationValues accessors) {
		return HandlerUtil.toSetterName(field.getAst(), accessors, field.getName(), isBoolean(field));
	}
	
	/**
	 * Translates the given field into all possible with names.
	 * Convenient wrapper around {@link HandlerUtil#toAllWithNames(lombok.core.AnnotationValues, CharSequence, boolean)}.
	 */
	public static java.util.List toAllWithNames(JavacNode field) {
		return HandlerUtil.toAllWithNames(field.getAst(), getAccessorsForField(field), field.getName(), isBoolean(field));
	}
	
	/**
	 * Translates the given field into all possible with names.
	 * Convenient wrapper around {@link HandlerUtil#toAllWithNames(lombok.core.AnnotationValues, CharSequence, boolean)}.
	 */
	public static java.util.List toAllWithNames(JavacNode field, AnnotationValues accessors) {
		return HandlerUtil.toAllWithNames(field.getAst(), accessors, field.getName(), isBoolean(field));
	}
	
	/**
	 * Translates the given field into all possible withBy names.
	 * Convenient wrapper around {@link HandlerUtil#toAllWithByNames(lombok.core.AnnotationValues, CharSequence, boolean)}.
	 */
	public static java.util.List toAllWithByNames(JavacNode field) {
		return HandlerUtil.toAllWithByNames(field.getAst(), getAccessorsForField(field), field.getName(), isBoolean(field));
	}
	
	/**
	 * Translates the given field into all possible withBy names.
	 * Convenient wrapper around {@link HandlerUtil#toAllWithByNames(lombok.core.AnnotationValues, CharSequence, boolean)}.
	 */
	public static java.util.List toAllWithByNames(JavacNode field, AnnotationValues accessors) {
		return HandlerUtil.toAllWithByNames(field.getAst(), accessors, field.getName(), isBoolean(field));
	}
	
	/**
	 * @return the likely with name for the stated field. (e.g. private boolean foo; to withFoo).
	 * 
	 * Convenient wrapper around {@link HandlerUtil#toWithName(lombok.core.AnnotationValues, CharSequence, boolean)}.
	 */
	public static String toWithName(JavacNode field) {
		return HandlerUtil.toWithName(field.getAst(), getAccessorsForField(field), field.getName(), isBoolean(field));
	}
	
	/**
	 * @return the likely with name for the stated field. (e.g. private boolean foo; to withFoo).
	 * 
	 * Convenient wrapper around {@link HandlerUtil#toWithName(lombok.core.AnnotationValues, CharSequence, boolean)}.
	 */
	public static String toWithName(JavacNode field, AnnotationValues accessors) {
		return HandlerUtil.toWithName(field.getAst(), accessors, field.getName(), isBoolean(field));
	}
	
	/**
	 * @return the likely withBy name for the stated field. (e.g. private boolean foo; to withFooBy).
	 * 
	 * Convenient wrapper around {@link HandlerUtil#toWithByName(lombok.core.AnnotationValues, CharSequence, boolean)}.
	 */
	public static String toWithByName(JavacNode field) {
		return HandlerUtil.toWithByName(field.getAst(), getAccessorsForField(field), field.getName(), isBoolean(field));
	}
	
	/**
	 * @return the likely withBy name for the stated field. (e.g. private boolean foo; to withFooBy).
	 * 
	 * Convenient wrapper around {@link HandlerUtil#toWithByName(lombok.core.AnnotationValues, CharSequence, boolean)}.
	 */
	public static String toWithByName(JavacNode field, AnnotationValues accessors) {
		return HandlerUtil.toWithByName(field.getAst(), accessors, field.getName(), isBoolean(field));
	}
	
	/**
	 * When generating a setter, the setter either returns void (beanspec) or Self (fluent).
	 * This method scans for the {@code Accessors} annotation to figure that out.
	 */
	public static boolean shouldReturnThis(JavacNode field, AnnotationValues accessors) {
		if ((((JCVariableDecl) field.get()).mods.flags & Flags.STATIC) != 0) return false;
		
		return HandlerUtil.shouldReturnThis0(accessors, field.getAst());
	}
	
	/**
	 * When generating a setter/getter/wither, should it be made final?
	 */
	public static boolean shouldMakeFinal(JavacNode field, AnnotationValues accessors) {
		if ((((JCVariableDecl) field.get()).mods.flags & Flags.STATIC) != 0) return false;
		
		return HandlerUtil.shouldMakeFinal0(accessors, field.getAst());
	}
	
	public static JCExpression cloneSelfType(JavacNode childOfType) {
		JavacNode typeNode = childOfType;
		JavacTreeMaker maker = childOfType.getTreeMaker();
		while (typeNode != null && typeNode.getKind() != Kind.TYPE) typeNode = typeNode.up();
		return JavacHandlerUtil.namePlusTypeParamsToTypeReference(maker, typeNode, ((JCClassDecl) typeNode.get()).typarams);
	}
	
	public static boolean isBoolean(JavacNode field) {
		JCExpression varType = ((JCVariableDecl) field.get()).vartype;
		return isBoolean(varType);
	}
	
	public static boolean isBoolean(JCExpression varType) {
		return varType != null && varType.toString().equals("boolean");
	}
	
	public static Name removePrefixFromField(JavacNode field) {
		java.util.List prefixes = null;
		for (JavacNode node : field.down()) {
			if (annotationTypeMatches(Accessors.class, node)) {
				AnnotationValues ann = createAnnotation(Accessors.class, node);
				if (ann.isExplicit("prefix")) prefixes = Arrays.asList(ann.getInstance().prefix());
				break;
			}
		}
		
		if (prefixes == null) {
			JavacNode current = field.up();
			outer:
			while (current != null) {
				for (JavacNode node : current.down()) {
					if (annotationTypeMatches(Accessors.class, node)) {
						AnnotationValues ann = createAnnotation(Accessors.class, node);
						if (ann.isExplicit("prefix")) prefixes = Arrays.asList(ann.getInstance().prefix());
						break outer;
					}
				}
				current = current.up();
			}
		}
		
		if (prefixes == null) prefixes = field.getAst().readConfiguration(ConfigurationKeys.ACCESSORS_PREFIX);
		
		if (!prefixes.isEmpty()) {
			CharSequence newName = removePrefix(field.getName(), prefixes);
			if (newName != null) return field.toName(newName.toString());
		}
		
		return ((JCVariableDecl) field.get()).name;
	}
	
	public static AnnotationValues getAccessorsForField(JavacNode field) {
		AnnotationValues values = null;
		
		for (JavacNode node : field.down()) {
			if (annotationTypeMatches(Accessors.class, node)) {
				values = createAnnotation(Accessors.class, node);
				break;
			}
		}
		
		JavacNode current = field.up();
		while (current != null) {
			for (JavacNode node : current.down()) {
				if (annotationTypeMatches(Accessors.class, node)) {
					AnnotationValues onType = createAnnotation(Accessors.class, node);
					values = values == null ? onType : values.integrate(onType);
					break;
				}
			}
			current = current.up();
		}
		
		return values == null ? AnnotationValues.of(Accessors.class, field) : values;
	}
	
	/**
	 * Checks if there is a field with the provided name.
	 * 
	 * @param fieldName the field name to check for.
	 * @param node Any node that represents the Type (JCClassDecl) to look in, or any child node thereof.
	 */
	public static MemberExistsResult fieldExists(String fieldName, JavacNode node) {
		node = upToTypeNode(node);
		
		if (node != null && node.get() instanceof JCClassDecl) {
			for (JCTree def : ((JCClassDecl)node.get()).defs) {
				if (def instanceof JCVariableDecl) {
					if (((JCVariableDecl)def).name.contentEquals(fieldName)) {
						return getGeneratedBy(def) == null ? MemberExistsResult.EXISTS_BY_USER : MemberExistsResult.EXISTS_BY_LOMBOK;
					}
				}
			}
		}
		
		return MemberExistsResult.NOT_EXISTS;
	}
	
	public static MemberExistsResult methodExists(String methodName, JavacNode node, int params) {
		return methodExists(methodName, node, true, params);
	}
	
	/**
	 * Checks if there is a method with the provided name. In case of multiple methods (overloading), only
	 * the first method decides if EXISTS_BY_USER or EXISTS_BY_LOMBOK is returned.
	 * 
	 * @param methodName the method name to check for.
	 * @param node Any node that represents the Type (JCClassDecl) to look in, or any child node thereof.
	 * @param caseSensitive If the search should be case sensitive.
	 * @param params The number of parameters the method should have; varargs count as 0-*. Set to -1 to find any method with the appropriate name regardless of parameter count.
	 */
	public static MemberExistsResult methodExists(String methodName, JavacNode node, boolean caseSensitive, int params) {
		node = upToTypeNode(node);
		
		if (node != null && node.get() instanceof JCClassDecl) {
			top: for (JCTree def : ((JCClassDecl)node.get()).defs) {
				if (def instanceof JCMethodDecl) {
					JCMethodDecl md = (JCMethodDecl) def;
					String name = md.name.toString();
					boolean matches = caseSensitive ? name.equals(methodName) : name.equalsIgnoreCase(methodName);
					if (matches) {
						if (params > -1) {
							List ps = md.params;
							int minArgs = 0;
							int maxArgs = 0;
							if (ps != null && ps.length() > 0) {
								minArgs = ps.length();
								if ((ps.last().mods.flags & Flags.VARARGS) != 0) {
									maxArgs = Integer.MAX_VALUE;
									minArgs--;
								} else {
									maxArgs = minArgs;
								}
							}
							
							if (params < minArgs || params > maxArgs) continue;
						}
						
						if (isTolerate(node, md)) continue top;
						
						return getGeneratedBy(def) == null ? MemberExistsResult.EXISTS_BY_USER : MemberExistsResult.EXISTS_BY_LOMBOK;
					}
				}
			}
		}
		
		return MemberExistsResult.NOT_EXISTS;
	}
	
	public static boolean isTolerate(JavacNode node, JCTree.JCMethodDecl md) {
		List annotations = md.getModifiers().getAnnotations();
		if (annotations != null) for (JCTree.JCAnnotation anno : annotations) {
			if (typeMatches(Tolerate.class, node, anno.getAnnotationType())) return true;
		}
		return false;
	}
	
	/**
	 * Checks if there is a (non-default) constructor. In case of multiple constructors (overloading), only
	 * the first constructor decides if EXISTS_BY_USER or EXISTS_BY_LOMBOK is returned.
	 * 
	 * @param node Any node that represents the Type (JCClassDecl) to look in, or any child node thereof.
	 */
	public static MemberExistsResult constructorExists(JavacNode node) {
		node = upToTypeNode(node);
		
		if (node != null && node.get() instanceof JCClassDecl) {
			for (JCTree def : ((JCClassDecl) node.get()).defs) {
				if (def instanceof JCMethodDecl) {
					JCMethodDecl md = (JCMethodDecl) def;
					if (md.name.contentEquals("")) {
						if ((md.mods.flags & Flags.GENERATEDCONSTR) != 0) continue;
						if (isTolerate(node, md)) continue;
						return getGeneratedBy(def) == null ? MemberExistsResult.EXISTS_BY_USER : MemberExistsResult.EXISTS_BY_LOMBOK;
					}
				}
			}
		}
		
		return MemberExistsResult.NOT_EXISTS;
	}
	
	public static boolean isConstructorCall(final JCStatement statement) {
		if (!(statement instanceof JCExpressionStatement)) return false;
		JCExpression expr = ((JCExpressionStatement) statement).expr;
		if (!(expr instanceof JCMethodInvocation)) return false;
		JCExpression invocation = ((JCMethodInvocation) expr).meth;
		String name;
		if (invocation instanceof JCFieldAccess) {
			name = ((JCFieldAccess) invocation).name.toString();
		} else if (invocation instanceof JCIdent) {
			name = ((JCIdent) invocation).name.toString();
		} else {
			name = "";
		}
		
		return "super".equals(name) || "this".equals(name);
	}
	
	/**
	 * Turns an {@code AccessLevel} instance into the flag bit used by javac.
	 */
	public static int toJavacModifier(AccessLevel accessLevel) {
		switch (accessLevel) {
		case MODULE:
		case PACKAGE:
			return 0;
		default:
		case PUBLIC:
			return Flags.PUBLIC;
		case NONE:
		case PRIVATE:
			return Flags.PRIVATE;
		case PROTECTED:
			return Flags.PROTECTED;
		}
	}
	
	private static class GetterMethod {
		private final Name name;
		private final JCExpression type;
		
		GetterMethod(Name name, JCExpression type) {
			this.name = name;
			this.type = type;
		}
	}
	
	private static GetterMethod findGetter(JavacNode field) {
		JCVariableDecl decl = (JCVariableDecl)field.get();
		JavacNode typeNode = field.up();
		for (String potentialGetterName : toAllGetterNames(field)) {
			for (JavacNode potentialGetter : typeNode.down()) {
				if (potentialGetter.getKind() != Kind.METHOD) continue;
				JCMethodDecl method = (JCMethodDecl) potentialGetter.get();
				if (!method.name.toString().equalsIgnoreCase(potentialGetterName)) continue;
				/** static getX() methods don't count. */
				if ((method.mods.flags & Flags.STATIC) != 0) continue;
				/** Nor do getters with a non-empty parameter list. */
				if (method.params != null && method.params.size() > 0) continue;
				return new GetterMethod(method.name, method.restype);
			}
		}
		
		// Check if the field has a @Getter annotation.
		
		boolean hasGetterAnnotation = false;
		
		for (JavacNode child : field.down()) {
			if (child.getKind() == Kind.ANNOTATION && annotationTypeMatches(Getter.class, child)) {
				AnnotationValues ann = createAnnotation(Getter.class, child);
				if (ann.getInstance().value() == AccessLevel.NONE) return null;   //Definitely WONT have a getter.
				hasGetterAnnotation = true;
			}
		}
		
		// Check if the class has a @Getter annotation.
		
		if (!hasGetterAnnotation && HandleGetter.fieldQualifiesForGetterGeneration(field)) {
			//Check if the class has @Getter or @Data annotation.
			
			JavacNode containingType = field.up();
			if (containingType != null) for (JavacNode child : containingType.down()) {
				if (child.getKind() == Kind.ANNOTATION && annotationTypeMatches(Data.class, child)) hasGetterAnnotation = true;
				if (child.getKind() == Kind.ANNOTATION && annotationTypeMatches(Getter.class, child)) {
					AnnotationValues ann = createAnnotation(Getter.class, child);
					if (ann.getInstance().value() == AccessLevel.NONE) return null;   //Definitely WONT have a getter.
					hasGetterAnnotation = true;
				}
			}
		}
		
		if (hasGetterAnnotation) {
			String getterName = toGetterName(field);
			if (getterName == null) return null;
			return new GetterMethod(field.toName(getterName), decl.vartype);
		}
		
		return null;
	}
	
	static boolean lookForGetter(JavacNode field, FieldAccess fieldAccess) {
		if (fieldAccess == FieldAccess.GETTER) return true;
		if (fieldAccess == FieldAccess.ALWAYS_FIELD) return false;
		
		// If @Getter(lazy = true) is used, then using it is mandatory.
		for (JavacNode child : field.down()) {
			if (child.getKind() != Kind.ANNOTATION) continue;
			if (annotationTypeMatches(Getter.class, child)) {
				AnnotationValues ann = createAnnotation(Getter.class, child);
				if (ann.getInstance().lazy()) return true;
			}
		}
		return false;
	}
	
	/**
	 * Returns the type of the field, unless a getter exists for this field, in which case the return type of the getter is returned.
	 * 
	 * @see #createFieldAccessor(TreeMaker, JavacNode, FieldAccess)
	 */
	static JCExpression getFieldType(JavacNode field, FieldAccess fieldAccess) {
		if (field.getKind() == Kind.METHOD) return ((JCMethodDecl) field.get()).restype;
		
		boolean lookForGetter = lookForGetter(field, fieldAccess);
		
		GetterMethod getter = lookForGetter ? findGetter(field) : null;
		
		if (getter == null) {
			return ((JCVariableDecl) field.get()).vartype;
		}
		
		return getter.type;
	}
	
	/**
	 * Creates an expression that reads the field. Will either be {@code this.field} or {@code this.getField()} depending on whether or not there's a getter.
	 */
	static JCExpression createFieldAccessor(JavacTreeMaker maker, JavacNode field, FieldAccess fieldAccess) {
		return createFieldAccessor(maker, field, fieldAccess, null);
	}
	
	static JCExpression createFieldAccessor(JavacTreeMaker maker, JavacNode field, FieldAccess fieldAccess, JCExpression receiver) {
		boolean lookForGetter = lookForGetter(field, fieldAccess);
		
		GetterMethod getter = lookForGetter ? findGetter(field) : null;
		JCVariableDecl fieldDecl = (JCVariableDecl) field.get();
		
		if (getter == null) {
			if (receiver == null) {
				if ((fieldDecl.mods.flags & Flags.STATIC) == 0) {
					receiver = maker.Ident(field.toName("this"));
				} else {
					JavacNode containerNode = field.up();
					if (containerNode != null && containerNode.get() instanceof JCClassDecl) {
						JCClassDecl container = (JCClassDecl) field.up().get();
						receiver = maker.Ident(container.name);
					}
				}
			}
			
			return receiver == null ? maker.Ident(fieldDecl.name) : maker.Select(receiver, fieldDecl.name);
		}
		
		if (receiver == null) receiver = maker.Ident(field.toName("this"));
		JCMethodInvocation call = maker.Apply(List.nil(),
			maker.Select(receiver, getter.name), List.nil());
		return call;
	}
	
	static JCExpression createMethodAccessor(JavacTreeMaker maker, JavacNode method) {
		return createMethodAccessor(maker, method, null);
	}
	
	static JCExpression createMethodAccessor(JavacTreeMaker maker, JavacNode method, JCExpression receiver) {
		JCMethodDecl methodDecl = (JCMethodDecl) method.get();
		
		if (receiver == null && (methodDecl.mods.flags & Flags.STATIC) == 0) {
			receiver = maker.Ident(method.toName("this"));
		} else if (receiver == null) {
			JavacNode containerNode = method.up();
			if (containerNode != null && containerNode.get() instanceof JCClassDecl) {
				JCClassDecl container = (JCClassDecl) method.up().get();
				receiver = maker.Ident(container.name);
			}
		}
		
		JCMethodInvocation call = maker.Apply(List.nil(),
			receiver == null ? maker.Ident(methodDecl.name) : maker.Select(receiver, methodDecl.name), List.nil());
		return call;
	}
	
	/**
	 * Adds the given new field declaration to the provided type AST Node.
	 * The field carries the @{@link SuppressWarnings}("all") annotation.
	 * Also takes care of updating the JavacAST.
	 */
	public static JavacNode injectFieldAndMarkGenerated(JavacNode typeNode, JCVariableDecl field) {
		return injectField(typeNode, field, true);
	}
	
	/**
	 * Adds the given new field declaration to the provided type AST Node.
	 * 
	 * Also takes care of updating the JavacAST.
	 */
	public static JavacNode injectField(JavacNode typeNode, JCVariableDecl field) {
		return injectField(typeNode, field, false);
	}
	
	public static JavacNode injectField(JavacNode typeNode, JCVariableDecl field, boolean addGenerated) {
		return injectField(typeNode, field, addGenerated, false);
	}
	
	public static JavacNode injectField(JavacNode typeNode, JCVariableDecl field, boolean addGenerated, boolean specialEnumHandling) {
		JCClassDecl type = (JCClassDecl) typeNode.get();
		
		if (addGenerated) {
			addSuppressWarningsAll(field.mods, typeNode, typeNode.getNodeFor(getGeneratedBy(field)), typeNode.getContext());
			addGenerated(field.mods, typeNode, typeNode.getNodeFor(getGeneratedBy(field)), typeNode.getContext());
		}
		
		List insertAfter = null;
		List insertBefore = type.defs;
		while (true) {
			boolean skip = false;
			if (insertBefore.head instanceof JCVariableDecl) {
				JCVariableDecl f = (JCVariableDecl) insertBefore.head;
				if ((!specialEnumHandling && isEnumConstant(f)) || isGenerated(f)) skip = true;
			} else if (insertBefore.head instanceof JCMethodDecl) {
				if ((((JCMethodDecl) insertBefore.head).mods.flags & GENERATEDCONSTR) != 0) skip = true;
			}
			if (skip) {
				insertAfter = insertBefore;
				insertBefore = insertBefore.tail;
				continue;
			}
			break;
		}
		List fieldEntry = List.of(field);
		fieldEntry.tail = insertBefore;
		if (insertAfter == null) {
			type.defs = fieldEntry;
		} else {
			insertAfter.tail = fieldEntry;
		}
		
		EnterReflect.memberEnter(field, typeNode);
		
		return typeNode.add(field, Kind.FIELD);
	}
	
	public static boolean isEnumConstant(final JCVariableDecl field) {
		return (field.mods.flags & Flags.ENUM) != 0;
	}
	
	static class JCAnnotatedTypeReflect {
		private static Class TYPE;
		private static Constructor CONSTRUCTOR;
		private static Field ANNOTATIONS, UNDERLYING_TYPE;
		
		private static void initByLoader(ClassLoader classLoader) {
			if (TYPE != null) return;
			Class c;
			try {
				c = classLoader.loadClass("com.sun.tools.javac.tree.JCTree$JCAnnotatedType");
			} catch (Exception e) {
				return;
			}
			init(c);
		}
		
		private static void init(Class in) {
			if (TYPE != null) return;
			if (!in.getName().equals("com.sun.tools.javac.tree.JCTree$JCAnnotatedType")) return;
			try {
				CONSTRUCTOR = Permit.getConstructor(in, List.class, JCExpression.class);
				ANNOTATIONS = Permit.getField(in, "annotations");
				UNDERLYING_TYPE = Permit.getField(in, "underlyingType");
				TYPE = in;
			} catch (Exception ignore) {}
		}
		
		static boolean is(JCTree obj) {
			if (obj == null) return false;
			init(obj.getClass());
			return obj.getClass() == TYPE;
		}
		
		@SuppressWarnings("unchecked")
		static List getAnnotations(JCTree obj) {
			init(obj.getClass());
			try {
				return (List) ANNOTATIONS.get(obj);
			} catch (Exception e) {
				return List.nil();
			}
		}
		
		static void setAnnotations(JCTree obj, List anns) {
			init(obj.getClass());
			try {
				ANNOTATIONS.set(obj, anns);
			} catch (Exception e) {}
		}
		
		static JCExpression getUnderlyingType(JCTree obj) {
			init(obj.getClass());
			try {
				return (JCExpression) UNDERLYING_TYPE.get(obj);
			} catch (Exception e) {
				return null;
			}
		}
		
		static JCExpression create(List annotations, JCExpression underlyingType) {
			initByLoader(underlyingType.getClass().getClassLoader());
			try {
				return (JCExpression) CONSTRUCTOR.newInstance(annotations, underlyingType);
			} catch (Exception e) {
				return underlyingType;
			}
		}
	}
	
	static class JCAnnotationReflect {
		private static final Field ATTRIBUTE;

		static {
			ATTRIBUTE = Permit.permissiveGetField(JCAnnotation.class, "attribute");
		}

		static Attribute.Compound getAttribute(JCAnnotation jcAnnotation) {
			if (ATTRIBUTE != null) {
				try {
					return (Attribute.Compound) ATTRIBUTE.get(jcAnnotation);
				} catch (Exception e) {
					// Ignore
				}
			}
			return null;
		}
		
		static void setAttribute(JCAnnotation jcAnnotation, Attribute.Compound attribute) {
			if (ATTRIBUTE != null) {
				try {
					Permit.set(ATTRIBUTE, jcAnnotation, attribute);
				} catch (Exception e) {
					// Ignore
				}
			}
		}
	}

	// jdk9 support, types have changed, names stay the same
	static class ClassSymbolMembersField {
		private static final Field membersField;
		private static final Method removeMethod;
		private static final Method enterMethod;
		
		static {
			Field f = null;
			Method r = null;
			Method e = null;
			try {
				f = Permit.getField(ClassSymbol.class, "members_field");
				r = Permit.getMethod(f.getType(), "remove", Symbol.class);
				e = Permit.getMethod(f.getType(), "enter", Symbol.class);
			} catch (Exception ex) {}
			membersField = f;
			removeMethod = r;
			enterMethod = e;
		}
		
		static void remove(ClassSymbol from, Symbol toRemove) {
			if (from == null) return;
			try {
				Scope scope = (Scope) membersField.get(from);
				if (scope == null) return;
				Permit.invoke(removeMethod, scope, toRemove);
			} catch (Exception e) {}
		}
		
		static void enter(ClassSymbol from, Symbol toEnter) {
			if (from == null) return;
			try {
				Scope scope = (Scope) membersField.get(from);
				if (scope == null) return;
				Permit.invoke(enterMethod, scope, toEnter);
			} catch (Exception e) {}
		}
	}
	
	/**
	 * Adds the given new method declaration to the provided type AST Node.
	 * Can also inject constructors.
	 * 
	 * Also takes care of updating the JavacAST.
	 */
	public static void injectMethod(JavacNode typeNode, JCMethodDecl method) {
		JCClassDecl type = (JCClassDecl) typeNode.get();
		
		if (method.getName().contentEquals("")) {
			//Scan for default constructor, and remove it.
			int idx = 0;
			for (JCTree def : type.defs) {
				if (def instanceof JCMethodDecl) {
					if ((((JCMethodDecl) def).mods.flags & Flags.GENERATEDCONSTR) != 0) {
						JavacNode tossMe = typeNode.getNodeFor(def);
						if (tossMe != null) tossMe.up().removeChild(tossMe);
						type.defs = addAllButOne(type.defs, idx);
						ClassSymbolMembersField.remove(type.sym, ((JCMethodDecl) def).sym);
						break;
					}
				}
				idx++;
			}
		}
		
		addSuppressWarningsAll(method.mods, typeNode, typeNode.getNodeFor(getGeneratedBy(method)), typeNode.getContext());
		addGenerated(method.mods, typeNode, typeNode.getNodeFor(getGeneratedBy(method)), typeNode.getContext());
		type.defs = type.defs.append(method);
		
		EnterReflect.memberEnter(method, typeNode);
		
		typeNode.add(method, Kind.METHOD);
	}

	/**
	 * Adds an inner type (class, interface, enum) to the given type. Cannot inject top-level types.
	 * 
	 * @param typeNode parent type to inject new type into
	 * @param type New type (class, interface, etc) to inject.
	 * @return 
	 */
	public static JavacNode injectType(JavacNode typeNode, final JCClassDecl type) {
		JCClassDecl typeDecl = (JCClassDecl) typeNode.get();
		addSuppressWarningsAll(type.mods, typeNode, typeNode.getNodeFor(getGeneratedBy(type)), typeNode.getContext());
		addGenerated(type.mods, typeNode, typeNode.getNodeFor(getGeneratedBy(type)), typeNode.getContext());
		typeDecl.defs = typeDecl.defs.append(type);
		
		EnterReflect.classEnter(type, typeNode);
		
		return typeNode.add(type, Kind.TYPE);
	}
	
	static class EnterReflect {
		private static final Method classEnter;
		private static final Method memberEnter;
		private static final Method blockAnnotations;
		private static final Method unblockAnnotations;
		
		static {
			classEnter = Permit.permissiveGetMethod(Enter.class, "classEnter", JCTree.class, Env.class);
			memberEnter = Permit.permissiveGetMethod(MemberEnter.class, "memberEnter", JCTree.class, Env.class);
			
			Method block = Permit.permissiveGetMethod(Annotate.class, "blockAnnotations");
			if (block == null) block = Permit.permissiveGetMethod(Annotate.class, "enterStart");
			blockAnnotations = block;
			
			Method unblock = Permit.permissiveGetMethod(Annotate.class, "unblockAnnotations");
			if (unblock == null) unblock = Permit.permissiveGetMethod(Annotate.class, "enterDone");
			unblockAnnotations = unblock;
		}
		
		static Type classEnter(JCTree tree, JavacNode parent) {
			Enter enter = Enter.instance(parent.getContext());
			Env classEnv = enter.getEnv((TypeSymbol) parent.getElement());
			if (classEnv == null) return null;
			Type type = (Type) Permit.invokeSneaky(classEnter, enter, tree, classEnv);
			if (type == null) return null;
			type.complete();
			return type;
		}
		
		static void memberEnter(JCTree tree, JavacNode parent) {
			Context context = parent.getContext();
			MemberEnter me = MemberEnter.instance(context);
			Annotate annotate = Annotate.instance(context);
			Enter enter = Enter.instance(context);
			
			Env classEnv = enter.getEnv((TypeSymbol) parent.getElement());
			if (classEnv == null) return;
			
			Permit.invokeSneaky(blockAnnotations, annotate);
			Permit.invokeSneaky(memberEnter, me, tree, classEnv);
			Permit.invokeSneaky(unblockAnnotations, annotate);
		}
	}
	
	public static long addFinalIfNeeded(long flags, Context context) {
		boolean addFinal = LombokOptionsFactory.getDelombokOptions(context).getFormatPreferences().generateFinalParams();
		
		if (addFinal) flags |= Flags.FINAL;
		return flags;
	}
	
	public static JCExpression genTypeRef(JavacNode node, String complexName) {
		String[] parts = complexName.split("\\.");
		if (parts.length > 2 && parts[0].equals("java") && parts[1].equals("lang")) {
			String[] subParts = new String[parts.length - 2];
			System.arraycopy(parts, 2, subParts, 0, subParts.length);
			return genJavaLangTypeRef(node, subParts);
		}
		
		return chainDots(node, parts);
	}
	
	public static JCExpression genJavaLangTypeRef(JavacNode node, String... simpleNames) {
		if (LombokOptionsFactory.getDelombokOptions(node.getContext()).getFormatPreferences().javaLangAsFqn()) {
			return chainDots(node, "java", "lang", simpleNames);
		} else {
			return chainDots(node, null, null, simpleNames);
		}
	}
	
	public static JCExpression genJavaLangTypeRef(JavacNode node, int pos, String... simpleNames) {
		if (LombokOptionsFactory.getDelombokOptions(node.getContext()).getFormatPreferences().javaLangAsFqn()) {
			return chainDots(node, pos, "java", "lang", simpleNames);
		} else {
			return chainDots(node, pos, null, null, simpleNames);
		}
	}
	
	public static void addSuppressWarningsAll(JCModifiers mods, JavacNode node, JavacNode source, Context context) {
		if (!LombokOptionsFactory.getDelombokOptions(context).getFormatPreferences().generateSuppressWarnings()) return;
		
		boolean addJLSuppress = !Boolean.FALSE.equals(node.getAst().readConfiguration(ConfigurationKeys.ADD_SUPPRESSWARNINGS_ANNOTATIONS));
		
		if (addJLSuppress) {
			for (JCAnnotation ann : mods.annotations) {
				JCTree type = ann.getAnnotationType();
				Name n = null;
				if (type instanceof JCIdent) n = ((JCIdent) type).name;
				else if (type instanceof JCFieldAccess) n = ((JCFieldAccess) type).name;
				if (n != null && n.contentEquals("SuppressWarnings")) {
					addJLSuppress = false;
				}
			}
		}
		if (addJLSuppress) addAnnotation(mods, node, source, "java.lang.SuppressWarnings", node.getTreeMaker().Literal("all"));
		
		if (Boolean.TRUE.equals(node.getAst().readConfiguration(ConfigurationKeys.ADD_FINDBUGS_SUPPRESSWARNINGS_ANNOTATIONS))) {
			JavacTreeMaker maker = node.getTreeMaker();
			JCExpression arg = maker.Assign(maker.Ident(node.toName("justification")), maker.Literal("generated code"));
			addAnnotation(mods, node, source, "edu.umd.cs.findbugs.annotations.SuppressFBWarnings", arg);
		}
	}
	
	public static void addGenerated(JCModifiers mods, JavacNode node, JavacNode source, Context context) {
		if (!LombokOptionsFactory.getDelombokOptions(context).getFormatPreferences().generateGenerated()) return;
		
		if (HandlerUtil.shouldAddGenerated(node)) {
			addAnnotation(mods, node, source, "javax.annotation.Generated", node.getTreeMaker().Literal("lombok"));
		}
		if (Boolean.TRUE.equals(node.getAst().readConfiguration(ConfigurationKeys.ADD_LOMBOK_GENERATED_ANNOTATIONS))) {
			addAnnotation(mods, node, source, "lombok.Generated", null);
		}
	}
	
	public static void addAnnotation(JCModifiers mods, JavacNode node, JavacNode source, String annotationTypeFqn, JCExpression arg) {
		boolean isJavaLangBased;
		String simpleName; {
			int idx = annotationTypeFqn.lastIndexOf('.');
			simpleName = idx == -1 ? annotationTypeFqn : annotationTypeFqn.substring(idx + 1);
			
			isJavaLangBased = idx == 9 && annotationTypeFqn.regionMatches(0, "java.lang.", 0, 10);
		}
		
		for (JCAnnotation ann : mods.annotations) {
			JCTree annType = ann.getAnnotationType();
			if (annType instanceof JCIdent) {
				Name lastPart = ((JCIdent) annType).name;
				if (lastPart.contentEquals(simpleName)) return;
			}
			
			if (annType instanceof JCFieldAccess) {
				if (annType.toString().equals(annotationTypeFqn)) return;
			}
		}
		
		JavacTreeMaker maker = node.getTreeMaker();
		JCExpression annType = isJavaLangBased ? genJavaLangTypeRef(node, simpleName) : chainDotsString(node, annotationTypeFqn);
		List argList = arg != null ? List.of(arg) : List.nil();
		JCAnnotation annotation = recursiveSetGeneratedBy(maker.Annotation(annType, argList), source);
		mods.annotations = mods.annotations.append(annotation);
	}
	
	static JCExpression addCheckerFrameworkReturnsReceiver(JCExpression returnType, JavacTreeMaker maker, JavacNode typeNode, CheckerFrameworkVersion cfv) {
		if (cfv.generateReturnsReceiver()) {
			JCAnnotation rrAnnotation = maker.Annotation(genTypeRef(typeNode, CheckerFrameworkVersion.NAME__RETURNS_RECEIVER), List.nil());
			returnType = maker.AnnotatedType(List.of(rrAnnotation), returnType);
		}
		return returnType;
	}
	
	private static List addAllButOne(List defs, int idx) {
		ListBuffer out = new ListBuffer();
		int i = 0;
		for (JCTree def : defs) {
			if (i++ != idx) out.append(def);
		}
		return out.toList();
	}
	
	/**
	 * In javac, dotted access of any kind, from {@code java.lang.String} to {@code var.methodName}
	 * is represented by a fold-left of {@code Select} nodes with the leftmost string represented by
	 * a {@code Ident} node. This method generates such an expression.
	 * 

* The position of the generated node(s) will be unpositioned (-1). * * For example, maker.Select(maker.Select(maker.Ident(NAME[java]), NAME[lang]), NAME[String]). * * @see com.sun.tools.javac.tree.JCTree.JCIdent * @see com.sun.tools.javac.tree.JCTree.JCFieldAccess */ public static JCExpression chainDots(JavacNode node, String elem1, String elem2, String... elems) { return chainDots(node, -1, elem1, elem2, elems); } public static JCExpression chainDots(JavacNode node, String[] elems) { return chainDots(node, -1, null, null, elems); } public static JCExpression chainDots(JavacNode node, LombokImmutableList elems) { assert elems != null; JavacTreeMaker maker = node.getTreeMaker(); JCExpression e = null; for (String elem : elems) { if (e == null) e = maker.Ident(node.toName(elem)); else e = maker.Select(e, node.toName(elem)); } return e; } /** * In javac, dotted access of any kind, from {@code java.lang.String} to {@code var.methodName} * is represented by a fold-left of {@code Select} nodes with the leftmost string represented by * a {@code Ident} node. This method generates such an expression. *

* The position of the generated node(s) will be equal to the {@code pos} parameter. * * For example, maker.Select(maker.Select(maker.Ident(NAME[java]), NAME[lang]), NAME[String]). * * @see com.sun.tools.javac.tree.JCTree.JCIdent * @see com.sun.tools.javac.tree.JCTree.JCFieldAccess */ public static JCExpression chainDots(JavacNode node, int pos, String elem1, String elem2, String... elems) { assert elems != null; JavacTreeMaker maker = node.getTreeMaker(); if (pos != -1) maker = maker.at(pos); JCExpression e = null; if (elem1 != null) e = maker.Ident(node.toName(elem1)); if (elem2 != null) e = e == null ? maker.Ident(node.toName(elem2)) : maker.Select(e, node.toName(elem2)); for (int i = 0 ; i < elems.length ; i++) { e = e == null ? maker.Ident(node.toName(elems[i])) : maker.Select(e, node.toName(elems[i])); } assert e != null; return e; } /** * In javac, dotted access of any kind, from {@code java.lang.String} to {@code var.methodName} * is represented by a fold-left of {@code Select} nodes with the leftmost string represented by * a {@code Ident} node. This method generates such an expression. * * For example, maker.Select(maker.Select(maker.Ident(NAME[java]), NAME[lang]), NAME[String]). * * @see com.sun.tools.javac.tree.JCTree.JCIdent * @see com.sun.tools.javac.tree.JCTree.JCFieldAccess */ public static JCExpression chainDotsString(JavacNode node, String elems) { return chainDots(node, null, null, elems.split("\\.")); } /** * Searches the given field node for annotations and returns each one that matches the provided regular expression pattern. * * Only the simple name is checked - the package and any containing class are ignored. */ public static List findAnnotations(JavacNode fieldNode, Pattern namePattern) { ListBuffer result = new ListBuffer(); for (JavacNode child : fieldNode.down()) { if (child.getKind() == Kind.ANNOTATION) { JCAnnotation annotation = (JCAnnotation) child.get(); String name = annotation.annotationType.toString(); int idx = name.lastIndexOf("."); String suspect = idx == -1 ? name : name.substring(idx + 1); if (namePattern.matcher(suspect).matches()) { result.append(annotation); } } } return result.toList(); } public static String scanForNearestAnnotation(JavacNode node, String... anns) { while (node != null) { for (JavacNode ann : node.down()) { if (ann.getKind() != Kind.ANNOTATION) continue; JCAnnotation a = (JCAnnotation) ann.get(); for (String annToFind : anns) if (typeMatches(annToFind, node, a.annotationType)) return annToFind; } node = node.up(); } return null; } public static boolean hasNonNullAnnotations(JavacNode node) { for (JavacNode child : node.down()) { if (child.getKind() == Kind.ANNOTATION) { JCAnnotation annotation = (JCAnnotation) child.get(); String annotationTypeName = getTypeName(annotation.annotationType); for (String nn : NONNULL_ANNOTATIONS) if (typeMatches(nn, node, annotationTypeName)) return true; } } return false; } public static boolean hasNonNullAnnotations(JavacNode node, List anns) { if (anns == null) return false; for (JCAnnotation ann : anns) { String annotationTypeName = getTypeName(ann.annotationType); for (String nn : NONNULL_ANNOTATIONS) if (typeMatches(nn, node, annotationTypeName)) return true; } return false; } /** * Searches the given field node for annotations and returns each one that is 'copyable' (either via configuration or from the base list). */ public static List findCopyableAnnotations(JavacNode node) { java.util.List configuredCopyable = node.getAst().readConfiguration(ConfigurationKeys.COPYABLE_ANNOTATIONS); ListBuffer result = new ListBuffer(); for (JavacNode child : node.down()) { if (child.getKind() == Kind.ANNOTATION) { JCAnnotation annotation = (JCAnnotation) child.get(); String annotationTypeName = getTypeName(annotation.annotationType); boolean match = false; for (TypeName cn : configuredCopyable) if (cn != null && typeMatches(cn.toString(), node, annotationTypeName)) { result.append(annotation); match = true; break; } if (!match) for (String bn : BASE_COPYABLE_ANNOTATIONS) if (typeMatches(bn, node, annotationTypeName)) { result.append(annotation); break; } } } return copyAnnotations(result.toList()); } /** * Searches the given field node for annotations that are specifically intentioned to be copied to the setter. */ public static List findCopyableToSetterAnnotations(JavacNode node) { return findAnnotationsInList(node, COPY_TO_SETTER_ANNOTATIONS); } /** * Searches the given field node for annotations that are specifically intentioned to be copied to the builder's singular method. */ public static List findCopyableToBuilderSingularSetterAnnotations(JavacNode node) { return findAnnotationsInList(node, COPY_TO_BUILDER_SINGULAR_SETTER_ANNOTATIONS); } /** * Searches the given field node for annotations that are in the given list, and returns those. */ private static List findAnnotationsInList(JavacNode node, java.util.List annotationsToFind) { JCAnnotation anno = null; String annoName = null; for (JavacNode child : node.down()) { if (child.getKind() == Kind.ANNOTATION) { if (anno != null) { annoName = ""; break; } JCAnnotation annotation = (JCAnnotation) child.get(); annoName = annotation.annotationType.toString(); anno = annotation; } } if (annoName == null) return List.nil(); if (!annoName.isEmpty()) { for (String bn : annotationsToFind) if (typeMatches(bn, node, annoName)) return List.of(anno); } ListBuffer result = new ListBuffer(); for (JavacNode child : node.down()) { if (child.getKind() == Kind.ANNOTATION) { JCAnnotation annotation = (JCAnnotation) child.get(); String annotationTypeName = getTypeName(annotation.annotationType); boolean match = false; if (!match) for (String bn : annotationsToFind) if (typeMatches(bn, node, annotationTypeName)) { result.append(annotation); break; } } } return copyAnnotations(result.toList()); } /** * Generates a new statement that checks if the given variable is null, and if so, throws a configured exception with the * variable name as message. */ public static JCStatement generateNullCheck(JavacTreeMaker maker, JavacNode variable, JavacNode source) { return generateNullCheck(maker, (JCVariableDecl) variable.get(), source); } /** * Generates a new statement that checks if the given local is null, and if so, throws a configured exception with the * local variable name as message. */ public static JCStatement generateNullCheck(JavacTreeMaker maker, JCExpression typeNode, Name varName, JavacNode source, String customMessage) { NullCheckExceptionType exceptionType = source.getAst().readConfiguration(ConfigurationKeys.NON_NULL_EXCEPTION_TYPE); if (exceptionType == null) exceptionType = NullCheckExceptionType.NULL_POINTER_EXCEPTION; if (typeNode != null && isPrimitive(typeNode)) return null; JCLiteral message = maker.Literal(exceptionType.toExceptionMessage(varName.toString(), customMessage)); LombokImmutableList method = exceptionType.getMethod(); if (method != null) { return maker.Exec(maker.Apply(List.nil(), chainDots(source, method), List.of(maker.Ident(varName), message))); } if (exceptionType == NullCheckExceptionType.ASSERTION) { return maker.Assert(maker.Binary(CTC_NOT_EQUAL, maker.Ident(varName), maker.Literal(CTC_BOT, null)), message); } JCExpression exType = genTypeRef(source, exceptionType.getExceptionType()); JCExpression exception = maker.NewClass(null, List.nil(), exType, List.of(message), null); JCStatement throwStatement = maker.Throw(exception); JCBlock throwBlock = maker.Block(0, List.of(throwStatement)); return maker.If(maker.Binary(CTC_EQUAL, maker.Ident(varName), maker.Literal(CTC_BOT, null)), throwBlock, null); } /** * Generates a new statement that checks if the given variable is null, and if so, throws a configured exception with the * variable name as message. * * This is a special case method reserved for use when the provided declaration differs from the * variable's declaration, i.e. in a constructor or setter where the local parameter is named the same but with the prefix * stripped as a result of @Accessors.prefix. */ public static JCStatement generateNullCheck(JavacTreeMaker maker, JCVariableDecl varDecl, JavacNode source) { return generateNullCheck(maker, varDecl.vartype, varDecl.name, source, null); } /** * Given a list of field names and a node referring to a type, finds each name in the list that does not match a field within the type. */ public static List createListOfNonExistentFields(List list, JavacNode type, boolean excludeStandard, boolean excludeTransient) { boolean[] matched = new boolean[list.size()]; for (JavacNode child : type.down()) { if (list.isEmpty()) break; if (child.getKind() != Kind.FIELD) continue; JCVariableDecl field = (JCVariableDecl)child.get(); if (excludeStandard) { if ((field.mods.flags & Flags.STATIC) != 0) continue; if (field.name.toString().startsWith("$")) continue; } if (excludeTransient && (field.mods.flags & Flags.TRANSIENT) != 0) continue; int idx = list.indexOf(child.getName()); if (idx > -1) matched[idx] = true; } ListBuffer problematic = new ListBuffer(); for (int i = 0 ; i < list.size() ; i++) { if (!matched[i]) problematic.append(i); } return problematic.toList(); } static List unboxAndRemoveAnnotationParameter(JCAnnotation ast, String parameterName, String errorName, JavacNode annotationNode) { ListBuffer params = new ListBuffer(); ListBuffer result = new ListBuffer(); outer: for (JCExpression param : ast.args) { boolean allowRaw; String nameOfParam = "value"; JCExpression valueOfParam = null; if (param instanceof JCAssign) { JCAssign assign = (JCAssign) param; if (assign.lhs instanceof JCIdent) { JCIdent ident = (JCIdent) assign.lhs; nameOfParam = ident.name.toString(); } valueOfParam = assign.rhs; } /* strip trailing underscores */ { int lastIdx; for (lastIdx = nameOfParam.length() ; lastIdx > 0; lastIdx--) { if (nameOfParam.charAt(lastIdx - 1) != '_') break; } allowRaw = lastIdx < nameOfParam.length(); nameOfParam = nameOfParam.substring(0, lastIdx); } if (!parameterName.equals(nameOfParam)) { params.append(param); continue outer; } int endPos = Javac.getEndPosition(param.pos(), (JCCompilationUnit) annotationNode.top().get()); annotationNode.getAst().removeFromDeferredDiagnostics(param.pos, endPos); if (valueOfParam instanceof JCAnnotation) { String dummyAnnotationName = ((JCAnnotation) valueOfParam).annotationType.toString(); dummyAnnotationName = dummyAnnotationName.replace("_", "").replace("$", "").replace("x", "").replace("X", ""); if (dummyAnnotationName.length() > 0) { if (allowRaw) { result.append((JCAnnotation) valueOfParam); } else { addError(errorName, annotationNode); continue outer; } } else { for (JCExpression expr : ((JCAnnotation) valueOfParam).args) { if (expr instanceof JCAssign && ((JCAssign) expr).lhs instanceof JCIdent) { JCIdent id = (JCIdent) ((JCAssign) expr).lhs; if ("value".equals(id.name.toString())) { expr = ((JCAssign) expr).rhs; } else { addError(errorName, annotationNode); } } if (expr instanceof JCAnnotation) { result.append((JCAnnotation) expr); } else if (expr instanceof JCNewArray) { for (JCExpression expr2 : ((JCNewArray) expr).elems) { if (expr2 instanceof JCAnnotation) { result.append((JCAnnotation) expr2); } else { addError(errorName, annotationNode); continue outer; } } } else { addError(errorName, annotationNode); continue outer; } } } } else if (valueOfParam instanceof JCNewArray) { JCNewArray arr = (JCNewArray) valueOfParam; if (arr.elems.isEmpty()) { // Just remove it, this is always fine. } else if (allowRaw) { for (JCExpression jce : arr.elems) { if (jce instanceof JCAnnotation) result.append((JCAnnotation) jce); else addError(errorName, annotationNode); } } else { addError(errorName, annotationNode); } } else { addError(errorName, annotationNode); } } for (JCAnnotation annotation : result) { clearTypes(annotation); } ast.args = params.toList(); return result.toList(); } /** * Removes all type information from the provided tree. */ private static void clearTypes(JCTree tree) { tree.accept(new TreeScanner() { @Override public void scan(JCTree tree) { if (tree == null) return; tree.type = null; super.scan(tree); } @Override public void visitClassDef(JCClassDecl tree) { tree.sym = null; super.visitClassDef(tree); } @Override public void visitMethodDef(JCMethodDecl tree) { tree.sym = null; super.visitMethodDef(tree); } @Override public void visitVarDef(JCVariableDecl tree) { tree.sym = null; super.visitVarDef(tree); } @Override public void visitSelect(JCFieldAccess tree) { tree.sym = null; super.visitSelect(tree); } @Override public void visitIdent(JCIdent tree) { tree.sym = null; super.visitIdent(tree); } @Override public void visitAnnotation(JCAnnotation tree) { JCAnnotationReflect.setAttribute(tree, null); super.visitAnnotation(tree); } }); } private static void addError(String errorName, JavacNode node) { if (node.getLatestJavaSpecSupported() < 8) { node.addError("The correct format up to JDK7 is " + errorName + "=@__({@SomeAnnotation, @SomeOtherAnnotation}))"); } else { node.addError("The correct format for JDK8+ is " + errorName + "_={@SomeAnnotation, @SomeOtherAnnotation})"); } } public static List copyTypeParams(JavacNode source, List params) { if (params == null || params.isEmpty()) return params; ListBuffer out = new ListBuffer(); JavacTreeMaker maker = source.getTreeMaker(); for (JCTypeParameter tp : params) { List bounds = tp.bounds; if (bounds != null && !bounds.isEmpty()) { ListBuffer boundsCopy = new ListBuffer(); for (JCExpression expr : tp.bounds) { boundsCopy.append(cloneType(maker, expr, source)); } bounds = boundsCopy.toList(); } out.append(maker.TypeParameter(tp.name, bounds)); } return out.toList(); } public static List getTypeUseAnnotations(JCExpression from) { if (!JCAnnotatedTypeReflect.is(from)) return List.nil(); return JCAnnotatedTypeReflect.getAnnotations(from); } public static JCExpression removeTypeUseAnnotations(JCExpression from) { if (!JCAnnotatedTypeReflect.is(from)) return from; return JCAnnotatedTypeReflect.getUnderlyingType(from); } public static JCExpression namePlusTypeParamsToTypeReference(JavacTreeMaker maker, JavacNode type, List params) { JCClassDecl td = (JCClassDecl) type.get(); boolean instance = (td.mods.flags & Flags.STATIC) == 0; return namePlusTypeParamsToTypeReference(maker, type.up(), td.name, instance, params, List.nil()); } public static JCExpression namePlusTypeParamsToTypeReference(JavacTreeMaker maker, JavacNode type, List params, List annotations) { JCClassDecl td = (JCClassDecl) type.get(); boolean instance = (td.mods.flags & Flags.STATIC) == 0; return namePlusTypeParamsToTypeReference(maker, type.up(), td.name, instance, params, annotations); } public static JCExpression namePlusTypeParamsToTypeReference(JavacTreeMaker maker, JavacNode parentType, Name typeName, boolean instance, List params) { return namePlusTypeParamsToTypeReference(maker, parentType, typeName, instance, params, List.nil()); } public static JCExpression namePlusTypeParamsToTypeReference(JavacTreeMaker maker, JavacNode parentType, Name typeName, boolean instance, List params, List annotations) { JCExpression r = null; if (parentType != null && parentType.getKind() == Kind.TYPE && !parentType.getName().isEmpty()) { JCClassDecl td = (JCClassDecl) parentType.get(); boolean outerInstance = instance && ((td.mods.flags & Flags.STATIC) == 0); List outerParams = instance ? td.typarams : List.nil(); r = namePlusTypeParamsToTypeReference(maker, parentType.up(), td.name, outerInstance, outerParams, List.nil()); } r = r == null ? maker.Ident(typeName) : maker.Select(r, typeName); if (!annotations.isEmpty()) r = JCAnnotatedTypeReflect.create(annotations, r); if (!params.isEmpty()) r = maker.TypeApply(r, typeParameterNames(maker, params)); return r; } public static List typeParameterNames(JavacTreeMaker maker, List params) { ListBuffer typeArgs = new ListBuffer(); for (JCTypeParameter param : params) { typeArgs.append(maker.Ident(param.name)); } return typeArgs.toList(); } public static void sanityCheckForMethodGeneratingAnnotationsOnBuilderClass(JavacNode typeNode, JavacNode errorNode) { List disallowed = List.nil(); for (JavacNode child : typeNode.down()) { for (String annType : INVALID_ON_BUILDERS) { if (annotationTypeMatches(annType, child)) { int lastIndex = annType.lastIndexOf('.'); disallowed = disallowed.append(lastIndex == -1 ? annType : annType.substring(lastIndex + 1)); } } } int size = disallowed.size(); if (size == 0) return; if (size == 1) { errorNode.addError("@" + disallowed.head + " is not allowed on builder classes."); return; } StringBuilder out = new StringBuilder(); for (String a : disallowed) out.append("@").append(a).append(", "); out.setLength(out.length() - 2); errorNode.addError(out.append(" are not allowed on builder classes.").toString()); } static List copyAnnotations(List in) { ListBuffer out = new ListBuffer(); for (JCExpression expr : in) { if (!(expr instanceof JCAnnotation)) continue; out.append((JCAnnotation) expr.clone()); } return out.toList(); } static List mergeAnnotations(List a, List b) { if (a == null || a.isEmpty()) return b; if (b == null || b.isEmpty()) return a; ListBuffer out = new ListBuffer(); for (JCAnnotation ann : a) out.append(ann); for (JCAnnotation ann : b) out.append(ann); return out.toList(); } /** * Returns {@code true} if the provided node is an actual class and not some other type declaration (so, not an annotation definition, interface, enum, or record). */ public static boolean isClass(JavacNode typeNode) { return isClassAndDoesNotHaveFlags(typeNode, Flags.INTERFACE | Flags.ENUM | Flags.ANNOTATION | RECORD); } /** * Returns {@code true} if the provided node is an actual class or enum and not some other type declaration (so, not an annotation definition, interface, or record). */ public static boolean isClassOrEnum(JavacNode typeNode) { return isClassAndDoesNotHaveFlags(typeNode, Flags.INTERFACE | Flags.ANNOTATION | RECORD); } public static boolean isClassOrEnumOrInterface(JavacNode typeNode) { return isClassAndDoesNotHaveFlags(typeNode, Flags.ANNOTATION | RECORD); } /** * Returns {@code true} if the provided node is an actual class, an enum or a record and not some other type declaration (so, not an annotation definition or interface). */ public static boolean isClassEnumOrRecord(JavacNode typeNode) { return isClassAndDoesNotHaveFlags(typeNode, Flags.INTERFACE | Flags.ANNOTATION); } /** * Returns {@code true} if the provided node is a record declaration (so, not an annotation definition, interface, enum, or plain class). */ public static boolean isRecord(JavacNode typeNode) { return typeNode.getKind() == Kind.TYPE && (((JCClassDecl) typeNode.get()).mods.flags & RECORD) != 0; } public static boolean isClassAndDoesNotHaveFlags(JavacNode typeNode, long flags) { JCClassDecl typeDecl = null; if (typeNode.get() instanceof JCClassDecl) typeDecl = (JCClassDecl) typeNode.get(); else return false; long typeDeclflags = typeDecl == null ? 0 : typeDecl.mods.flags; return (typeDeclflags & flags) == 0; } /** * Returns {@code true} if the provided node supports static methods and types (top level or static class) */ public static boolean isStaticAllowed(JavacNode typeNode) { return typeNode.isStatic() || typeNode.up() == null || typeNode.up().getKind() == Kind.COMPILATION_UNIT || isRecord(typeNode); } public static JavacNode upToTypeNode(JavacNode node) { if (node == null) throw new NullPointerException("node"); while ((node != null) && !(node.get() instanceof JCClassDecl)) node = node.up(); return node; } public static List cloneTypes(JavacTreeMaker maker, List in, JavacNode source) { if (in.isEmpty()) return List.nil(); if (in.size() == 1) return List.of(cloneType(maker, in.get(0), source)); ListBuffer lb = new ListBuffer(); for (JCExpression expr : in) lb.append(cloneType(maker, expr, source)); return lb.toList(); } /** * Creates a full clone of a given javac AST type node. Every part is cloned (every identifier, every select, every wildcard, every type apply, every type_use annotation). * * If there's any node in the tree that we don't know how to clone, that part isn't cloned. However, we wouldn't know what could possibly show up that we * can't currently clone; that's just a safeguard. * * This should be used if the type looks the same in the code, but resolves differently. For example, a static method that has some generics in it named after * the class's own parameter, but as its a static method, the static method's notion of {@code T} is different from the class notion of {@code T}. If you're duplicating * a type used in the class context, you need to use this method. */ public static JCExpression cloneType(JavacTreeMaker maker, JCExpression in, JavacNode source) { JCExpression out = cloneType0(maker, in); if (out != null) recursiveSetGeneratedBy(out, source); return out; } private static JCExpression cloneType0(JavacTreeMaker maker, JCTree in) { if (in == null) return null; if (in instanceof JCPrimitiveTypeTree) { return maker.TypeIdent(TypeTag.typeTag(in)); } if (in instanceof JCIdent) { return maker.Ident(((JCIdent) in).name); } if (in instanceof JCFieldAccess) { JCFieldAccess fa = (JCFieldAccess) in; return maker.Select(cloneType0(maker, fa.selected), fa.name); } if (in instanceof JCArrayTypeTree) { JCArrayTypeTree att = (JCArrayTypeTree) in; return maker.TypeArray(cloneType0(maker, att.elemtype)); } if (in instanceof JCTypeApply) { JCTypeApply ta = (JCTypeApply) in; ListBuffer lb = new ListBuffer(); for (JCExpression typeArg : ta.arguments) { lb.append(cloneType0(maker, typeArg)); } return maker.TypeApply(cloneType0(maker, ta.clazz), lb.toList()); } if (in instanceof JCWildcard) { JCWildcard w = (JCWildcard) in; JCExpression newInner = cloneType0(maker, w.inner); TypeBoundKind newKind; switch (w.getKind()) { case SUPER_WILDCARD: newKind = maker.TypeBoundKind(BoundKind.SUPER); break; case EXTENDS_WILDCARD: newKind = maker.TypeBoundKind(BoundKind.EXTENDS); break; default: case UNBOUNDED_WILDCARD: newKind = maker.TypeBoundKind(BoundKind.UNBOUND); break; } return maker.Wildcard(newKind, newInner); } if (JCAnnotatedTypeReflect.is(in)) { JCExpression underlyingType = cloneType0(maker, JCAnnotatedTypeReflect.getUnderlyingType(in)); List anns = copyAnnotations(JCAnnotatedTypeReflect.getAnnotations(in)); return JCAnnotatedTypeReflect.create(anns, underlyingType); } // This is somewhat unsafe, but it's better than outright throwing an exception here. Returning null will just cause an exception down the pipeline. return (JCExpression) in; } public static enum CopyJavadoc { VERBATIM { @Override public String apply(final JCCompilationUnit cu, final JavacNode node) { return Javac.getDocComment(cu, node.get()); } }, GETTER { @Override public String apply(final JCCompilationUnit cu, final JavacNode node) { final JCTree n = node.get(); String javadoc = Javac.getDocComment(cu, n); // step 1: Check if there is a 'GETTER' section. If yes, that becomes the new method's javadoc. String out = getJavadocSection(javadoc, "GETTER"); final boolean sectionBased = out != null; if (!sectionBased) { out = stripLinesWithTagFromJavadoc(stripSectionsFromJavadoc(javadoc), JavadocTag.PARAM); } node.getAst().cleanupTask("javadocfilter-getter", n, new CleanupTask() { @Override public void cleanup() { String javadoc = Javac.getDocComment(cu, n); if (javadoc == null || javadoc.isEmpty()) return; javadoc = stripSectionsFromJavadoc(javadoc); if (!sectionBased) { javadoc = stripLinesWithTagFromJavadoc(stripSectionsFromJavadoc(javadoc), JavadocTag.RETURN); } Javac.setDocComment(cu, n, javadoc); } }); return out; } }, SETTER { @Override public String apply(final JCCompilationUnit cu, final JavacNode node) { return applySetter(cu, node, "SETTER"); } }, WITH { @Override public String apply(final JCCompilationUnit cu, final JavacNode node) { return addReturnsUpdatedSelfIfNeeded(applySetter(cu, node, "WITH|WITHER")); } }, WITH_BY { @Override public String apply(final JCCompilationUnit cu, final JavacNode node) { return applySetter(cu, node, "WITHBY|WITH_BY"); } }; public abstract String apply(final JCCompilationUnit cu, final JavacNode node); private static String applySetter(final JCCompilationUnit cu, JavacNode node, String sectionName) { final JCTree n = node.get(); String javadoc = Javac.getDocComment(cu, n); // step 1: Check if there is a 'SETTER' section. If yes, that becomes the new method's javadoc. String out = getJavadocSection(javadoc, sectionName); final boolean sectionBased = out != null; if (!sectionBased) { out = stripLinesWithTagFromJavadoc(stripSectionsFromJavadoc(javadoc), JavadocTag.RETURN); } node.getAst().cleanupTask("javadocfilter-setter", n, new CleanupTask() { @Override public void cleanup() { String javadoc = Javac.getDocComment(cu, n); if (javadoc == null || javadoc.isEmpty()) return; javadoc = stripSectionsFromJavadoc(javadoc); if (!sectionBased) { javadoc = stripLinesWithTagFromJavadoc(stripSectionsFromJavadoc(javadoc), JavadocTag.PARAM); } Javac.setDocComment(cu, n, javadoc); } }); return shouldReturnThis(node, JavacHandlerUtil.getAccessorsForField(node)) ? addReturnsThisIfNeeded(out) : out; } } public static void copyJavadoc(JavacNode from, JCTree to, CopyJavadoc copyMode) { copyJavadoc(from, to, copyMode, false); } /** * Copies javadoc on one node to the other. * * in 'GETTER' copyMode, first a 'GETTER' segment is searched for. If it exists, that will become the javadoc for the 'to' node, and this section is * stripped out of the 'from' node. If no 'GETTER' segment is found, then the entire javadoc is taken minus any {@code @param} lines and other sections. * any {@code @return} lines are stripped from 'from'. * * in 'SETTER' mode, stripping works similarly to 'GETTER' mode, except {@code param} are copied and stripped from the original and {@code @return} are skipped. */ public static void copyJavadoc(JavacNode from, JCTree to, CopyJavadoc copyMode, boolean forceAddReturn) { if (copyMode == null) copyMode = CopyJavadoc.VERBATIM; try { JCCompilationUnit cu = ((JCCompilationUnit) from.top().get()); String newJavadoc = copyMode.apply(cu, from); if (forceAddReturn) { newJavadoc = addReturnsThisIfNeeded(newJavadoc); } Javac.setDocComment(cu, to, newJavadoc); } catch (Exception ignore) {} } public static boolean isDirectDescendantOfObject(JavacNode typeNode) { if (!(typeNode.get() instanceof JCClassDecl)) throw new IllegalArgumentException("not a type node"); JCTree extending = Javac.getExtendsClause((JCClassDecl) typeNode.get()); if (extending == null) return true; String p = extending.toString(); return p.equals("Object") || p.equals("java.lang.Object"); } public static void createRelevantNullableAnnotation(JavacNode typeNode, JCMethodDecl mth) { NullAnnotationLibrary lib = typeNode.getAst().readConfiguration(ConfigurationKeys.ADD_NULL_ANNOTATIONS); if (lib == null) return; applyAnnotationToMethodDecl(typeNode, mth, lib.getNullableAnnotation(), lib.isTypeUse()); } public static void createRelevantNonNullAnnotation(JavacNode typeNode, JCMethodDecl mth) { NullAnnotationLibrary lib = typeNode.getAst().readConfiguration(ConfigurationKeys.ADD_NULL_ANNOTATIONS); if (lib == null) return; applyAnnotationToMethodDecl(typeNode, mth, lib.getNonNullAnnotation(), lib.isTypeUse()); } public static void createRelevantNonNullAnnotation(JavacNode typeNode, JCVariableDecl arg) { NullAnnotationLibrary lib = typeNode.getAst().readConfiguration(ConfigurationKeys.ADD_NULL_ANNOTATIONS); if (lib == null) return; applyAnnotationToVarDecl(typeNode, arg, lib.getNonNullAnnotation(), lib.isTypeUse()); } public static void createRelevantNullableAnnotation(JavacNode typeNode, JCVariableDecl arg) { NullAnnotationLibrary lib = typeNode.getAst().readConfiguration(ConfigurationKeys.ADD_NULL_ANNOTATIONS); if (lib == null) return; applyAnnotationToVarDecl(typeNode, arg, lib.getNullableAnnotation(), lib.isTypeUse()); } private static void applyAnnotationToMethodDecl(JavacNode typeNode, JCMethodDecl mth, String annType, boolean typeUse) { if (annType == null) return; JavacTreeMaker maker = typeNode.getTreeMaker(); JCAnnotation m = maker.Annotation(genTypeRef(typeNode, annType), List.nil()); if (typeUse) { JCExpression resType = mth.restype; if (resType instanceof JCTypeApply) { JCTypeApply ta = (JCTypeApply) resType; if (ta.clazz instanceof JCFieldAccess) { mth.restype = maker.TypeApply(maker.AnnotatedType(List.of(m), ta.clazz), ta.arguments); return; } resType = ta.clazz; } if (resType instanceof JCFieldAccess || resType instanceof JCArrayTypeTree) { mth.restype = maker.AnnotatedType(List.of(m), resType); return; } if (JCAnnotatedTypeReflect.is(resType)) { List annotations = JCAnnotatedTypeReflect.getAnnotations(resType); JCAnnotatedTypeReflect.setAnnotations(resType, annotations.prepend(m)); return; } if (resType instanceof JCPrimitiveTypeTree || resType instanceof JCIdent) { mth.mods.annotations = mth.mods.annotations == null ? List.of(m) : mth.mods.annotations.prepend(m); } } else { mth.mods.annotations = mth.mods.annotations == null ? List.of(m) : mth.mods.annotations.prepend(m); } } private static void applyAnnotationToVarDecl(JavacNode typeNode, JCVariableDecl arg, String annType, boolean typeUse) { if (annType == null) return; JavacTreeMaker maker = typeNode.getTreeMaker(); JCAnnotation m = maker.Annotation(genTypeRef(typeNode, annType), List.nil()); if (typeUse) { JCExpression varType = arg.vartype; JCTypeApply ta = null; if (varType instanceof JCTypeApply) { ta = (JCTypeApply) varType; varType = ta.clazz; } if (varType instanceof JCFieldAccess || varType instanceof JCArrayTypeTree) { varType = maker.AnnotatedType(List.of(m), varType); if (ta != null) ta.clazz = varType; else arg.vartype = varType; return; } if (JCAnnotatedTypeReflect.is(varType)) { List annotations = JCAnnotatedTypeReflect.getAnnotations(varType); JCAnnotatedTypeReflect.setAnnotations(varType, annotations.prepend(m)); return; } if (varType instanceof JCPrimitiveTypeTree || varType instanceof JCIdent) { arg.mods.annotations = arg.mods.annotations == null ? List.of(m) : arg.mods.annotations.prepend(m); } } else { arg.mods.annotations = arg.mods.annotations == null ? List.of(m) : arg.mods.annotations.prepend(m); } } }