From 48b1f17f7ef0e0b3d90dee74ee1b490a02cacff2 Mon Sep 17 00:00:00 2001
From: Jan Rieke <it@janrieke.de>
Date: Fri, 1 May 2020 23:56:17 +0200
Subject: [SuperBuilder] allow constructor customization

---
 .../eclipse/handlers/HandleSuperBuilder.java       | 28 +++++++++++++++--
 .../lombok/javac/handlers/HandleSuperBuilder.java  | 35 ++++++++++++++++++++--
 2 files changed, 59 insertions(+), 4 deletions(-)

(limited to 'src')

diff --git a/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java b/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java
index a49c20a4..455d40ed 100644
--- a/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java
+++ b/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java
@@ -274,8 +274,10 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler<SuperBuilder> {
 		// If there is no superclass, superclassBuilderClassExpression is still == null at this point.
 		// You can use it to check whether to inherit or not.
 		
-		generateBuilderBasedConstructor(cfv, tdParent, typeParams, builderFields, annotationNode, builderClassName,
-			superclassBuilderClass != null);
+		if (!constructorExists(tdParent, builderClassName)) {
+			generateBuilderBasedConstructor(cfv, tdParent, typeParams, builderFields, annotationNode, builderClassName,
+				superclassBuilderClass != null);
+		}
 		
 		// Create the abstract builder class, or reuse an existing one.
 		EclipseNode builderType = findInnerClass(tdParent, builderClassName);
@@ -1159,4 +1161,26 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler<SuperBuilder> {
 		System.arraycopy(name, 0, out, prefix.length, name.length);
 		return out;
 	}
+	
+	private boolean constructorExists(EclipseNode type, String builderClassName) {
+		if (type != null && type.get() instanceof TypeDeclaration) {
+			TypeDeclaration typeDecl = (TypeDeclaration)type.get();
+			if (typeDecl.methods != null) for (AbstractMethodDeclaration def : typeDecl.methods) {
+				if (def instanceof ConstructorDeclaration) {
+					if ((def.bits & ASTNode.IsDefaultConstructor) != 0) continue;
+					if (!def.isConstructor()) continue;
+					if (isTolerate(type, def)) continue;
+					if (def.arguments.length != 1) continue;
+					
+					// Cannot use typeMatches() here, because the parameter could be fully-qualified, partially-qualified, or not qualified.
+					// A string-compare of the last part should work. If it's a false-positive, users could still @Tolerate it.
+					char[] typeName = def.arguments[0].type.getLastToken();
+					if (builderClassName.equals(String.valueOf(typeName)))
+						return true;
+				}
+			}
+		}
+		
+		return false;
+	}
 }
diff --git a/src/core/lombok/javac/handlers/HandleSuperBuilder.java b/src/core/lombok/javac/handlers/HandleSuperBuilder.java
index 2dc3247d..b7cd6f9a 100644
--- a/src/core/lombok/javac/handlers/HandleSuperBuilder.java
+++ b/src/core/lombok/javac/handlers/HandleSuperBuilder.java
@@ -354,8 +354,10 @@ public class HandleSuperBuilder extends JavacAnnotationHandler<SuperBuilder> {
 		}
 		
 		// Generate a constructor in the annotated class that takes a builder as argument.
-		generateBuilderBasedConstructor(cfv, tdParent, typeParams, builderFields, annotationNode, builderClassName,
-			superclassBuilderClassExpression != null);
+		if (!constructorExists(tdParent, builderClassName)) {
+			generateBuilderBasedConstructor(cfv, tdParent, typeParams, builderFields, annotationNode, builderClassName,
+				superclassBuilderClassExpression != null);
+		}
 		
 		if (isAbstract) {
 			// Only non-abstract classes get the builder() and toBuilder() methods.
@@ -1070,4 +1072,33 @@ public class HandleSuperBuilder extends JavacAnnotationHandler<SuperBuilder> {
 		}
 		return typeParameter;
 	}
+	
+	/**
+	 * Checks if there is a manual constructor in the given type with a single parameter (builder).
+	 */
+	private boolean constructorExists(JavacNode type, String builderClassName) {
+		if (type != null && type.get() instanceof JCClassDecl) {
+			for (JCTree def : ((JCClassDecl)type.get()).defs) {
+				if (def instanceof JCMethodDecl) {
+					JCMethodDecl md = (JCMethodDecl) def;
+					String name = md.name.toString();
+					boolean matches = name.equals("<init>");
+					if (isTolerate(type, md)) 
+						continue;
+					if (matches && md.params != null && md.params.length() == 1) {
+						// Cannot use typeMatches() here, because the parameter could be fully-qualified, partially-qualified, or not qualified.
+						// A string-compare of the last part should work. If it's a false-positive, users could still @Tolerate it.
+						String typeName = md.params.get(0).getType().toString();
+						int lastIndexOfDot = typeName.lastIndexOf('.');
+						if (lastIndexOfDot >= 0) {
+							typeName = typeName.substring(lastIndexOfDot+1);
+						}
+						if ((builderClassName+"<?, ?>").equals(typeName))
+							return true;
+					}
+				}
+			}
+		}
+		return false;
+	}
 }
-- 
cgit