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

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

Go to download

The library enhancing Java classes at compilation by annotations that contained in the Develhack Core Library.

The newest version!
/*
 * Copyright (C) 2009-2015 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 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.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.Matcher;
import java.util.regex.Pattern;

import lombok.AccessLevel;
import lombok.ConfigurationKeys;
import lombok.Data;
import lombok.Getter;
import lombok.core.AST.Kind;
import lombok.core.AnnotationValues;
import lombok.core.LombokImmutableList;
import lombok.core.AnnotationValues.AnnotationValue;
import lombok.core.TypeResolver;
import lombok.core.configuration.NullCheckExceptionType;
import lombok.core.handlers.HandlerUtil;
import lombok.delombok.LombokOptionsFactory;
import lombok.experimental.Accessors;
import lombok.experimental.Tolerate;
import lombok.javac.Javac;
import lombok.javac.JavacNode;
import lombok.javac.JavacTreeMaker;

import com.sun.tools.javac.code.BoundKind;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.parser.Tokens.Comment;
import com.sun.tools.javac.tree.DocCommentTable;
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.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.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;

/**
 * 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 JCTree source;
		private final Context context;
		
		MarkingScanner(JCTree source, Context context) {
			this.source = source;
			this.context = context;
		}
		
		@Override public void scan(JCTree tree) {
			if (tree == null) return;
			setGeneratedBy(tree, source, context);
			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());
	}
	
	private static boolean inNetbeansEditor(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, JCTree source, Context context) {
		if (node == null) return null;
		setGeneratedBy(node, source, context);
		node.accept(new MarkingScanner(source, context));
		return node;
	}
	
	public static  T setGeneratedBy(T node, JCTree source, Context context) {
		if (node == null) return null;
		if (source == null) JCTree_generatedNode.clear(node);
		else JCTree_generatedNode.set(node, source);
		if (source != null && (!inNetbeansEditor(context) || (node instanceof JCVariableDecl && (((JCVariableDecl) node).mods.flags & Flags.PARAMETER) != 0))) node.pos = source.pos;
		return node;
	}
	
	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;
		}
	}
	
	/**
	 * 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 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) {
		String typeName = typeNode.toString();
		
		TypeResolver resolver = new TypeResolver(node.getImportList());
		return resolver.typeMatches(node, type.getName(), typeName);
	}
	
	/**
	 * 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) {
		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;
	}
	
	/**
	 * 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) {
		Map values = new HashMap();
		JCAnnotation anno = (JCAnnotation) node.get();
		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);
					guesses.add(calculateGuess(inner));
					positions.add(inner.pos());
				}
			} else {
				raws.add(rhs.toString());
				expressions.add(rhs);
				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.
	 */
	@SuppressWarnings("unchecked")
	public static void deleteAnnotationIfNeccessary(JavacNode annotation, Class 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.
	 */
	@SuppressWarnings("unchecked")
	public static void deleteAnnotationIfNeccessary(JavacNode annotation, Class annotationType1, Class annotationType2) {
		deleteAnnotationIfNeccessary0(annotation, annotationType1, annotationType2);
	}
	
	private static void deleteAnnotationIfNeccessary0(JavacNode annotation, Class... 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());
			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 (Class annotationType : annotationTypes) {
			deleteImportFromCompilationUnit(annotation, annotationType.getName());
		}
	}
	
	public static void deleteImportFromCompilationUnit(JavacNode node, String name) {
		if (inNetbeansEditor(node)) return;
		if (!node.shouldDeleteLombokAnnotations()) return;
		ListBuffer newDefs = new ListBuffer();
		
		JCCompilationUnit unit = (JCCompilationUnit) node.top().get();
		
		for (JCTree def : unit.defs) {
			boolean delete = false;
			if (def instanceof JCImport) {
				JCImport imp0rt = (JCImport)def;
				delete = (!imp0rt.staticImport && imp0rt.qualid.toString().equals(name));
			}
			if (!delete) newDefs.append(def);
		}
		unit.defs = newDefs.toList();
	}

	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();
	}
	
	/** 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 TransformationsUtil#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));
	}
	
	/**
	 * @return the likely getter name for the stated field. (e.g. private boolean foo; to isFoo).
	 * 
	 * Convenient wrapper around {@link TransformationsUtil#toGetterName(lombok.core.AnnotationValues, CharSequence, boolean)}.
	 */
	public static String toGetterName(JavacNode field) {
		return HandlerUtil.toGetterName(field.getAst(), getAccessorsForField(field), field.getName(), isBoolean(field));
	}
	
	/**
	 * Translates the given field into all possible setter names.
	 * Convenient wrapper around {@link TransformationsUtil#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));
	}
	
	/**
	 * @return the likely setter name for the stated field. (e.g. private boolean foo; to setFoo).
	 * 
	 * Convenient wrapper around {@link TransformationsUtil#toSetterName(lombok.core.AnnotationValues, CharSequence, boolean)}.
	 */
	public static String toSetterName(JavacNode field) {
		return HandlerUtil.toSetterName(field.getAst(), getAccessorsForField(field), field.getName(), isBoolean(field));
	}
	
	/**
	 * Translates the given field into all possible wither names.
	 * Convenient wrapper around {@link TransformationsUtil#toAllWitherNames(lombok.core.AnnotationValues, CharSequence, boolean)}.
	 */
	public static java.util.List toAllWitherNames(JavacNode field) {
		return HandlerUtil.toAllWitherNames(field.getAst(), getAccessorsForField(field), field.getName(), isBoolean(field));
	}
	
	/**
	 * @return the likely wither name for the stated field. (e.g. private boolean foo; to withFoo).
	 * 
	 * Convenient wrapper around {@link TransformationsUtil#toWitherName(lombok.core.AnnotationValues, CharSequence, boolean)}.
	 */
	public static String toWitherName(JavacNode field) {
		return HandlerUtil.toWitherName(field.getAst(), getAccessorsForField(field), 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) {
		if ((((JCVariableDecl) field.get()).mods.flags & Flags.STATIC) != 0) return false;
		
		AnnotationValues accessors = JavacHandlerUtil.getAccessorsForField(field);
		
		return HandlerUtil.shouldReturnThis0(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();
		if (typeNode != null && typeNode.get() instanceof JCClassDecl) {
			JCClassDecl type = (JCClassDecl) typeNode.get();
			ListBuffer typeArgs = new ListBuffer();
			if (!type.typarams.isEmpty()) {
				for (JCTypeParameter tp : type.typarams) {
					typeArgs.append(maker.Ident(tp.name));
				}
				return maker.TypeApply(maker.Ident(type.name), typeArgs.toList());
			} else {
				return maker.Ident(type.name);
			}
		} else {
			return null;
		}
	}
	
	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) {
		for (JavacNode node : field.down()) {
			if (annotationTypeMatches(Accessors.class, node)) {
				return createAnnotation(Accessors.class, node);
			}
		}
		
		JavacNode current = field.up();
		while (current != null) {
			for (JavacNode node : current.down()) {
				if (annotationTypeMatches(Accessors.class, node)) {
					return createAnnotation(Accessors.class, node);
				}
			}
			current = current.up();
		}
		
		return AnnotationValues.of(Accessors.class, field);
	}
	
	/**
	 * 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;
						}
						
						List annotations = md.getModifiers().getAnnotations();
						if (annotations != null) for (JCAnnotation anno : annotations) {
							if (typeMatches(Tolerate.class, node, anno.getAnnotationType())) continue top;
						}
						
						return getGeneratedBy(def) == null ? MemberExistsResult.EXISTS_BY_USER : MemberExistsResult.EXISTS_BY_LOMBOK;
					}
				}
			}
		}
		
		return MemberExistsResult.NOT_EXISTS;
	}
	
	/**
	 * 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) {
			top: 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;
						List annotations = md.getModifiers().getAnnotations();
						if (annotations != null) for (JCAnnotation anno : annotations) {
							if (typeMatches(Tolerate.class, node, anno.getAnnotationType())) continue top;
						}
						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 && new 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;
	}
	
	public enum FieldAccess {
		GETTER, PREFER_FIELD, ALWAYS_FIELD;
	}
	
	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) {
		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;
	}
	
	/**
	 * 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);
	}

	private static JavacNode injectField(JavacNode typeNode, JCVariableDecl field, boolean addGenerated) {
		JCClassDecl type = (JCClassDecl) typeNode.get();
		
		if (addGenerated) {
			addSuppressWarningsAll(field.mods, typeNode, field.pos, getGeneratedBy(field), typeNode.getContext());
			addGenerated(field.mods, typeNode, field.pos, getGeneratedBy(field), typeNode.getContext());
		}
		
		List insertAfter = null;
		List insertBefore = type.defs;
		while (insertBefore.tail != null) {
			if (insertBefore.head instanceof JCVariableDecl) {
				JCVariableDecl f = (JCVariableDecl) insertBefore.head;
				if (isEnumConstant(f) || isGenerated(f)) {
					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;
		}
		
		return typeNode.add(field, Kind.FIELD);
	}
	
	public static boolean isEnumConstant(final JCVariableDecl field) {
		return (field.mods.flags & Flags.ENUM) != 0;
	}
	
	/**
	 * 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);
						if (type.sym != null && type.sym.members_field != null) {
							 type.sym.members_field.remove(((JCMethodDecl)def).sym);
						}
						break;
					}
				}
				idx++;
			}
		}
		
		addSuppressWarningsAll(method.mods, typeNode, method.pos, getGeneratedBy(method), typeNode.getContext());
		addGenerated(method.mods, typeNode, method.pos, getGeneratedBy(method), typeNode.getContext());
		type.defs = type.defs.append(method);
		
		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, type.pos, getGeneratedBy(type), typeNode.getContext());
		addGenerated(type.mods, typeNode, type.pos, getGeneratedBy(type), typeNode.getContext());
		typeDecl.defs = typeDecl.defs.append(type);
		return typeNode.add(type, Kind.TYPE);
	}
	
	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, int pos, JCTree source, Context context) {
		if (!LombokOptionsFactory.getDelombokOptions(context).getFormatPreferences().generateSuppressWarnings()) return;
		addAnnotation(mods, node, pos, source, context, "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, pos, source, context, "edu.umd.cs.findbugs.annotations.SuppressFBWarnings", arg);
		}
	}
	
	public static void addGenerated(JCModifiers mods, JavacNode node, int pos, JCTree source, Context context) {
		if (!LombokOptionsFactory.getDelombokOptions(context).getFormatPreferences().generateGenerated()) return;
		
		if (!Boolean.FALSE.equals(node.getAst().readConfiguration(ConfigurationKeys.ADD_GENERATED_ANNOTATIONS))) {
			addAnnotation(mods, node, pos, source, context, "javax.annotation.Generated", node.getTreeMaker().Literal("lombok"));
		}
	}
	
	private static void addAnnotation(JCModifiers mods, JavacNode node, int pos, JCTree source, Context context, 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();
			Name lastPart = null;
			if (annType instanceof JCIdent) lastPart = ((JCIdent) annType).name;
			else if (annType instanceof JCFieldAccess) lastPart = ((JCFieldAccess) annType).name;
			
			if (lastPart != null && lastPart.contentEquals(simpleName)) return;
		}
		JavacTreeMaker maker = node.getTreeMaker();
		JCExpression annType = isJavaLangBased ? genJavaLangTypeRef(node, simpleName) : chainDotsString(node, annotationTypeFqn);
		annType.pos = pos;
		if (arg != null) {
			arg.pos = pos;
			if (arg instanceof JCAssign) {
				((JCAssign) arg).lhs.pos = pos;
				((JCAssign) arg).rhs.pos = pos;
			}
		}
		List argList = arg != null ? List.of(arg) : List.nil();
		JCAnnotation annotation = recursiveSetGeneratedBy(maker.Annotation(annType, argList), source, context);
		annotation.pos = pos;
		mods.annotations = mods.annotations.append(annotation);
	}
	
	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(); } /** * Generates a new statement that checks if the given variable is null, and if so, throws a specified exception with the * variable name as message. * * @param exName The name of the exception to throw; normally {@code java.lang.NullPointerException}. */ public static JCStatement generateNullCheck(JavacTreeMaker maker, JavacNode variable, JavacNode source) { NullCheckExceptionType exceptionType = source.getAst().readConfiguration(ConfigurationKeys.NON_NULL_EXCEPTION_TYPE); if (exceptionType == null) exceptionType = NullCheckExceptionType.NULL_POINTER_EXCEPTION; JCVariableDecl varDecl = (JCVariableDecl) variable.get(); if (isPrimitive(varDecl.vartype)) return null; Name fieldName = varDecl.name; JCExpression exType = genTypeRef(variable, exceptionType.getExceptionType()); JCExpression exception = maker.NewClass(null, List.nil(), exType, List.of(maker.Literal(exceptionType.toExceptionMessage(fieldName.toString()))), null); JCStatement throwStatement = maker.Throw(exception); JCBlock throwBlock = maker.Block(0, List.of(throwStatement)); return maker.If(maker.Binary(CTC_EQUAL, maker.Ident(fieldName), maker.Literal(CTC_BOT, null)), throwBlock, 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(); try { for (JCExpression arg : ast.args) { String argName = "value"; if (arg instanceof JCAssign) { JCAssign as = (JCAssign) arg; argName = as.lhs.toString(); } if (!argName.equals(parameterName)) continue; } } catch (Exception ignore) {} outer: for (JCExpression param : ast.args) { 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; } 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) { annotationNode.addError("The correct format is " + errorName + "@__({@SomeAnnotation, @SomeOtherAnnotation}))"); continue outer; } 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 { annotationNode.addError("The correct format is " + errorName + "@__({@SomeAnnotation, @SomeOtherAnnotation}))"); continue outer; } } 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 { annotationNode.addError("The correct format is " + errorName + "@__({@SomeAnnotation, @SomeOtherAnnotation}))"); continue outer; } } } else { annotationNode.addError("The correct format is " + errorName + "@__({@SomeAnnotation, @SomeOtherAnnotation}))"); continue outer; } } } else { if (valueOfParam instanceof JCNewArray && ((JCNewArray) valueOfParam).elems.isEmpty()) { // Then we just remove it and move on (it's onMethod={} for example). } else { annotationNode.addError("The correct format is " + errorName + "@__({@SomeAnnotation, @SomeOtherAnnotation}))"); } } } ast.args = params.toList(); return result.toList(); } public static List copyTypeParams(JavacTreeMaker maker, List params) { if (params == null || params.isEmpty()) return params; ListBuffer out = new ListBuffer(); for (JCTypeParameter tp : params) out.append(maker.TypeParameter(tp.name, tp.bounds)); return out.toList(); } public static JCExpression namePlusTypeParamsToTypeReference(JavacTreeMaker maker, Name typeName, List params) { ListBuffer typeArgs = new ListBuffer(); if (!params.isEmpty()) { for (JCTypeParameter param : params) { typeArgs.append(maker.Ident(param.name)); } return maker.TypeApply(maker.Ident(typeName), typeArgs.toList()); } return maker.Ident(typeName); } public static void sanityCheckForMethodGeneratingAnnotationsOnBuilderClass(JavacNode typeNode, JavacNode errorNode) { List disallowed = List.nil(); for (JavacNode child : typeNode.down()) { for (Class annType : INVALID_ON_BUILDERS) { if (annotationTypeMatches(annType, child)) { disallowed = disallowed.append(annType.getSimpleName()); } } } 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 boolean isClass(JavacNode typeNode) { return isClassAndDoesNotHaveFlags(typeNode, Flags.INTERFACE | Flags.ENUM | Flags.ANNOTATION); } static boolean isClassOrEnum(JavacNode typeNode) { return isClassAndDoesNotHaveFlags(typeNode, Flags.INTERFACE | Flags.ANNOTATION); } public static boolean isClassAndDoesNotHaveFlags(JavacNode typeNode, int 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; } 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; } /** * Creates a full clone of a given javac AST type node. Every part is cloned (every identifier, every select, every wildcard, every type apply). * * 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, JCTree source, Context context) { JCExpression out = cloneType0(maker, in); if (out != null) recursiveSetGeneratedBy(out, source, context); return out; } private static JCExpression cloneType0(JavacTreeMaker maker, JCTree in) { if (in == null) return null; if (in instanceof JCPrimitiveTypeTree) return (JCExpression) 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); } // 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; } private static final Pattern SECTION_FINDER = Pattern.compile("^\\s*\\**\\s*[-*][-*]+\\s*([GS]ETTER|WITHER)\\s*[-*][-*]+\\s*\\**\\s*$", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE); public static String stripLinesWithTagFromJavadoc(String javadoc, String regexpFragment) { Pattern p = Pattern.compile("^\\s*\\**\\s*" + regexpFragment + "\\s*\\**\\s*$", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE); Matcher m = p.matcher(javadoc); return m.replaceAll(""); } public static String stripSectionsFromJavadoc(String javadoc) { Matcher m = SECTION_FINDER.matcher(javadoc); if (!m.find()) return javadoc; return javadoc.substring(0, m.start()); } public static String[] splitJavadocOnSectionIfPresent(String javadoc, String sectionName) { Matcher m = SECTION_FINDER.matcher(javadoc); int getterSectionHeaderStart = -1; int getterSectionStart = -1; int getterSectionEnd = -1; while (m.find()) { if (m.group(1).equalsIgnoreCase(sectionName)) { getterSectionStart = m.end() + 1; getterSectionHeaderStart = m.start(); } else if (getterSectionStart != -1) { getterSectionEnd = m.start(); } } if (getterSectionStart != -1) { if (getterSectionEnd != -1) { return new String[] {javadoc.substring(getterSectionStart, getterSectionEnd), javadoc.substring(0, getterSectionHeaderStart) + javadoc.substring(getterSectionEnd)}; } else { return new String[] {javadoc.substring(getterSectionStart), javadoc.substring(0, getterSectionHeaderStart)}; } } return null; } public static enum CopyJavadoc { VERBATIM, GETTER { @Override public String[] split(String javadoc) { // step 1: Check if there is a 'GETTER' section. If yes, that becomes the new method's javadoc and we strip that from the original. String[] out = splitJavadocOnSectionIfPresent(javadoc, "GETTER"); if (out != null) return out; // failing that, create a copy, but strip @return from the original and @param from the copy, as well as other sections. String copy = javadoc; javadoc = stripLinesWithTagFromJavadoc(javadoc, "@returns?\\s+.*"); copy = stripLinesWithTagFromJavadoc(copy, "@param(?:eter)?\\s+.*"); copy = stripSectionsFromJavadoc(copy); return new String[] {copy, javadoc}; } }, SETTER { @Override public String[] split(String javadoc) { return splitForSetters(javadoc, "SETTER"); } }, WITHER { @Override public String[] split(String javadoc) { return splitForSetters(javadoc, "WITHER"); } }; private static String[] splitForSetters(String javadoc, String sectionName) { // step 1: Check if there is a 'SETTER' section. If yes, that becomes the new one and we strip that from the original. String[] out = splitJavadocOnSectionIfPresent(javadoc, sectionName); if (out != null) return out; // failing that, create a copy, but strip @param from the original and @return from the copy. String copy = javadoc; javadoc = stripLinesWithTagFromJavadoc(javadoc, "@param(?:eter)?\\s+.*"); copy = stripLinesWithTagFromJavadoc(copy, "@returns?\\s+.*"); copy = stripSectionsFromJavadoc(copy); return new String[] {copy, javadoc}; } /** Splits the javadoc into the section to be copied (ret[0]) and the section to replace the original with (ret[1]) */ public String[] split(String javadoc) { return new String[] {javadoc, javadoc}; } } /** * 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) { if (copyMode == null) copyMode = CopyJavadoc.VERBATIM; try { JCCompilationUnit cu = ((JCCompilationUnit) from.top().get()); Object dc = Javac.getDocComments(cu); if (dc instanceof Map) { copyJavadoc_jdk6_7(from, to, copyMode, dc); } else if (Javac.instanceOfDocCommentTable(dc)) { CopyJavadoc_8.copyJavadoc(from, to, copyMode, dc); } } catch (Exception ignore) {} } private static final Pattern FIND_RETURN = Pattern.compile("^\\s*\\**\\s*@returns?\\s+.*$", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE); static String addReturnsThisIfNeeded(String in) { if (FIND_RETURN.matcher(in).find()) return in; return addJavadocLine(in, "@return this"); } static String addJavadocLine(String in, String line) { if (in.endsWith("\n")) return in + line + "\n"; return in + "\n" + line; } private static class CopyJavadoc_8 { static void copyJavadoc(JavacNode from, JCTree to, CopyJavadoc copyMode, Object dc) { DocCommentTable dct = (DocCommentTable) dc; Comment javadoc = dct.getComment(from.get()); if (javadoc != null) { String[] filtered = copyMode.split(javadoc.getText()); if (copyMode == CopyJavadoc.SETTER && shouldReturnThis(from)) { filtered[0] = addReturnsThisIfNeeded(filtered[0]); } dct.putComment(to, createJavadocComment(filtered[0], from)); dct.putComment(from.get(), createJavadocComment(filtered[1], from)); } } private static Comment createJavadocComment(final String text, final JavacNode field) { return new Comment() { @Override public String getText() { return text; } @Override public int getSourcePos(int index) { return -1; } @Override public CommentStyle getStyle() { return CommentStyle.JAVADOC; } @Override public boolean isDeprecated() { return text.contains("@deprecated") && field.getKind() == Kind.FIELD && isFieldDeprecated(field); } }; } } @SuppressWarnings({"unchecked", "all"}) private static void copyJavadoc_jdk6_7(JavacNode from, JCTree to, CopyJavadoc copyMode, Object dc) { Map docComments = (Map) dc; String javadoc = docComments.get(from.get()); if (javadoc != null) { String[] filtered = copyMode.split(javadoc); if (copyMode == CopyJavadoc.SETTER && shouldReturnThis(from)) { filtered[0] = addReturnsThisIfNeeded(filtered[0]); } docComments.put(to, filtered[0]); docComments.put(from.get(), filtered[1]); } } }