diff options
author | Reinier Zwitserloot <r.zwitserloot@projectlombok.org> | 2021-04-16 06:40:35 +0200 |
---|---|---|
committer | Reinier Zwitserloot <r.zwitserloot@projectlombok.org> | 2021-04-16 06:40:35 +0200 |
commit | 415e303ac283df8d694b311f4f8df08f6e79cf0e (patch) | |
tree | 99c4e502fdb90db2e8e1e27ebf855b236ac41b59 /src/core | |
parent | 2e212de523407c8d9f4471fea573c6c70164513b (diff) | |
download | lombok-415e303ac283df8d694b311f4f8df08f6e79cf0e.tar.gz lombok-415e303ac283df8d694b311f4f8df08f6e79cf0e.tar.bz2 lombok-415e303ac283df8d694b311f4f8df08f6e79cf0e.zip |
[pr 2702] finishing the `@StandardException` feature.
* rewritten how it works a bit: Now compatible with parent exceptions that don't have the Throwable variants.
* rewritten how it works a bit: You can now provide the full constructor only; the rest will forward to it.
* fixing up style.
* rewrite the docs.
Diffstat (limited to 'src/core')
3 files changed, 242 insertions, 230 deletions
diff --git a/src/core/lombok/eclipse/handlers/HandleStandardException.java b/src/core/lombok/eclipse/handlers/HandleStandardException.java index e7f25edb..1a2542f4 100755 --- a/src/core/lombok/eclipse/handlers/HandleStandardException.java +++ b/src/core/lombok/eclipse/handlers/HandleStandardException.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2020 The Project Lombok Authors. + * Copyright (C) 2021 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 @@ -26,129 +26,138 @@ import lombok.ConfigurationKeys; import lombok.experimental.StandardException; import lombok.core.AST.Kind; import lombok.core.AnnotationValues; -import lombok.eclipse.Eclipse; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; import lombok.eclipse.handlers.EclipseHandlerUtil.*; import lombok.spi.Provides; import org.eclipse.jdt.internal.compiler.ast.*; -import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; +import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; import java.lang.reflect.Modifier; import java.util.*; import static lombok.core.handlers.HandlerUtil.handleFlagUsage; import static lombok.eclipse.Eclipse.ECLIPSE_DO_NOT_TOUCH_FLAG; -import static lombok.eclipse.Eclipse.pos; import static lombok.eclipse.handlers.EclipseHandlerUtil.*; @Provides public class HandleStandardException extends EclipseAnnotationHandler<StandardException> { - private static final String NAME = StandardException.class.getSimpleName(); - @Override public void handle(AnnotationValues<StandardException> annotation, Annotation ast, EclipseNode annotationNode) { handleFlagUsage(annotationNode, ConfigurationKeys.STANDARD_EXCEPTION_FLAG_USAGE, "@StandardException"); - + EclipseNode typeNode = annotationNode.up(); - if (!checkLegality(typeNode, annotationNode)) return; - - SuperParameter message = new SuperParameter("message", new SingleTypeReference("String".toCharArray(), pos(typeNode.get()))); - SuperParameter cause = new SuperParameter("cause", new SingleTypeReference("Throwable".toCharArray(), pos(typeNode.get()))); - - boolean skip = true; - generateConstructor( - typeNode, AccessLevel.PUBLIC, Collections.<SuperParameter>emptyList(), skip, annotationNode); - generateConstructor( - typeNode, AccessLevel.PUBLIC, Collections.singletonList(message), skip, annotationNode); - generateConstructor( - typeNode, AccessLevel.PUBLIC, Collections.singletonList(cause), skip, annotationNode); - generateConstructor( - typeNode, AccessLevel.PUBLIC, Arrays.asList(message, cause), skip, annotationNode); - } - - private static boolean checkLegality(EclipseNode typeNode, EclipseNode errorNode) { - TypeDeclaration typeDecl = null; - if (typeNode.get() instanceof TypeDeclaration) typeDecl = (TypeDeclaration) typeNode.get(); - int modifiers = typeDecl == null ? 0 : typeDecl.modifiers; - boolean notAClass = (modifiers & (ClassFileConstants.AccInterface | ClassFileConstants.AccAnnotation)) != 0; - - if (typeDecl == null || notAClass) { - errorNode.addError(HandleStandardException.NAME + " is only supported on a class or an enum."); - return false; + if (!isClass(typeNode)) { + annotationNode.addError("@StandardException is only supported on a class."); + return; } - return true; + AccessLevel access = annotation.getInstance().access(); + + generateNoArgsConstructor(typeNode, access, annotationNode); + generateMsgOnlyConstructor(typeNode, access, annotationNode); + generateCauseOnlyConstructor(typeNode, access, annotationNode); + generateFullConstructor(typeNode, access, annotationNode); } - - public void generateConstructor( - EclipseNode typeNode, AccessLevel level, List<SuperParameter> parameters, boolean skipIfConstructorExists, - EclipseNode sourceNode) { + + private void generateNoArgsConstructor(EclipseNode typeNode, AccessLevel level, EclipseNode source) { + if (hasConstructor(typeNode) != MemberExistsResult.NOT_EXISTS) return; + int pS = source.get().sourceStart, pE = source.get().sourceEnd; - generate(typeNode, level, parameters, skipIfConstructorExists, sourceNode); + ExplicitConstructorCall explicitCall = new ExplicitConstructorCall(ExplicitConstructorCall.This); + explicitCall.arguments = new Expression[] {new NullLiteral(pS, pE), new NullLiteral(pS, pE)}; + ConstructorDeclaration constructor = createConstructor(level, typeNode, false, false, source, explicitCall, null); + injectMethod(typeNode, constructor); } - public void generate( - EclipseNode typeNode, AccessLevel level, List<SuperParameter> parameters, boolean skipIfConstructorExists, - EclipseNode sourceNode) { - if (!(skipIfConstructorExists - && constructorExists(typeNode, parameters) != MemberExistsResult.NOT_EXISTS)) { - ConstructorDeclaration constr = createConstructor(level, typeNode, parameters, sourceNode); - injectMethod(typeNode, constr); - } + private void generateMsgOnlyConstructor(EclipseNode typeNode, AccessLevel level, EclipseNode source) { + if (hasConstructor(typeNode, String.class) != MemberExistsResult.NOT_EXISTS) return; + int pS = source.get().sourceStart, pE = source.get().sourceEnd; + long p = (long) pS << 32 | pE; + + ExplicitConstructorCall explicitCall = new ExplicitConstructorCall(ExplicitConstructorCall.This); + explicitCall.arguments = new Expression[] {new SingleNameReference(MESSAGE, p), new NullLiteral(pS, pE)}; + ConstructorDeclaration constructor = createConstructor(level, typeNode, true, false, source, explicitCall, null); + injectMethod(typeNode, constructor); } - + + private void generateCauseOnlyConstructor(EclipseNode typeNode, AccessLevel level, EclipseNode source) { + if (hasConstructor(typeNode, Throwable.class) != MemberExistsResult.NOT_EXISTS) return; + int pS = source.get().sourceStart, pE = source.get().sourceEnd; + long p = (long) pS << 32 | pE; + + ExplicitConstructorCall explicitCall = new ExplicitConstructorCall(ExplicitConstructorCall.This); + Expression causeNotNull = new EqualExpression(new SingleNameReference(CAUSE, p), new NullLiteral(pS, pE), OperatorIds.NOT_EQUAL); + MessageSend causeDotGetMessage = new MessageSend(); + causeDotGetMessage.sourceStart = pS; causeDotGetMessage.sourceEnd = pE; + causeDotGetMessage.receiver = new SingleNameReference(CAUSE, p); + causeDotGetMessage.selector = GET_MESSAGE; + Expression messageExpr = new ConditionalExpression(causeNotNull, causeDotGetMessage, new NullLiteral(pS, pE)); + explicitCall.arguments = new Expression[] {messageExpr, new SingleNameReference(CAUSE, p)}; + ConstructorDeclaration constructor = createConstructor(level, typeNode, false, true, source, explicitCall, null); + injectMethod(typeNode, constructor); + } + + private void generateFullConstructor(EclipseNode typeNode, AccessLevel level, EclipseNode source) { + if (hasConstructor(typeNode, String.class, Throwable.class) != MemberExistsResult.NOT_EXISTS) return; + int pS = source.get().sourceStart, pE = source.get().sourceEnd; + long p = (long) pS << 32 | pE; + + ExplicitConstructorCall explicitCall = new ExplicitConstructorCall(ExplicitConstructorCall.Super); + explicitCall.arguments = new Expression[] {new SingleNameReference(MESSAGE, p)}; + Expression causeNotNull = new EqualExpression(new SingleNameReference(CAUSE, p), new NullLiteral(pS, pE), OperatorIds.NOT_EQUAL); + MessageSend causeDotInitCause = new MessageSend(); + causeDotInitCause.sourceStart = pS; causeDotInitCause.sourceEnd = pE; + causeDotInitCause.receiver = new SuperReference(pS, pE); + causeDotInitCause.selector = INIT_CAUSE; + causeDotInitCause.arguments = new Expression[] {new SingleNameReference(CAUSE, p)}; + IfStatement ifs = new IfStatement(causeNotNull, causeDotInitCause, pS, pE); + ConstructorDeclaration constructor = createConstructor(level, typeNode, true, true, source, explicitCall, ifs); + injectMethod(typeNode, constructor); + } + /** * Checks if a constructor with the provided parameters exists under the type node. */ - public static MemberExistsResult constructorExists(EclipseNode node, List<SuperParameter> parameters) { + public static MemberExistsResult hasConstructor(EclipseNode node, Class<?>... paramTypes) { node = upToTypeNode(node); - SuperParameter[] parameterArray = parameters.toArray(new SuperParameter[0]); - + if (node != null && node.get() instanceof TypeDeclaration) { - TypeDeclaration typeDecl = (TypeDeclaration)node.get(); + TypeDeclaration typeDecl = (TypeDeclaration) node.get(); if (typeDecl.methods != null) for (AbstractMethodDeclaration def : typeDecl.methods) { if (def instanceof ConstructorDeclaration) { - if (!paramsMatch(node, def.arguments, parameterArray)) continue; + if ((def.bits & ASTNode.IsDefaultConstructor) != 0) continue; + if (!paramsMatch(node, def.arguments, paramTypes)) continue; return getGeneratedBy(def) == null ? MemberExistsResult.EXISTS_BY_USER : MemberExistsResult.EXISTS_BY_LOMBOK; } } } - + return MemberExistsResult.NOT_EXISTS; } - - private static boolean paramsMatch(EclipseNode node, Argument[] arguments, SuperParameter[] parameters) { - if (arguments == null) { - return parameters.length == 0; - } else if (arguments.length != parameters.length) { - return false; - } else { - for (int i = 0; i < parameters.length; i++) { - String fieldTypeName = Eclipse.toQualifiedName(parameters[i].type.getTypeName()); - String argTypeName = Eclipse.toQualifiedName(arguments[i].type.getTypeName()); - - if (!typeNamesMatch(node, fieldTypeName, argTypeName)) - return false; - } + + private static boolean paramsMatch(EclipseNode node, Argument[] a, Class<?>[] b) { + if (a == null) return b == null || b.length == 0; + if (b == null) return a.length == 0; + if (a.length != b.length) return false; + + for (int i = 0; i < a.length; i++) { + if (!typeMatches(b[i], node, a[i].type)) return false; } return true; } - - private static boolean typeNamesMatch(EclipseNode node, String a, String b) { - boolean isFqn = node.getImportListAsTypeResolver().typeMatches(node, a, b); - boolean reverseIsFqn = node.getImportListAsTypeResolver().typeMatches(node, b, a); - return isFqn || reverseIsFqn; - } - + private static final char[][] JAVA_BEANS_CONSTRUCTORPROPERTIES = new char[][] { "java".toCharArray(), "beans".toCharArray(), "ConstructorProperties".toCharArray() }; - public static Annotation[] createConstructorProperties(ASTNode source, Collection<SuperParameter> fields) { - if (fields.isEmpty()) return null; + private static final char[] MESSAGE = "message".toCharArray(), CAUSE = "cause".toCharArray(), GET_MESSAGE = "getMessage".toCharArray(), INIT_CAUSE = "initCause".toCharArray(); + + public static Annotation[] createConstructorProperties(ASTNode source, boolean msgParam, boolean causeParam) { + if (!msgParam && !causeParam) return null; int pS = source.sourceStart, pE = source.sourceEnd; long p = (long) pS << 32 | pE; long[] poss = new long[3]; Arrays.fill(poss, p); + QualifiedTypeReference constructorPropertiesType = new QualifiedTypeReference(JAVA_BEANS_CONSTRUCTORPROPERTIES, poss); setGeneratedBy(constructorPropertiesType, source); SingleMemberAnnotation ann = new SingleMemberAnnotation(constructorPropertiesType, pS); @@ -157,12 +166,16 @@ public class HandleStandardException extends EclipseAnnotationHandler<StandardEx ArrayInitializer fieldNames = new ArrayInitializer(); fieldNames.sourceStart = pS; fieldNames.sourceEnd = pE; - fieldNames.expressions = new Expression[fields.size()]; + fieldNames.expressions = new Expression[(msgParam && causeParam) ? 2 : 1]; int ctr = 0; - for (SuperParameter field : fields) { - char[] fieldName = field.name.toCharArray(); - fieldNames.expressions[ctr] = new StringLiteral(fieldName, pS, pE, 0); + if (msgParam) { + fieldNames.expressions[ctr] = new StringLiteral(MESSAGE, pS, pE, 0); + setGeneratedBy(fieldNames.expressions[ctr], source); + ctr++; + } + if (causeParam) { + fieldNames.expressions[ctr] = new StringLiteral(CAUSE, pS, pE, 0); setGeneratedBy(fieldNames.expressions[ctr], source); ctr++; } @@ -172,59 +185,55 @@ public class HandleStandardException extends EclipseAnnotationHandler<StandardEx setGeneratedBy(ann.memberValue, source); return new Annotation[] { ann }; } - - @SuppressWarnings("deprecation") public static ConstructorDeclaration createConstructor( - AccessLevel level, EclipseNode type, Collection<SuperParameter> parameters, EclipseNode sourceNode) { + + @SuppressWarnings("deprecation") public static ConstructorDeclaration createConstructor(AccessLevel level, EclipseNode typeNode, boolean msgParam, boolean causeParam, EclipseNode sourceNode, ExplicitConstructorCall explicitCall, Statement extra) { ASTNode source = sourceNode.get(); - TypeDeclaration typeDeclaration = ((TypeDeclaration) type.get()); - - boolean isEnum = (((TypeDeclaration) type.get()).modifiers & ClassFileConstants.AccEnum) != 0; - if (isEnum) level = AccessLevel.PRIVATE; - + TypeDeclaration typeDeclaration = ((TypeDeclaration) typeNode.get()); + int pS = source.sourceStart, pE = source.sourceEnd; + long p = (long) pS << 32 | pE; + boolean addConstructorProperties; - if (parameters.isEmpty()) { + if ((!msgParam && !causeParam) || isLocalType(typeNode)) { addConstructorProperties = false; } else { - Boolean v = type.getAst().readConfiguration(ConfigurationKeys.ANY_CONSTRUCTOR_ADD_CONSTRUCTOR_PROPERTIES); + Boolean v = typeNode.getAst().readConfiguration(ConfigurationKeys.ANY_CONSTRUCTOR_ADD_CONSTRUCTOR_PROPERTIES); addConstructorProperties = v != null ? v.booleanValue() : - Boolean.FALSE.equals(type.getAst().readConfiguration(ConfigurationKeys.ANY_CONSTRUCTOR_SUPPRESS_CONSTRUCTOR_PROPERTIES)); + Boolean.FALSE.equals(typeNode.getAst().readConfiguration(ConfigurationKeys.ANY_CONSTRUCTOR_SUPPRESS_CONSTRUCTOR_PROPERTIES)); } - ConstructorDeclaration constructor = new ConstructorDeclaration(((CompilationUnitDeclaration) type.top().get()).compilationResult); + ConstructorDeclaration constructor = new ConstructorDeclaration(((CompilationUnitDeclaration) typeNode.top().get()).compilationResult); constructor.modifiers = toEclipseModifier(level); constructor.selector = typeDeclaration.name; constructor.thrownExceptions = null; constructor.typeParameters = null; constructor.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; - constructor.bodyStart = constructor.declarationSourceStart = constructor.sourceStart = source.sourceStart; - constructor.bodyEnd = constructor.declarationSourceEnd = constructor.sourceEnd = source.sourceEnd; + constructor.bodyStart = constructor.declarationSourceStart = constructor.sourceStart = pS; + constructor.bodyEnd = constructor.declarationSourceEnd = constructor.sourceEnd = pE; constructor.arguments = null; List<Argument> params = new ArrayList<Argument>(); - List<Expression> superArgs = new ArrayList<Expression>(); - - for (SuperParameter fieldNode : parameters) { - char[] fieldName = fieldNode.name.toCharArray(); - long fieldPos = (((long) type.get().sourceStart) << 32) | type.get().sourceEnd; - Argument parameter = new Argument(fieldName, fieldPos, copyType(fieldNode.type, source), Modifier.FINAL); + + if (msgParam) { + TypeReference typeRef = new QualifiedTypeReference(TypeConstants.JAVA_LANG_STRING, new long[] {p, p, p}); + Argument parameter = new Argument(MESSAGE, p, typeRef, Modifier.FINAL); params.add(parameter); - superArgs.add(new SingleNameReference(fieldName, 0)); } - - // Super constructor call - constructor.constructorCall = new ExplicitConstructorCall(ExplicitConstructorCall.Super); - constructor.constructorCall.arguments = superArgs.toArray(new Expression[0]); - constructor.constructorCall.sourceStart = source.sourceStart; - constructor.constructorCall.sourceEnd = source.sourceEnd; - + if (causeParam) { + TypeReference typeRef = new QualifiedTypeReference(TypeConstants.JAVA_LANG_THROWABLE, new long[] {p, p, p}); + Argument parameter = new Argument(CAUSE, p, typeRef, Modifier.FINAL); + params.add(parameter); + } + + explicitCall.sourceStart = pS; + explicitCall.sourceEnd = pE; + constructor.constructorCall = explicitCall; + constructor.statements = extra != null ? new Statement[] {extra} : null; constructor.arguments = params.isEmpty() ? null : params.toArray(new Argument[0]); Annotation[] constructorProperties = null; - if (addConstructorProperties && !isLocalType(type)) constructorProperties = createConstructorProperties(source, parameters); - constructor.annotations = copyAnnotations(source, - constructorProperties); - + if (addConstructorProperties) constructorProperties = createConstructorProperties(source, msgParam, causeParam); + constructor.annotations = copyAnnotations(source, constructorProperties); constructor.traverse(new SetGeneratedByVisitor(source), typeDeclaration.scope); return constructor; } @@ -235,14 +244,4 @@ public class HandleStandardException extends EclipseAnnotationHandler<StandardEx if (kind == Kind.TYPE) return isLocalType(type.up()); return true; } - - private static class SuperParameter { - private final String name; - private final TypeReference type; - - private SuperParameter(String name, TypeReference type) { - this.name = name; - this.type = type; - } - } } diff --git a/src/core/lombok/experimental/StandardException.java b/src/core/lombok/experimental/StandardException.java index 9f8a4e65..b04ac2ee 100644 --- a/src/core/lombok/experimental/StandardException.java +++ b/src/core/lombok/experimental/StandardException.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2017 The Project Lombok Authors. + * Copyright (C) 2021 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 @@ -26,9 +26,25 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import lombok.AccessLevel; + /** + * Put on any class that extends some {@code java.lang.Throwable} type to add the 4 common exception constructors. + * + * Specifically, all 4 constructors derived from the combinatorial explosion of {@code String message} and {@code Throwable cause}. + * You may write any or all of these 4 constructors by hand; lombok will only generate the missing ones. + * <p> + * All but the full {@code (String message, Throwable cause)} constructor are implemented as a {@code this(msg, cause)} call; it is therefore + * possibly to write code to run on construction by writing just the {@code (String message, Throwable cause)} constructor. */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.SOURCE) public @interface StandardException { + /** + * Sets the access level of the generated constuctors. By default, generated constructors are {@code public}. + * Note: This does nothing if you write your own constructors (we won't change their access levels). + * + * @return The constructors will be generated with this access modifier. + */ + AccessLevel access() default lombok.AccessLevel.PUBLIC; } diff --git a/src/core/lombok/javac/handlers/HandleStandardException.java b/src/core/lombok/javac/handlers/HandleStandardException.java index 598f1aa7..39913a90 100644 --- a/src/core/lombok/javac/handlers/HandleStandardException.java +++ b/src/core/lombok/javac/handlers/HandleStandardException.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2019 The Project Lombok Authors. + * Copyright (C) 2021 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 @@ -42,174 +42,171 @@ import lombok.javac.handlers.JavacHandlerUtil.*; import lombok.spi.Provides; import static lombok.core.handlers.HandlerUtil.handleFlagUsage; -import static lombok.javac.Javac.CTC_VOID; +import static lombok.javac.Javac.*; import static lombok.javac.handlers.JavacHandlerUtil.*; @Provides public class HandleStandardException extends JavacAnnotationHandler<StandardException> { - private static final String NAME = StandardException.class.getSimpleName(); - @Override public void handle(AnnotationValues<StandardException> annotation, JCAnnotation ast, JavacNode annotationNode) { handleFlagUsage(annotationNode, ConfigurationKeys.STANDARD_EXCEPTION_FLAG_USAGE, "@StandardException"); deleteAnnotationIfNeccessary(annotationNode, StandardException.class); + deleteImportFromCompilationUnit(annotationNode, "lombok.AccessLevel"); JavacNode typeNode = annotationNode.up(); - if (!checkLegality(typeNode, annotationNode)) return; - - SuperParameter messageField = new SuperParameter("message", typeNode.getSymbolTable().stringType); - SuperParameter causeField = new SuperParameter("cause", typeNode.getSymbolTable().throwableType); - - boolean skip = true; - generateConstructor(typeNode, AccessLevel.PUBLIC, List.<SuperParameter>nil(), skip, annotationNode); - generateConstructor(typeNode, AccessLevel.PUBLIC, List.of(messageField), skip, annotationNode); - generateConstructor(typeNode, AccessLevel.PUBLIC, List.of(causeField), skip, annotationNode); - generateConstructor(typeNode, AccessLevel.PUBLIC, List.of(messageField, causeField), skip, annotationNode); - } - - private static boolean checkLegality(JavacNode typeNode, JavacNode errorNode) { - JCClassDecl typeDecl = null; - if (typeNode.get() instanceof JCClassDecl) typeDecl = (JCClassDecl) typeNode.get(); - long modifiers = typeDecl == null ? 0 : typeDecl.mods.flags; - boolean notAClass = (modifiers & (Flags.INTERFACE | Flags.ANNOTATION)) != 0; - - if (typeDecl == null || notAClass) { - errorNode.addError(NAME + " is only supported on a class or an enum."); - return false; + + if (!isClass(typeNode)) { + annotationNode.addError("@StandardException is only supported on a class."); + return; } - return true; + AccessLevel access = annotation.getInstance().access(); + if (access == null) access = AccessLevel.PUBLIC; + if (access == AccessLevel.NONE) { + annotationNode.addError("AccessLevel.NONE is not valid here"); + access = AccessLevel.PUBLIC; + } + + generateNoArgsConstructor(typeNode, access, annotationNode); + generateMsgOnlyConstructor(typeNode, access, annotationNode); + generateCauseOnlyConstructor(typeNode, access, annotationNode); + generateFullConstructor(typeNode, access, annotationNode); } - - public void generateConstructor(JavacNode typeNode, AccessLevel level, List<SuperParameter> fields, - boolean skipIfConstructorExists, JavacNode source) { - generate(typeNode, level, fields, skipIfConstructorExists, source); + + private void generateNoArgsConstructor(JavacNode typeNode, AccessLevel level, JavacNode source) { + if (hasConstructor(typeNode) != MemberExistsResult.NOT_EXISTS) return; + JavacTreeMaker maker = typeNode.getTreeMaker(); + + List<JCExpression> args = List.<JCExpression>of(maker.Literal(CTC_BOT, null), maker.Literal(CTC_BOT, null)); + JCStatement thisCall = maker.Exec(maker.Apply(List.<JCExpression>nil(), maker.Ident(typeNode.toName("this")), args)); + JCMethodDecl constr = createConstructor(level, typeNode, false, false, source, List.of(thisCall)); + injectMethod(typeNode, constr, List.<Type>nil(), Javac.createVoidType(typeNode.getSymbolTable(), CTC_VOID)); } - - private void generate(JavacNode typeNode, AccessLevel level, List<SuperParameter> fields, boolean skipIfConstructorExists, - JavacNode source) { - ListBuffer<Type> argTypes = new ListBuffer<Type>(); - for (SuperParameter field : fields) { - Type mirror = field.type; - if (mirror == null) { - argTypes = null; - break; - } - argTypes.append(mirror); - } - List<Type> argTypes_ = argTypes == null ? null : argTypes.toList(); - - if (!(skipIfConstructorExists && constructorExists(typeNode, fields) != MemberExistsResult.NOT_EXISTS)) { - JCMethodDecl constr = createConstructor(level, typeNode, fields, source); - injectMethod(typeNode, constr, argTypes_, Javac.createVoidType(typeNode.getSymbolTable(), CTC_VOID)); - } + + private void generateMsgOnlyConstructor(JavacNode typeNode, AccessLevel level, JavacNode source) { + if (hasConstructor(typeNode, String.class) != MemberExistsResult.NOT_EXISTS) return; + JavacTreeMaker maker = typeNode.getTreeMaker(); + + List<JCExpression> args = List.<JCExpression>of(maker.Ident(typeNode.toName("message")), maker.Literal(CTC_BOT, null)); + JCStatement thisCall = maker.Exec(maker.Apply(List.<JCExpression>nil(), maker.Ident(typeNode.toName("this")), args)); + JCMethodDecl constr = createConstructor(level, typeNode, true, false, source, List.of(thisCall)); + injectMethod(typeNode, constr, List.<Type>of(typeNode.getSymbolTable().stringType), Javac.createVoidType(typeNode.getSymbolTable(), CTC_VOID)); } - - public static MemberExistsResult constructorExists(JavacNode node, List<SuperParameter> parameters) { + + private void generateCauseOnlyConstructor(JavacNode typeNode, AccessLevel level, JavacNode source) { + if (hasConstructor(typeNode, Throwable.class) != MemberExistsResult.NOT_EXISTS) return; + JavacTreeMaker maker = typeNode.getTreeMaker(); + Name causeName = typeNode.toName("cause"); + + JCExpression causeDotGetMessage = maker.Apply(List.<JCExpression>nil(), maker.Select(maker.Ident(causeName), typeNode.toName("getMessage")), List.<JCExpression>nil()); + JCExpression msgExpression = maker.Conditional(maker.Binary(CTC_NOT_EQUAL, maker.Ident(causeName), maker.Literal(CTC_BOT, null)), causeDotGetMessage, maker.Literal(CTC_BOT, null)); + + List<JCExpression> args = List.<JCExpression>of(msgExpression, maker.Ident(causeName)); + JCStatement thisCall = maker.Exec(maker.Apply(List.<JCExpression>nil(), maker.Ident(typeNode.toName("this")), args)); + JCMethodDecl constr = createConstructor(level, typeNode, false, true, source, List.of(thisCall)); + injectMethod(typeNode, constr, List.<Type>of(typeNode.getSymbolTable().throwableType), Javac.createVoidType(typeNode.getSymbolTable(), CTC_VOID)); + } + + private void generateFullConstructor(JavacNode typeNode, AccessLevel level, JavacNode source) { + if (hasConstructor(typeNode, String.class, Throwable.class) != MemberExistsResult.NOT_EXISTS) return; + JavacTreeMaker maker = typeNode.getTreeMaker(); + + Name causeName = typeNode.toName("cause"); + Name superName = typeNode.toName("super"); + + List<JCExpression> args = List.<JCExpression>of(maker.Ident(typeNode.toName("message"))); + JCStatement superCall = maker.Exec(maker.Apply(List.<JCExpression>nil(), maker.Ident(superName), args)); + JCExpression causeNotNull = maker.Binary(CTC_NOT_EQUAL, maker.Ident(causeName), maker.Literal(CTC_BOT, null)); + JCStatement initCauseCall = maker.Exec(maker.Apply(List.<JCExpression>nil(), maker.Select(maker.Ident(superName), typeNode.toName("initCause")), List.<JCExpression>of(maker.Ident(causeName)))); + JCStatement initCause = maker.If(causeNotNull, initCauseCall, null); + JCMethodDecl constr = createConstructor(level, typeNode, true, true, source, List.of(superCall, initCause)); + injectMethod(typeNode, constr, List.<Type>of(typeNode.getSymbolTable().stringType, typeNode.getSymbolTable().throwableType), Javac.createVoidType(typeNode.getSymbolTable(), CTC_VOID)); + } + + private static MemberExistsResult hasConstructor(JavacNode node, Class<?>... paramTypes) { 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("<init>") && (md.mods.flags & Flags.GENERATEDCONSTR) == 0) { - if (!paramsMatch(md.params, parameters)) continue; + if (!paramsMatch(node, md.params, paramTypes)) continue; return getGeneratedBy(def) == null ? MemberExistsResult.EXISTS_BY_USER : MemberExistsResult.EXISTS_BY_LOMBOK; } } } } - + return MemberExistsResult.NOT_EXISTS; } - - private static boolean paramsMatch(List<JCVariableDecl> params, List<SuperParameter> superParams) { - if (params == null) { - return superParams.size() == 0; - } else if (params.size() != superParams.size()) { - return false; - } else { - for (int i = 0; i < superParams.size(); i++) { - SuperParameter field = superParams.get(i); - JCVariableDecl param = params.get(i); - if (!param.getType().type.equals(field.type)) - return false; - } + + private static boolean paramsMatch(JavacNode node, List<JCVariableDecl> a, Class<?>[] b) { + if (a == null) return b == null || b.length == 0; + if (b == null) return a.size() == 0; + if (a.size() != b.length) return false; + + for (int i = 0; i < a.size(); i++) { + JCVariableDecl param = a.get(i); + Class<?> c = b[i]; + if (!typeMatches(c, node, param.vartype)) return false; } - + return true; } - public static void addConstructorProperties(JCModifiers mods, JavacNode node, List<SuperParameter> fields) { - if (fields.isEmpty()) return; + private static void addConstructorProperties(JCModifiers mods, JavacNode node, boolean msgParam, boolean causeParam) { + if (!msgParam && !causeParam) return; JavacTreeMaker maker = node.getTreeMaker(); JCExpression constructorPropertiesType = chainDots(node, "java", "beans", "ConstructorProperties"); ListBuffer<JCExpression> fieldNames = new ListBuffer<JCExpression>(); - for (SuperParameter field : fields) { - Name fieldName = node.toName(field.name); - fieldNames.append(maker.Literal(fieldName.toString())); - } + if (msgParam) fieldNames.append(maker.Literal("message")); + if (causeParam) fieldNames.append(maker.Literal("cause")); JCExpression fieldNamesArray = maker.NewArray(null, List.<JCExpression>nil(), fieldNames.toList()); JCAnnotation annotation = maker.Annotation(constructorPropertiesType, List.of(fieldNamesArray)); mods.annotations = mods.annotations.append(annotation); } - @SuppressWarnings("deprecation") public static JCMethodDecl createConstructor(AccessLevel level, JavacNode typeNode, - List<SuperParameter> fieldsToParam, JavacNode source) { + @SuppressWarnings("deprecation") private static JCMethodDecl createConstructor(AccessLevel level, JavacNode typeNode, boolean msgParam, boolean causeParam, JavacNode source, List<JCStatement> statements) { JavacTreeMaker maker = typeNode.getTreeMaker(); - boolean isEnum = (((JCClassDecl) typeNode.get()).mods.flags & Flags.ENUM) != 0; - if (isEnum) level = AccessLevel.PRIVATE; - boolean addConstructorProperties; - if (fieldsToParam.isEmpty()) { + if ((!msgParam && !causeParam) || isLocalType(typeNode) || !LombokOptionsFactory.getDelombokOptions(typeNode.getContext()).getFormatPreferences().generateConstructorProperties()) { addConstructorProperties = false; } else { Boolean v = typeNode.getAst().readConfiguration(ConfigurationKeys.ANY_CONSTRUCTOR_ADD_CONSTRUCTOR_PROPERTIES); addConstructorProperties = v != null ? v.booleanValue() : Boolean.FALSE.equals(typeNode.getAst().readConfiguration(ConfigurationKeys.ANY_CONSTRUCTOR_SUPPRESS_CONSTRUCTOR_PROPERTIES)); } - + ListBuffer<JCVariableDecl> params = new ListBuffer<JCVariableDecl>(); - ListBuffer<JCExpression> superArgs = new ListBuffer<JCExpression>(); - for (SuperParameter fieldNode : fieldsToParam) { - Name fieldName = source.toName(fieldNode.name); + if (msgParam) { + Name fieldName = typeNode.toName("message"); long flags = JavacHandlerUtil.addFinalIfNeeded(Flags.PARAMETER, typeNode.getContext()); - JCExpression pType = maker.getUnderlyingTreeMaker().Ident(fieldNode.type.tsym); + JCExpression pType = genJavaLangTypeRef(typeNode, "String"); JCVariableDecl param = maker.VarDef(maker.Modifiers(flags), fieldName, pType, null); params.append(param); - superArgs.append(maker.Ident(fieldName)); } - - ListBuffer<JCStatement> statements = new ListBuffer<JCStatement>(); - JCMethodInvocation callToSuper = maker.Apply(List.<JCExpression>nil(), - maker.Ident(typeNode.toName("super")), - superArgs.toList()); - statements.add(maker.Exec(callToSuper)); - - JCModifiers mods = maker.Modifiers(toJavacModifier(level), List.<JCAnnotation>nil()); - if (addConstructorProperties && !isLocalType(typeNode) && LombokOptionsFactory.getDelombokOptions(typeNode.getContext()).getFormatPreferences().generateConstructorProperties()) { - addConstructorProperties(mods, typeNode, fieldsToParam); + + if (causeParam) { + Name fieldName = typeNode.toName("cause"); + long flags = JavacHandlerUtil.addFinalIfNeeded(Flags.PARAMETER, typeNode.getContext()); + JCExpression pType = genJavaLangTypeRef(typeNode, "Throwable"); + JCVariableDecl param = maker.VarDef(maker.Modifiers(flags), fieldName, pType, null); + params.append(param); } + + JCModifiers mods = maker.Modifiers(toJavacModifier(level), List.<JCAnnotation>nil()); + if (addConstructorProperties) addConstructorProperties(mods, typeNode, msgParam, causeParam); return recursiveSetGeneratedBy(maker.MethodDef(mods, typeNode.toName("<init>"), null, List.<JCTypeParameter>nil(), params.toList(), List.<JCExpression>nil(), - maker.Block(0L, statements.toList()), null), source); + maker.Block(0L, statements), null), source); } - + public static boolean isLocalType(JavacNode type) { Kind kind = type.up().getKind(); if (kind == Kind.COMPILATION_UNIT) return false; if (kind == Kind.TYPE) return isLocalType(type.up()); return true; } - - private static class SuperParameter { - private final String name; - private final Type type; - - private SuperParameter(String name, Type type) { - this.name = name; - this.type = type; - } - } } |