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

com.develhack.lombok.javac.handlers.feature.AbstractFeatureHandler Maven / Gradle / Ivy

package com.develhack.lombok.javac.handlers.feature;

import static com.sun.tools.javac.code.Flags.*;
import static lombok.javac.Javac.*;

import java.lang.annotation.Annotation;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;

import lombok.ConfigurationKeys;
import lombok.core.AnnotationValues;
import lombok.core.AnnotationValues.AnnotationValueDecodeFail;
import lombok.javac.Javac;
import lombok.javac.JavacNode;
import lombok.javac.handlers.HandleEqualsAndHashCode;
import lombok.javac.handlers.HandleToString;
import lombok.javac.handlers.JavacHandlerUtil;
import lombok.javac.handlers.JavacHandlerUtil.FieldAccess;

import com.develhack.Conditions;
import com.develhack.annotation.feature.Access;
import com.develhack.annotation.feature.Equatable;
import com.develhack.annotation.feature.ExcludedFrom;
import com.develhack.annotation.feature.Meta;
import com.develhack.annotation.feature.Stringable;
import com.develhack.lombok.NameResolver;
import com.develhack.lombok.javac.handlers.AbstractJavacHandler;
import com.sun.source.tree.Tree.Kind;
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.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.JCLiteral;
import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
import com.sun.tools.javac.tree.JCTree.JCModifiers;
import com.sun.tools.javac.tree.JCTree.JCNewArray;
import com.sun.tools.javac.tree.JCTree.JCStatement;
import com.sun.tools.javac.tree.JCTree.JCTypeParameter;
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.ListBuffer;
import com.sun.tools.javac.util.Name;

public abstract class AbstractFeatureHandler extends AbstractJavacHandler {

	public static final int PRIORITY = Integer.MIN_VALUE;

	public AbstractFeatureHandler(Class annotationType) {
		super(annotationType);
	}

	protected int toModifier(Access access) {
		switch (access) {
			case DEFAULT:
				return 0;
			case PUBLIC:
				return PUBLIC;
			case PROTECTED:
				return PROTECTED;
			case PRIVATE:
				return PRIVATE;
			default:
				throw new AssertionError(access.toString());
		}
	}

	protected boolean argumentTypesEquals(JCMethodDecl method, List arguments) {

		if (Conditions.isEmpty(method.getParameters())) {
			if (!Conditions.isEmpty(arguments)) return false;
			return true;
		}

		if (Conditions.isEmpty(arguments)) return false;
		if (method.getParameters().size() != arguments.size()) return false;

		Iterator argumentItr = method.getParameters().iterator();
		for (JCVariableDecl argument : arguments) {
			if (!argument.vartype.toString().equals(argumentItr.next().vartype.toString())) {
				return false;
			}
		}
		return true;
	}

	protected boolean isExcludedFrom(JCVariableDecl field, Class feature) {

		AnnotationValues excludeFrom = findAnnotationValues(ExcludedFrom.class, field.mods.annotations);
		if (excludeFrom == null) return false;

		try {
			for (Class specifiedFeature : excludeFrom.getInstance().value()) {
				if (feature.equals(specifiedFeature)) return true;
			}
		} catch (AnnotationValueDecodeFail e) {}

		return false;
	}

	protected JCMethodDecl findConstructor(List fieldsToBeInitialize) {

		if (typeNode == null) return null;
		JCClassDecl clazz = (JCClassDecl) typeNode.get();

		for (JCTree child : clazz.defs) {

			if (child.getKind() != Kind.METHOD) continue;

			JCMethodDecl method = (JCMethodDecl) child;
			if (!isConstructor(method)) continue;

			if (argumentTypesEquals(method, fieldsToBeInitialize)) return method;
		}

		return null;
	}

	protected List findFields(int requiredModifiers, int excludedModifiers,
			Class excludeFeature, boolean notInitializedOnly) {

		if (typeNode == null) return null;
		JCClassDecl clazz = (JCClassDecl) typeNode.get();

		ListBuffer fields = new ListBuffer();
		for (JCTree child : clazz.defs) {

			if (child.getKind() != Kind.VARIABLE) continue;

			JCVariableDecl field = (JCVariableDecl) child;
			if ((field.getModifiers().flags & requiredModifiers) != requiredModifiers) continue;
			if ((field.getModifiers().flags & excludedModifiers) != 0) continue;
			if (notInitializedOnly && field.getInitializer() != null) continue;
			if (isExcludedFrom(field, excludeFeature)) continue;

			fields.append(field);
		}

		return fields.toList();
	}

