»
«val String fieldSuffix = c.getName.substring(TypeConstants.PATTERN_CONSTANT_NAME.length)»
+ «val jurPatternRef = JUR_PATTERN.importedName»
«IF cValue.size == 1»
- private static final «Pattern.importedName» «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «Pattern.importedName».compile("«cValue.keySet.get(0).escapeJava»");
- private static final String «Constants.MEMBER_REGEX_LIST»«fieldSuffix» = "«cValue.values.get(0).escapeJava»";
+ «val firstEntry = cValue.entrySet.iterator.next»
+ private static final «jurPatternRef» «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «jurPatternRef».compile("«firstEntry.key.escapeJava»");
+ private static final String «Constants.MEMBER_REGEX_LIST»«fieldSuffix» = "«firstEntry.value.escapeJava»";
«ELSE»
- private static final «Pattern.importedName»[] «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «CodeHelpers.importedName».compilePatterns(«ImmutableList.importedName».of(
+ private static final «jurPatternRef»[] «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «CODEHELPERS.importedName».compilePatterns(«ImmutableList.importedName».of(
«FOR v : cValue.keySet SEPARATOR ", "»"«v.escapeJava»"«ENDFOR»));
private static final String[] «Constants.MEMBER_REGEX_LIST»«fieldSuffix» = { «
FOR v : cValue.values SEPARATOR ", "»"«v.escapeJava»"«ENDFOR» };
@@ -259,45 +359,100 @@ class BuilderTemplate extends AbstractBuilderTemplate {
«ENDFOR»
'''
- def private generateListSetter(GeneratedProperty field, Type actualType) '''
+ def private generateSetter(BuilderGeneratedProperty field) {
+ val returnType = field.returnType
+ if (returnType instanceof ParameterizedType) {
+ if (Types.isListType(returnType) || Types.isSetType(returnType)) {
+ val arguments = returnType.actualTypeArguments
+ if (arguments.isEmpty) {
+ return generateListSetter(field, Types.objectType)
+ }
+ return generateListSetter(field, arguments.get(0))
+ } else if (Types.isMapType(returnType)) {
+ return generateMapSetter(field, returnType.actualTypeArguments.get(1))
+ }
+ }
+ return generateSimpleSetter(field, returnType)
+ }
+
+ def private generateListSetter(BuilderGeneratedProperty field, Type actualType) '''
«val restrictions = restrictionsForSetter(actualType)»
«IF restrictions !== null»
«generateCheckers(field, restrictions, actualType)»
«ENDIF»
+
+ /**
+ * Set the property corresponding to {@link «targetType.importedName»#«field.getterName»()} to the specified
+ * value.
+ *
+ * @param values desired value
+ * @return this builder
+ */
public «type.getName» set«field.getName.toFirstUpper»(final «field.returnType.importedName» values) {
«IF restrictions !== null»
if (values != null) {
- for («actualType.getFullyQualifiedName» value : values) {
+ for («actualType.importedName» value : values) {
«checkArgument(field, restrictions, actualType, "value")»
}
}
«ENDIF»
- this.«field.fieldName.toString» = values;
+ this.«field.fieldName» = values;
return this;
}
'''
- def private generateSetter(GeneratedProperty field, Type actualType) '''
+ def private generateMapSetter(BuilderGeneratedProperty field, Type actualType) '''
«val restrictions = restrictionsForSetter(actualType)»
«IF restrictions !== null»
«generateCheckers(field, restrictions, actualType)»
«ENDIF»
- public «type.getName» set«field.getName.toFirstUpper»(final «field.returnType.importedName» value) {
+ /**
+ * Set the property corresponding to {@link «targetType.importedName»#«field.getterName»()} to the specified
+ * value.
+ *
+ * @param values desired value
+ * @return this builder
+ */
+ public «type.getName» set«field.name.toFirstUpper»(final «field.returnType.importedName» values) {
«IF restrictions !== null»
- if (value != null) {
- «checkArgument(field, restrictions, actualType, "value")»
+ if (values != null) {
+ for («actualType.importedName» value : values.values()) {
+ «checkArgument(field, restrictions, actualType, "value")»
+ }
}
«ENDIF»
- this.«field.fieldName.toString» = value;
+ this.«field.fieldName» = values;
return this;
}
'''
- private def Type getActualType(ParameterizedType ptype) {
- return ptype.getActualTypeArguments.get(0)
- }
+ def private generateSimpleSetter(BuilderGeneratedProperty field, Type actualType) '''
+ «val restrictions = restrictionsForSetter(actualType)»
+ «IF restrictions !== null»
+
+ «generateCheckers(field, restrictions, actualType)»
+ «ENDIF»
+
+ /**
+ * Set the property corresponding to {@link «targetType.importedName»#«field.getterName»()} to the specified
+ * value.
+ *
+ * @param value desired value
+ * @return this builder
+ */
+ «val setterName = "set" + field.getName.toFirstUpper»
+ public «type.getName» «setterName»(final «field.returnType.importedName» value) {
+ «IF restrictions !== null»
+ if (value != null) {
+ «checkArgument(field, restrictions, actualType, "value")»
+ }
+ «ENDIF»
+ this.«field.fieldName» = value;
+ return this;
+ }
+ '''
/**
* Template method which generates setter methods
@@ -312,29 +467,37 @@ class BuilderTemplate extends AbstractBuilderTemplate {
}
«ENDIF»
«FOR property : properties»
- «IF property.returnType instanceof ParameterizedType && Types.isListType(property.returnType)»
- «generateListSetter(property, getActualType(property.returnType as ParameterizedType))»
- «ELSE»
- «generateSetter(property, property.returnType)»
- «ENDIF»
+ «generateSetter(property)»
«ENDFOR»
«IF augmentType !== null»
- public «type.name» add«AUGMENTATION_FIELD.toFirstUpper»(«Class.importedName» extends «augmentType.importedName»> augmentationType, «augmentType.importedName» augmentationValue) {
- if (augmentationValue == null) {
- return remove«AUGMENTATION_FIELD.toFirstUpper»(augmentationType);
- }
-
- if (!(this.«AUGMENTATION_FIELD» instanceof «HashMap.importedName»)) {
- this.«AUGMENTATION_FIELD» = new «HashMap.importedName»<>();
+ «val augmentTypeRef = augmentType.importedName»
+ «val hashMapRef = JU_HASHMAP.importedName»
+ /**
+ * Add an augmentation to this builder's product.
+ *
+ * @param augmentation augmentation to be added
+ * @return this builder
+ * @throws NullPointerException if {@code augmentation} is null
+ */
+ public «type.name» addAugmentation(«augmentTypeRef» augmentation) {
+ if (!(this.«AUGMENTATION_FIELD» instanceof «hashMapRef»)) {
+ this.«AUGMENTATION_FIELD» = new «hashMapRef»<>();
}
- this.«AUGMENTATION_FIELD».put(augmentationType, augmentationValue);
+ this.«AUGMENTATION_FIELD».put(augmentation.«DATA_CONTAINER_IMPLEMENTED_INTERFACE_NAME»(), augmentation);
return this;
}
- public «type.name» remove«AUGMENTATION_FIELD.toFirstUpper»(«Class.importedName» extends «augmentType.importedName»> augmentationType) {
- if (this.«AUGMENTATION_FIELD» instanceof «HashMap.importedName») {
+ /**
+ * Remove an augmentation from this builder's product. If this builder does not track such an augmentation
+ * type, this method does nothing.
+ *
+ * @param augmentationType augmentation type to be removed
+ * @return this builder
+ */
+ public «type.name» removeAugmentation(«CLASS.importedName» extends «augmentTypeRef»> augmentationType) {
+ if (this.«AUGMENTATION_FIELD» instanceof «hashMapRef») {
this.«AUGMENTATION_FIELD».remove(augmentationType);
}
return this;
@@ -342,11 +505,44 @@ class BuilderTemplate extends AbstractBuilderTemplate {
«ENDIF»
'''
- private def createDescription(GeneratedType type) {
+ private def createDescription(GeneratedType targetType) {
+ val target = targetType.importedName
return '''
- Class that builds {@link «type.importedName»} instances.
-
- @see «type.importedName»
+ Class that builds {@link «target»} instances. Overall design of the class is that of a
+ fluent interface, where method chaining is used.
+
+
+ In general, this class is supposed to be used like this template:
+
+
+ «target» create«target»(int fooXyzzy, int barBaz) {
+ return new «target»Builder()
+ .setFoo(new FooBuilder().setXyzzy(fooXyzzy).build())
+ .setBar(new BarBuilder().setBaz(barBaz).build())
+ .build();
+ }
+
+
+
+
+ This pattern is supported by the immutable nature of «target», as instances can be freely passed around without
+ worrying about synchronization issues.
+
+
+ As a side note: method chaining results in:
+
+ - very efficient Java bytecode, as the method invocation result, in this case the Builder reference, is
+ on the stack, so further method invocations just need to fill method arguments for the next method
+ invocation, which is terminated by {@link #build()}, which is then returned from the method
+ - better understanding by humans, as the scope of mutable state (the builder) is kept to a minimum and is
+ very localized
+ - better optimization opportunities, as the object scope is minimized in terms of invocation (rather than
+ method) stack, making escape analysis a lot
+ easier. Given enough compiler (JIT/AOT) prowess, the cost of th builder object can be completely
+ eliminated
+
+
+ @see «target»
'''
}
@@ -361,9 +557,17 @@ class BuilderTemplate extends AbstractBuilderTemplate {
}
private def generateAugmentation() '''
- @«SuppressWarnings.importedName»({ "unchecked", "checkstyle:methodTypeParameterName"})
- public E$$ «AUGMENTABLE_AUGMENTATION_NAME»(«Class.importedName» augmentationType) {
- return (E$$) «AUGMENTATION_FIELD».get(«CodeHelpers.importedName».nonNullValue(augmentationType, "augmentationType"));
+ /**
+ * Return the specified augmentation, if it is present in this builder.
+ *
+ * @param augmentation type
+ * @param augmentationType augmentation type class
+ * @return Augmentation object from this builder, or {@code null} if not present
+ * @throws «NPE.importedName» if {@code augmentType} is {@code null}
+ */
+ @«SUPPRESS_WARNINGS.importedName»({ "unchecked", "checkstyle:methodTypeParameterName"})
+ public E$$ «AUGMENTABLE_AUGMENTATION_NAME»(«CLASS.importedName» augmentationType) {
+ return (E$$) «AUGMENTATION_FIELD».get(«JU_OBJECTS.importedName».requireNonNull(augmentationType));
}
'''
@@ -374,27 +578,16 @@ class BuilderTemplate extends AbstractBuilderTemplate {
«ENDFOR»
'''
- override protected generateCopyAugmentation(Type implType) {
- val augmentationHolderRef = AugmentationHolder.importedName
- val typeRef = targetType.importedName
- val hashMapRef = HashMap.importedName
- val augmentTypeRef = augmentType.importedName
- return '''
- if (base instanceof «augmentationHolderRef») {
- @SuppressWarnings("unchecked")
- «Map.importedName»<«Class.importedName» extends «augmentTypeRef»>, «augmentTypeRef»> aug =((«augmentationHolderRef»<«typeRef»>) base).augmentations();
- if (!aug.isEmpty()) {
- this.«AUGMENTATION_FIELD» = new «hashMapRef»<>(aug);
- }
- }
- '''
- }
-
- private static def hasNonDefaultMethods(GeneratedType type) {
- !type.methodDefinitions.isEmpty && type.methodDefinitions.exists([def | !def.isDefault])
- }
+ override protected CharSequence generateCopyNonKeys(Collection props) '''
+ «FOR field : props»
+ this.«field.fieldName» = base.«field.getterName»();
+ «ENDFOR»
+ '''
- private static def nonDefaultMethods(GeneratedType type) {
- type.methodDefinitions.filter([def | !def.isDefault])
- }
+ override protected generateCopyAugmentation(Type implType) '''
+ final var aug = base.augmentations();
+ if (!aug.isEmpty()) {
+ this.«AUGMENTATION_FIELD» = new «JU_HASHMAP.importedName»<>(aug);
+ }
+ '''
}