2 * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.mdsal.binding.java.api.generator
10 import static extension org.apache.commons.text.StringEscapeUtils.escapeJava
11 import static org.opendaylight.mdsal.binding.model.util.BindingTypes.DATA_OBJECT
12 import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.AUGMENTABLE_AUGMENTATION_NAME
13 import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.AUGMENTATION_FIELD
14 import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.DATA_CONTAINER_IMPLEMENTED_INTERFACE_NAME
16 import com.google.common.collect.ImmutableList
17 import java.util.ArrayList
18 import java.util.Collection
19 import java.util.HashSet
23 import org.opendaylight.mdsal.binding.model.api.AnnotationType
24 import org.opendaylight.mdsal.binding.model.api.GeneratedProperty
25 import org.opendaylight.mdsal.binding.model.api.GeneratedTransferObject
26 import org.opendaylight.mdsal.binding.model.api.GeneratedType
27 import org.opendaylight.mdsal.binding.model.api.JavaTypeName
28 import org.opendaylight.mdsal.binding.model.api.ParameterizedType
29 import org.opendaylight.mdsal.binding.model.api.Type
30 import org.opendaylight.mdsal.binding.model.util.TypeConstants
31 import org.opendaylight.mdsal.binding.model.util.Types
32 import org.opendaylight.mdsal.binding.spec.naming.BindingMapping
33 import org.opendaylight.yangtools.concepts.Builder
34 import org.opendaylight.yangtools.yang.binding.AugmentationHolder
37 * Template for generating JAVA builder classes.
39 class BuilderTemplate extends AbstractBuilderTemplate {
41 * Constant used as suffix for builder name.
43 package static val BUILDER_STR = "Builder";
45 static val AUGMENTATION_FIELD_UPPER = AUGMENTATION_FIELD.toFirstUpper
46 static val BUILDER = JavaTypeName.create(Builder)
49 * Constructs new instance of this class.
50 * @throws IllegalArgumentException if <code>genType</code> equals <code>null</code>
52 new(GeneratedType genType, GeneratedType targetType, Set<GeneratedProperty> properties, Type augmentType,
54 super(genType, targetType, properties, augmentType, keyType)
57 override isLocalInnerClass(JavaTypeName name) {
58 // Builders do not have inner types
63 * Template method which generates JAVA class body for builder class and for IMPL class.
65 * @return string with JAVA source code
68 «wrapToDocumentation(formatDataForJavaDoc(targetType))»
69 «targetType.annotations.generateDeprecatedAnnotation»
70 public class «type.name» implements «BUILDER.importedName»<«targetType.importedName»> {
72 «generateFields(false)»
74 «constantsDeclarations()»
76 «IF augmentType !== null»
77 «generateAugmentField()»
80 «generateConstructorsFromIfcs()»
82 public «generateCopyConstructor(targetType, type.enclosedTypes.get(0))»
84 «generateMethodFieldsFrom()»
86 «generateGetters(false)»
87 «IF augmentType !== null»
89 «generateAugmentation()»
94 @«OVERRIDE.importedName»
95 public «targetType.name» build() {
96 return new «type.enclosedTypes.get(0).importedName»(this);
99 «new BuilderImplTemplate(this, type.enclosedTypes.get(0)).body»
103 override generateDeprecatedAnnotation(AnnotationType ann) {
104 val forRemoval = ann.getParameter("forRemoval")
105 if (forRemoval !== null) {
106 return "@" + DEPRECATED.importedName + "(forRemoval = " + forRemoval.value + ")"
108 return "@" + SUPPRESS_WARNINGS.importedName + "(\"deprecation\")"
112 * Generate default constructor and constructor for every implemented interface from uses statements.
114 def private generateConstructorsFromIfcs() '''
115 public «type.name»() {
117 «IF (!(targetType instanceof GeneratedTransferObject))»
118 «FOR impl : targetType.implements»
119 «generateConstructorFromIfc(impl)»
125 * Generate constructor with argument of given type.
127 def private Object generateConstructorFromIfc(Type impl) '''
128 «IF (impl instanceof GeneratedType)»
129 «IF impl.hasNonDefaultMethods»
130 public «type.name»(«impl.fullyQualifiedName» arg) {
131 «printConstructorPropertySetter(impl)»
134 «FOR implTypeImplement : impl.implements»
135 «generateConstructorFromIfc(implTypeImplement)»
140 def private Object printConstructorPropertySetter(Type implementedIfc) '''
141 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
142 «val ifc = implementedIfc as GeneratedType»
143 «FOR getter : ifc.nonDefaultMethods»
144 «IF BindingMapping.isGetterMethodName(getter.name)»
145 this._«getter.propertyNameFromGetter» = arg.«getter.name»();
148 «FOR impl : ifc.implements»
149 «printConstructorPropertySetter(impl)»
155 * Generate 'fieldsFrom' method to set builder properties based on type of given argument.
157 def private generateMethodFieldsFrom() '''
158 «IF (!(targetType instanceof GeneratedTransferObject))»
159 «IF targetType.hasImplementsFromUses»
160 «val List<Type> done = targetType.getBaseIfcs»
161 «generateMethodFieldsFromComment(targetType)»
162 public void fieldsFrom(«DATA_OBJECT.importedName» arg) {
163 boolean isValidArg = false;
164 «FOR impl : targetType.getAllIfcs»
165 «generateIfCheck(impl, done)»
167 «CODEHELPERS.importedName».validValue(isValidArg, arg, "«targetType.getAllIfcs.toListOfNames»");
173 def private generateMethodFieldsFromComment(GeneratedType type) '''
175 * Set fields from given grouping argument. Valid argument is instance of one of following types:
177 «FOR impl : type.getAllIfcs»
178 * <li>«impl.fullyQualifiedName»</li>
182 * @param arg grouping object
183 * @throws IllegalArgumentException if given argument is none of valid types
188 * Method is used to find out if given type implements any interface from uses.
190 def boolean hasImplementsFromUses(GeneratedType type) {
192 for (impl : type.getAllIfcs) {
193 if (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods) {
200 def private generateIfCheck(Type impl, List<Type> done) '''
201 «IF (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods)»
202 «val implType = impl as GeneratedType»
203 if (arg instanceof «implType.fullyQualifiedName») {
204 «printPropertySetter(implType)»
210 def private printPropertySetter(Type implementedIfc) '''
211 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
212 «val ifc = implementedIfc as GeneratedType»
213 «FOR getter : ifc.nonDefaultMethods»
214 «IF BindingMapping.isGetterMethodName(getter.name)»
215 this._«getter.propertyNameFromGetter» = ((«implementedIfc.fullyQualifiedName»)arg).«getter.name»();
221 private def List<Type> getBaseIfcs(GeneratedType type) {
222 val List<Type> baseIfcs = new ArrayList();
223 for (ifc : type.implements) {
224 if (ifc instanceof GeneratedType && (ifc as GeneratedType).hasNonDefaultMethods) {
231 private def Set<Type> getAllIfcs(Type type) {
232 val Set<Type> baseIfcs = new HashSet()
233 if (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject)) {
234 val ifc = type as GeneratedType
235 for (impl : ifc.implements) {
236 if (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods) {
239 baseIfcs.addAll(impl.getAllIfcs)
245 private def List<String> toListOfNames(Collection<Type> types) {
246 val List<String> names = new ArrayList
248 names.add(type.fullyQualifiedName)
253 def private constantsDeclarations() '''
254 «FOR c : type.getConstantDefinitions»
255 «IF c.getName.startsWith(TypeConstants.PATTERN_CONSTANT_NAME)»
256 «val cValue = c.value as Map<String, String>»
257 «val String fieldSuffix = c.getName.substring(TypeConstants.PATTERN_CONSTANT_NAME.length)»
258 «val jurPatternRef = JUR_PATTERN.importedName»
259 «IF cValue.size == 1»
260 «val firstEntry = cValue.entrySet.iterator.next»
261 private static final «jurPatternRef» «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «jurPatternRef».compile("«firstEntry.key.escapeJava»");
262 private static final String «Constants.MEMBER_REGEX_LIST»«fieldSuffix» = "«firstEntry.value.escapeJava»";
264 private static final «jurPatternRef»[] «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «CODEHELPERS.importedName».compilePatterns(«ImmutableList.importedName».of(
265 «FOR v : cValue.keySet SEPARATOR ", "»"«v.escapeJava»"«ENDFOR»));
266 private static final String[] «Constants.MEMBER_REGEX_LIST»«fieldSuffix» = { «
267 FOR v : cValue.values SEPARATOR ", "»"«v.escapeJava»"«ENDFOR» };
275 def private generateSetter(GeneratedProperty field) {
276 val returnType = field.returnType
277 if (returnType instanceof ParameterizedType) {
278 if (Types.isListType(returnType)) {
279 return generateListSetter(field, returnType.actualTypeArguments.get(0), "")
280 } else if (Types.isMapType(returnType)) {
281 return generateListSetter(field, returnType.actualTypeArguments.get(1), ".values()")
284 return generateSimpleSetter(field, returnType)
287 def private generateListSetter(GeneratedProperty field, Type actualType, String extractor) '''
288 «val restrictions = restrictionsForSetter(actualType)»
289 «IF restrictions !== null»
290 «generateCheckers(field, restrictions, actualType)»
292 public «type.getName» set«field.getName.toFirstUpper»(final «field.returnType.importedName» values) {
293 «IF restrictions !== null»
294 if (values != null) {
295 for («actualType.importedName» value : values«extractor») {
296 «checkArgument(field, restrictions, actualType, "value")»
300 this.«field.fieldName» = values;
306 def private generateSimpleSetter(GeneratedProperty field, Type actualType) '''
307 «val restrictions = restrictionsForSetter(actualType)»
308 «IF restrictions !== null»
310 «generateCheckers(field, restrictions, actualType)»
313 «val setterName = "set" + field.getName.toFirstUpper»
314 public «type.getName» «setterName»(final «field.returnType.importedName» value) {
315 «IF restrictions !== null»
317 «checkArgument(field, restrictions, actualType, "value")»
320 this.«field.fieldName» = value;
326 * Template method which generates setter methods
328 * @return string with the setter methods
330 def private generateSetters() '''
331 «IF keyType !== null»
332 public «type.getName» withKey(final «keyType.importedName» key) {
337 «FOR property : properties»
338 «generateSetter(property)»
341 «IF augmentType !== null»
342 «val augmentTypeRef = augmentType.importedName»
343 «val jlClassRef = CLASS.importedName»
344 «val hashMapRef = JU_HASHMAP.importedName»
345 public «type.name» add«AUGMENTATION_FIELD_UPPER»(«augmentTypeRef» augmentation) {
346 return add«AUGMENTATION_FIELD_UPPER»(augmentation.«DATA_CONTAINER_IMPLEMENTED_INTERFACE_NAME»(), augmentation);
349 public «type.name» add«AUGMENTATION_FIELD_UPPER»(«jlClassRef»<? extends «augmentTypeRef»> augmentationType, «augmentTypeRef» augmentationValue) {
350 if (augmentationValue == null) {
351 return remove«AUGMENTATION_FIELD_UPPER»(augmentationType);
354 if (!(this.«AUGMENTATION_FIELD» instanceof «hashMapRef»)) {
355 this.«AUGMENTATION_FIELD» = new «hashMapRef»<>();
358 this.«AUGMENTATION_FIELD».put(augmentationType, augmentationValue);
362 public «type.name» remove«AUGMENTATION_FIELD_UPPER»(«jlClassRef»<? extends «augmentTypeRef»> augmentationType) {
363 if (this.«AUGMENTATION_FIELD» instanceof «hashMapRef») {
364 this.«AUGMENTATION_FIELD».remove(augmentationType);
371 private def createDescription(GeneratedType targetType) {
372 val target = type.importedName
374 Class that builds {@link «target»} instances. Overall design of the class is that of a
375 <a href="https://en.wikipedia.org/wiki/Fluent_interface">fluent interface</a>, where method chaining is used.
378 In general, this class is supposed to be used like this template:
381 «target» createTarget(int fooXyzzy, int barBaz) {
382 return new «target»Builder()
383 .setFoo(new FooBuilder().setXyzzy(fooXyzzy).build())
384 .setBar(new BarBuilder().setBaz(barBaz).build())
391 This pattern is supported by the immutable nature of «target», as instances can be freely passed around without
392 worrying about synchronization issues.
395 As a side note: method chaining results in:
397 <li>very efficient Java bytecode, as the method invocation result, in this case the Builder reference, is
398 on the stack, so further method invocations just need to fill method arguments for the next method
399 invocation, which is terminated by {@link #build()}, which is then returned from the method</li>
400 <li>better understanding by humans, as the scope of mutable state (the builder) is kept to a minimum and is
402 <li>better optimization oportunities, as the object scope is minimized in terms of invocation (rather than
403 method) stack, making <a href="https://en.wikipedia.org/wiki/Escape_analysis">escape analysis</a> a lot
404 easier. Given enough compiler (JIT/AOT) prowess, the cost of th builder object can be completely
409 @see «BUILDER.importedName»
413 override protected String formatDataForJavaDoc(GeneratedType type) {
414 val typeDescription = createDescription(type)
417 «IF !typeDescription.nullOrEmpty»
423 private def generateAugmentation() '''
424 @«SUPPRESS_WARNINGS.importedName»({ "unchecked", "checkstyle:methodTypeParameterName"})
425 public <E$$ extends «augmentType.importedName»> E$$ «AUGMENTABLE_AUGMENTATION_NAME»(«CLASS.importedName»<E$$> augmentationType) {
426 return (E$$) «AUGMENTATION_FIELD».get(«JU_OBJECTS.importedName».requireNonNull(augmentationType));
430 override protected generateCopyKeys(List<GeneratedProperty> keyProps) '''
431 this.key = base.«BindingMapping.IDENTIFIABLE_KEY_NAME»();
432 «FOR field : keyProps»
433 this.«field.fieldName» = base.«field.getterMethodName»();
437 override protected generateCopyAugmentation(Type implType) {
438 val augmentationHolderRef = AugmentationHolder.importedName
439 val typeRef = targetType.importedName
440 val hashMapRef = JU_HASHMAP.importedName
441 val augmentTypeRef = augmentType.importedName
443 if (base instanceof «augmentationHolderRef») {
444 @SuppressWarnings("unchecked")
445 «JU_MAP.importedName»<«CLASS.importedName»<? extends «augmentTypeRef»>, «augmentTypeRef»> aug =((«augmentationHolderRef»<«typeRef»>) base).augmentations();
446 if (!aug.isEmpty()) {
447 this.«AUGMENTATION_FIELD» = new «hashMapRef»<>(aug);
453 private static def hasNonDefaultMethods(GeneratedType type) {
454 !type.methodDefinitions.isEmpty && type.methodDefinitions.exists([def | !def.isDefault])
457 private static def nonDefaultMethods(GeneratedType type) {
458 type.methodDefinitions.filter([def | !def.isDefault])