	protected JCExpression findSuperInterface(Class interfaceType, List superInterfaces) {

		if (Conditions.isEmpty(superInterfaces)) return null;

		String interfaceName = interfaceType.getSimpleName();
		for (JCExpression superInterface : superInterfaces) {
			if (getLastToken(superInterface).equals(interfaceName)) return superInterface;
		}

		return null;
	}

	protected List findExcludeFields(Class feature) {

		if (typeNode == null) return null;
		JCClassDecl clazz = (JCClassDecl) typeNode.get();

		ArrayList excludes = new ArrayList();
		for (JCTree child : clazz.defs) {

			if (child.getKind() != com.sun.source.tree.Tree.Kind.VARIABLE) continue;

			JCVariableDecl field = (JCVariableDecl) child;

			if (isExcludedFrom(field, feature)) excludes.add(field.name.toString());
		}

		return List.from(excludes.toArray(new String[excludes.size()]));
	}

	protected JCMethodDecl generateConstructor(List fieldsToBeInitialize, int modifiers) {

		List typeParameters = List.nil();
		ListBuffer parameters = new ListBuffer();
		List thrown = List.nil();
		ListBuffer statements = new ListBuffer();
		for (JCVariableDecl fieldToBeInitialize : fieldsToBeInitialize) {

			String argumentName = NameResolver.resolvePropertyName(sourceNode.getAst(), fieldToBeInitialize.name.toString());

			JCExpression argumentReference = maker.Ident(sourceNode.toName(argumentName));
			JCExpression fieldReference = generateFieldReference(fieldToBeInitialize);

			JCVariableDecl argument = maker.VarDef(maker.Modifiers(Flags.PARAMETER), sourceNode.toName(argumentName),
					fieldToBeInitialize.vartype, null);
			parameters.append(argument);

			JCAssign assignment = maker.Assign(fieldReference, argumentReference);
			statements.append(maker.Exec(assignment));
		}

		return maker.MethodDef(maker.Modifiers(modifiers), sourceNode.toName(""), null, typeParameters,
				parameters.toList(), thrown, maker.Block(0, statements.toList()), null);
	}

	protected JCMethodDecl generateGetter(JCVariableDecl field, long modifiers) {

		String getterName = NameResolver.resolveGetterName(sourceNode.getAst(), field.name.toString(), isBoolean(field));

		JCExpression fieldReference = generateFieldReference(field);
		JCStatement returnStatement = maker.Return(fieldReference);

		JCModifiers mods = maker.Modifiers(modifiers, List. nil());
		Name name = sourceNode.toName(getterName);
		JCExpression returnType = field.vartype;
		List typeParameters = List.nil();
		List parameters = List.nil();
		List thrown = List.nil();
		JCBlock body = maker.Block(0, List.of(returnStatement));
		JCExpression defaultValue = null;

		return maker.MethodDef(mods, name, returnType, typeParameters, parameters, thrown, body, defaultValue);
	}

	protected JCMethodDecl generateSetter(JCVariableDecl field, long modifiers) {

		if (typeNode == null) return null;

		String setterName = NameResolver.resolveSetterName(sourceNode.getAst(), field.name.toString(), isBoolean(field));
		String argumentName = NameResolver.resolvePropertyName(sourceNode.getAst(), field.name.toString());

		JCExpression argumentReference = maker.Ident(sourceNode.toName(argumentName));
		JCExpression fieldReference = generateFieldReference(field);

		JCAssign assignment = maker.Assign(fieldReference, argumentReference);

		JCVariableDecl argument = maker.VarDef(maker.Modifiers(Flags.PARAMETER), sourceNode.toName(argumentName),
				field.vartype, null);;

		JCModifiers mods = maker.Modifiers(modifiers | Flags.PARAMETER, List. nil());
		Name name = sourceNode.toName(setterName);
		JCExpression returnType = maker.Type(Javac.createVoidType(maker, CTC_VOID));
		List typeParameters = List.nil();
		List parameters = List.of(argument);
		List thrown = List.nil();
		JCBlock body = maker.Block(0, List. of(maker.Exec(assignment)));
		JCExpression defaultValue = null;

		return maker.MethodDef(mods, name, returnType, typeParameters, parameters, thrown, body, defaultValue);
	}

	protected JCExpression generateFieldReference(JCVariableDecl field) {

		JCExpression fieldRef = null;
		if ((field.mods.flags & Flags.STATIC) != 0) {
			fieldRef = generateNameReference(typeNode.getName(), field.name.toString());
		} else {
			fieldRef = maker.Select(maker.Ident(sourceNode.toName("this")), field.name);
		}

		return fieldRef;
	}

	protected void supplementFinalModifier() {

		if (typeNode == null) return;

		JCClassDecl clazz = (JCClassDecl) typeNode.get();
		clazz.mods.flags |= FINAL;

		JavacNode modsNode = sourceNode.getNodeFor(clazz.mods);
		if (modsNode == null) return;

		modsNode.getAst().setChanged();
	}

	protected void supplementConstructor(Access access, List fieldsToBeInitialize) {

		if (typeNode == null) return;

		int modifiers = toModifier(access);

		JCMethodDecl constructor = findConstructor(fieldsToBeInitialize);
		if (constructor != null) {
			if (access == Access.NONE || (constructor.mods.flags & modifiers) != modifiers) {
				JavacNode constructorNode = sourceNode.getNodeFor(constructor);
				if (constructorNode != null) {
					constructorNode.addWarning(String.format("conflicted with the %s.", source));
				}
			}
			return;
		}

		constructor = generateConstructor(fieldsToBeInitialize, modifiers);

		injectMethod(recursiveSetGeneratedBy(constructor));

		StringBuilder constructorDocComment = new StringBuilder();

		String constructorDocCommentTemplate = sourceNode.getAst().readConfiguration(
				ConfigurationKeys.CONSTRUCTOR_COMMENT_TEMPLATE);
		if (constructorDocCommentTemplate != null) {
			JCClassDecl clazz = ((JCClassDecl) typeNode.get());
			Object[] docCommentArgs = buildJavadocCommentArgments(clazz, clazz.name.toString(), clazz.mods.annotations);
			constructorDocComment.append(MessageFormat.format(constructorDocCommentTemplate, docCommentArgs));
			constructorDocComment.append('\n');
		}
		for (JCVariableDecl fieldToBeInitialize : fieldsToBeInitialize) {
			String fieldComment = getJavadocComment(fieldToBeInitialize);
			if (fieldComment == null) continue;
			String argumentName = NameResolver.resolvePropertyName(sourceNode.getAst(), fieldToBeInitialize.name.toString());
			constructorDocComment.append("@param ");
			constructorDocComment.append(argumentName);
			constructorDocComment.append(' ');
			constructorDocComment.append(fieldComment);
			constructorDocComment.append("\n");
		}
		if (constructorDocComment.length() != 0) {
			setJavadocComment(constructor, constructorDocComment.toString());
		}
	}

	protected void supplementUncallableConstructor() {

		if (typeNode == null) return;

		JavacNode defaultConstructorNode = null;
		for (JavacNode child : typeNode.down()) {
			if (child.getKind() != lombok.core.AST.Kind.METHOD) continue;
			JCMethodDecl method = (JCMethodDecl) child.get();
			if (!isConstructor(method)) continue;
			if ((method.mods.flags & GENERATEDCONSTR) != 0) {
				defaultConstructorNode = child;
				break;
			}
			if ((method.mods.flags & PRIVATE) == 0) {
				child.addWarning(String.format("class annotated by @%s must not have callable constructor.",
						getAnnotationName()));
				continue;
			}
		}

		if (defaultConstructorNode == null) {
			return;
		}

		JCExpression assertionError = maker.NewClass(null, List. nil(),
				generateNameReference(AssertionError.class.getName()), List. nil(), null);

		JCMethodDecl defaultConstructor = (JCMethodDecl) defaultConstructorNode.get();
		defaultConstructor.mods = maker.Modifiers(Flags.PRIVATE);
		defaultConstructor.typarams = List.nil();
		defaultConstructor.params = List.nil();
		defaultConstructor.thrown = List.nil();
		defaultConstructor.body = maker.Block(0, List. of(maker.Throw(assertionError)));

		recursiveSetGeneratedBy(defaultConstructor);
	}

	protected void supplementGetter(JCVariableDecl field, Access access) {

		if (typeNode == null) return;

		JCMethodDecl getter = findGetter(field);
		if (access == Access.NONE) {
			if (getter != null) {
				sourceNode.getNodeFor(getter).addWarning(String.format("conflicted with the %s.", source));
			}
			return;
		}

		int modifiers = toModifier(access) | (int) (field.mods.flags & STATIC);

		if (getter == null) {
			getter = generateGetter(field, modifiers);
			if (getter == null) return;

			injectMethod(recursiveSetGeneratedBy(getter));

			String argumentName = NameResolver.resolvePropertyName(sourceNode.getAst(), field.name.toString());
			Object[] docCommentArgs = buildJavadocCommentArgments(field, argumentName, field.mods.annotations);
			String getterDocCommentTemplate = sourceNode.getAst().readConfiguration(ConfigurationKeys.GETTER_COMMENT_TEMPLATE);
			StringBuilder getterDocComment = new StringBuilder();
			if (getterDocCommentTemplate != null) {
				getterDocComment.append(getterDocCommentTemplate);
				getterDocComment.append('\n');
			}
			if (docCommentArgs[1] != null) {
				getterDocComment.append("@return {1}");
			}
			if (getterDocComment.length() != 0) {
				setJavadocComment(getter, MessageFormat.format(getterDocComment.toString(), docCommentArgs));
			}
			return;
		}

		if (isAbstract(getter)) {
			sourceNode.addWarning(String.format("abstract getter of '%s' already exists.", field.name));
			return;
		}

		if (!modifiersMatches(getter.mods.flags, modifiers, STATIC, PUBLIC, PROTECTED, PRIVATE)) {
			sourceNode.getNodeFor(getter).addWarning(String.format("conflicted with the %s.", source));
			return;
		}
	}

	protected void supplementSetter(JCVariableDecl field, Access access) {

		if (typeNode == null) return;

		if ((field.mods.flags & FINAL) != 0) {
			return;
		}

		JCMethodDecl setter = findSetter(field);
		if (access == Access.NONE) {
			if (setter != null) {
				sourceNode.getNodeFor(setter).addWarning(String.format("conflicted with the %s.", source));
			}
			return;
		}

		int modifiers = toModifier(access) | (int) (field.mods.flags & STATIC);

		if (setter == null) {
			setter = generateSetter(field, modifiers);
			if (setter == null) return;

			injectMethod(recursiveSetGeneratedBy(setter));

			String argumentName = NameResolver.resolvePropertyName(sourceNode.getAst(), field.name.toString());
			Object[] docCommentArgs = buildJavadocCommentArgments(field, argumentName, field.mods.annotations);
			String setterDocCommentTemplate = sourceNode.getAst().readConfiguration(ConfigurationKeys.SETTER_COMMENT_TEMPLATE);
			StringBuilder setterDocComment = new StringBuilder();
			if (setterDocCommentTemplate != null) {
				setterDocComment.append(setterDocCommentTemplate);
				setterDocComment.append('\n');
			}
			if (docCommentArgs[1] != null) {
				setterDocComment.append("@param {0} {1}");
			}
			if (setterDocComment.length() != 0) {
				setJavadocComment(setter, MessageFormat.format(setterDocComment.toString(), docCommentArgs));
			}
			return;
		}

		if (isAbstract(setter)) {
			sourceNode.addWarning(String.format("abstract setter of '%s' already exists.", field.name));
			return;
		}

		if (!modifiersMatches(setter.mods.flags, modifiers, STATIC, PUBLIC, PROTECTED, PRIVATE)) {
			sourceNode.getNodeFor(setter).addWarning(String.format("conflicted with the %s.", source));
			return;
		}
	}

	protected void supplementSuperInterface(Class interfaceType) {

		if (typeNode == null) return;

		JCClassDecl clazz = (JCClassDecl) typeNode.get();

		if (findSuperInterface(interfaceType, clazz.implementing) != null) return;

		JCExpression typeReference = generateNameReference(interfaceType.getName());

		if (Conditions.isEmpty(clazz.implementing)) {
			clazz.implementing = List.of(typeReference);
		} else {
			clazz.implementing = clazz.implementing.append(typeReference);
		}
	}

	protected void supplementSuppressWaring(String suppressed) {

		if (typeNode == null) return;

		JCClassDecl clazz = (JCClassDecl) typeNode.get();

		JCAnnotation suppressWarnings = findAnnotation(SuppressWarnings.class, clazz.mods.annotations);

		if (suppressWarnings == null) {

			suppressWarnings = maker.Annotation(generateNameReference(SuppressWarnings.class.getName()),
					List. of(maker.Literal(suppressed)));
			recursiveSetGeneratedBy(suppressWarnings);

			if (Conditions.isEmpty(clazz.mods.annotations)) {
				clazz.mods.annotations = List.of(suppressWarnings);
			} else {
				clazz.mods.annotations = clazz.mods.annotations.append(suppressWarnings);
			}
			return;
		}

		for (JCExpression arg : suppressWarnings.args) {

			if (!(arg instanceof JCAssign)) continue;

			JCAssign assign = (JCAssign) arg;
			if (!assign.lhs.toString().equals("value")) continue;

			if (assign.rhs instanceof JCLiteral) {

				JCLiteral literal = (JCLiteral) assign.rhs;
				if (literal.value.equals(suppressed)) return;

				List args = List. of(maker.Literal(literal.value), maker.Literal(suppressed));
				assign.rhs = recursiveSetGeneratedBy(maker.NewArray(null, List. nil(), args));
				return;
			}

			if (!(assign.rhs instanceof JCNewArray)) continue;

			JCNewArray values = (JCNewArray) assign.rhs;
			for (JCExpression value : values.elems) {

				if (!(value instanceof JCLiteral)) continue;

				JCLiteral literal = (JCLiteral) value;
				if (literal.value.equals(suppressed)) return;
			}

			values.elems = values.elems.append(recursiveSetGeneratedBy(maker.Literal(suppressed)));
			return;
		}
	}

	protected void supplementEqualsAndHashCode(boolean evaluateSuperclass) {

		if (typeNode == null) return;

		List excludes = findExcludeFields(Equatable.class);
		List includes = null;
		List onParam = List.nil();

		new HandleEqualsAndHashCode().generateMethods(typeNode, sourceNode, excludes, includes, evaluateSuperclass, false,
				FieldAccess.ALWAYS_FIELD, onParam);
	}

	protected void supplementToString(boolean evaluateSuperclass) {

		if (typeNode == null) return;

		List excludes = findExcludeFields(Stringable.class);
		List includes = null;

		new HandleToString().generateToString(typeNode, sourceNode, excludes, includes, true, evaluateSuperclass, false,
				FieldAccess.ALWAYS_FIELD);
	}

	protected void injectMethod(JCMethodDecl method) {

		if (typeNode == null) return;

		JavacHandlerUtil.injectMethod(typeNode, method);

		typeNode.rebuild();
	}

	// Javac only.
	protected JCTree getExtendsClause(JCClassDecl clazz) {
		return Javac.getExtendsClause(clazz);
	}

	@SuppressWarnings("unchecked")
	protected String getJavadocComment(JCTree tree) {

		JavacNode node = sourceNode.getNodeFor(tree);
		if (node == null) return null;

		try {
			JCCompilationUnit compilationUnit = ((JCCompilationUnit) node.top().get());
			Object docComments = Javac.getDocComments(compilationUnit);
			if (docComments instanceof Map) {
				return ((Map) docComments).get(tree);
			}
			if (Javac.instanceOfDocCommentTable(docComments)) {
				return Java8Comment.get(docComments, tree);
			}
		} catch (Exception e) {}
		return null;
	}

	@SuppressWarnings("unchecked")
	protected void setJavadocComment(final JCTree tree, final String docComment) {

		JavacNode node = sourceNode.getNodeFor(tree);
		if (node == null) return;

		try {
			JCCompilationUnit compilationUnit = ((JCCompilationUnit) node.top().get());
			Object docComments = Javac.getDocComments(compilationUnit);
			if (docComments instanceof Map) {
				((Map) docComments).put(tree, docComment);
			}
			if (Javac.instanceOfDocCommentTable(docComments)) {
				Java8Comment.set(docComments, tree, docComment);
			}
		} catch (Exception e) {}
	}

	private Object[] buildJavadocCommentArgments(JCTree tree, String name, List annotations) {
		AnnotationValues metaValues = findAnnotationValues(Meta.class, annotations);
		Object[] docCommentArgs;
		if (metaValues == null) {
			docCommentArgs = new Object[] { name, getJavadocComment(tree) };
		} else {
			String[] additionalArgs = metaValues.getInstance().value();
			docCommentArgs = new Object[2 + additionalArgs.length];
			docCommentArgs[0] = name;
			docCommentArgs[1] = getJavadocComment(tree);
			System.arraycopy(additionalArgs, 0, docCommentArgs, 2, additionalArgs.length);
		}
		return docCommentArgs;
	}

	private static class Java8Comment {

		static String get(Object docComments, JCTree tree) {
			return ((DocCommentTable) docComments).getCommentText(tree);
		}

		static void set(Object docComments, final JCTree tree, final String docComment) {
			((DocCommentTable) docComments).putComment(tree, new Comment() {

				@Override
				public String getText() {
					return docComment;
				}

				@Override
				public int getSourcePos(int index) {
					return -1;
				}

				@Override
				public CommentStyle getStyle() {
					return CommentStyle.JAVADOC;
				}

				@Override
				public boolean isDeprecated() {
					return JavacHandlerUtil.nodeHasDeprecatedFlag(tree);
				}
			});
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy