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.ri.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 com.google.common.collect.ImmutableSet
18 import com.google.common.collect.Sets
19 import java.util.ArrayList
20 import java.util.Collection
21 import java.util.HashSet
25 import org.opendaylight.mdsal.binding.model.api.AnnotationType
26 import org.opendaylight.mdsal.binding.model.api.GeneratedProperty
27 import org.opendaylight.mdsal.binding.model.api.GeneratedTransferObject
28 import org.opendaylight.mdsal.binding.model.api.GeneratedType
29 import org.opendaylight.mdsal.binding.model.api.JavaTypeName
30 import org.opendaylight.mdsal.binding.model.api.MethodSignature;
31 import org.opendaylight.mdsal.binding.model.api.ParameterizedType
32 import org.opendaylight.mdsal.binding.model.api.Type
33 import org.opendaylight.mdsal.binding.model.ri.TypeConstants
34 import org.opendaylight.mdsal.binding.model.ri.Types
35 import org.opendaylight.mdsal.binding.spec.naming.BindingMapping
36 import org.opendaylight.yangtools.concepts.Builder
39 * Template for generating JAVA builder classes.
41 class BuilderTemplate extends AbstractBuilderTemplate {
42 static val BUILDER = JavaTypeName.create(Builder)
44 val BuilderImplTemplate implTemplate
47 * Constructs new instance of this class.
48 * @throws IllegalArgumentException if <code>genType</code> equals <code>null</code>
50 new(GeneratedType genType, GeneratedType targetType, Type keyType) {
51 super(genType, targetType, keyType)
52 implTemplate = new BuilderImplTemplate(this, type.enclosedTypes.get(0))
55 override isLocalInnerClass(JavaTypeName name) {
56 // Builders do not have inner types
61 * Template method which generates JAVA class body for builder class and for IMPL class.
63 * @return string with JAVA source code
66 «wrapToDocumentation(formatDataForJavaDoc(targetType))»
67 «targetType.annotations.generateDeprecatedAnnotation»
69 public class «type.name» implements «BUILDER.importedName»<«targetType.importedName»> {
71 «generateFields(false)»
73 «constantsDeclarations()»
75 «IF augmentType !== null»
76 «generateAugmentField()»
79 «generateConstructorsFromIfcs()»
81 public «generateCopyConstructor(targetType, type.enclosedTypes.get(0))»
83 «generateMethodFieldsFrom()»
85 «generateGetters(false)»
86 «IF augmentType !== null»
88 «generateAugmentation()»
93 @«OVERRIDE.importedName»
94 public «targetType.name» build() {
95 return new «type.enclosedTypes.get(0).importedName»(this);
102 override generateDeprecatedAnnotation(AnnotationType ann) {
103 val forRemoval = ann.getParameter("forRemoval")
104 if (forRemoval !== null) {
105 return "@" + DEPRECATED.importedName + "(forRemoval = " + forRemoval.value + ")"
107 return "@" + SUPPRESS_WARNINGS.importedName + "(\"deprecation\")"
111 * Generate default constructor and constructor for every implemented interface from uses statements.
113 def private generateConstructorsFromIfcs() '''
114 public «type.name»() {
117 «IF (!(targetType instanceof GeneratedTransferObject))»
118 «FOR impl : targetType.implements SEPARATOR "\n"»
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.importedName» 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 «val propertyName = getter.propertyNameFromGetter»
146 «printPropertySetter(getter, '''arg.«getter.name»()''', propertyName)»;
149 «FOR impl : ifc.implements»
150 «printConstructorPropertySetter(impl, getSpecifiedGetters(ifc))»
155 def private Object printConstructorPropertySetter(Type implementedIfc, Set<MethodSignature> alreadySetProperties) '''
156 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
157 «val ifc = implementedIfc as GeneratedType»
158 «FOR getter : ifc.nonDefaultMethods»
159 «IF BindingMapping.isGetterMethodName(getter.name) && getterByName(alreadySetProperties, getter.name).isEmpty»
160 «val propertyName = getter.propertyNameFromGetter»
161 «printPropertySetter(getter, '''arg.«getter.name»()''', propertyName)»;
164 «FOR descendant : ifc.implements»
165 «printConstructorPropertySetter(descendant, Sets.union(alreadySetProperties, getSpecifiedGetters(ifc)))»
170 def static Set<MethodSignature> getSpecifiedGetters(GeneratedType type) {
171 val ImmutableSet.Builder<MethodSignature> setBuilder = new ImmutableSet.Builder
172 for (MethodSignature method : type.getMethodDefinitions()) {
173 if (method.hasOverrideAnnotation) {
174 setBuilder.add(method)
177 return setBuilder.build()
181 * Generate 'fieldsFrom' method to set builder properties based on type of given argument.
183 def private generateMethodFieldsFrom() '''
184 «IF (!(targetType instanceof GeneratedTransferObject))»
185 «IF targetType.hasImplementsFromUses»
186 «val List<Type> done = targetType.getBaseIfcs»
187 «generateMethodFieldsFromComment(targetType)»
188 public void fieldsFrom(«DATA_OBJECT.importedName» arg) {
189 boolean isValidArg = false;
190 «FOR impl : targetType.getAllIfcs»
191 «generateIfCheck(impl, done)»
193 «CODEHELPERS.importedName».validValue(isValidArg, arg, "«targetType.getAllIfcs.toListOfNames»");
199 def private generateMethodFieldsFromComment(GeneratedType type) '''
201 * Set fields from given grouping argument. Valid argument is instance of one of following types:
203 «FOR impl : type.getAllIfcs»
204 * <li>«impl.importedName»</li>
208 * @param arg grouping object
209 * @throws IllegalArgumentException if given argument is none of valid types or has property with incompatible value
214 * Method is used to find out if given type implements any interface from uses.
216 def boolean hasImplementsFromUses(GeneratedType type) {
218 for (impl : type.getAllIfcs) {
219 if (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods) {
226 def private generateIfCheck(Type impl, List<Type> done) '''
227 «IF (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods)»
228 «val implType = impl as GeneratedType»
229 if (arg instanceof «implType.importedName») {
230 «printPropertySetter(implType)»
236 def private printPropertySetter(Type implementedIfc) '''
237 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
238 «val ifc = implementedIfc as GeneratedType»
239 «FOR getter : ifc.nonDefaultMethods»
240 «IF BindingMapping.isGetterMethodName(getter.name) && !hasOverrideAnnotation(getter)»
241 «printPropertySetter(getter, '''((«ifc.importedName»)arg).«getter.name»()''', getter.propertyNameFromGetter)»;
247 def private printPropertySetter(MethodSignature getter, String retrieveProperty, String propertyName) {
248 val ownGetter = implTemplate.findGetter(getter.name)
249 val ownGetterType = ownGetter.returnType
250 if (Types.strictTypeEquals(getter.returnType, ownGetterType)) {
251 return "this._" + propertyName + " = " + retrieveProperty
253 if (Types.isListType(ownGetterType)) {
254 val itemType = (ownGetterType as ParameterizedType).actualTypeArguments.get(0)
256 this._«propertyName» = «CODEHELPERS.importedName».checkListFieldCast(«itemType.importedName».class, "«propertyName»", «retrieveProperty»)'''
259 this._«propertyName» = «CODEHELPERS.importedName».checkFieldCast(«ownGetter.returnType.importedName».class, "«propertyName»", «retrieveProperty»)'''
262 private def List<Type> getBaseIfcs(GeneratedType type) {
263 val List<Type> baseIfcs = new ArrayList();
264 for (ifc : type.implements) {
265 if (ifc instanceof GeneratedType && (ifc as GeneratedType).hasNonDefaultMethods) {
272 private def Set<Type> getAllIfcs(Type type) {
273 val Set<Type> baseIfcs = new HashSet()
274 if (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject)) {
275 val ifc = type as GeneratedType
276 for (impl : ifc.implements) {
277 if (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods) {
280 baseIfcs.addAll(impl.getAllIfcs)
286 private def List<String> toListOfNames(Collection<Type> types) {
287 val List<String> names = new ArrayList
289 names.add(type.importedName)
294 def private constantsDeclarations() '''
295 «FOR c : type.getConstantDefinitions»
296 «IF c.getName.startsWith(TypeConstants.PATTERN_CONSTANT_NAME)»
297 «val cValue = c.value as Map<String, String>»
298 «val String fieldSuffix = c.getName.substring(TypeConstants.PATTERN_CONSTANT_NAME.length)»
299 «val jurPatternRef = JUR_PATTERN.importedName»
300 «IF cValue.size == 1»
301 «val firstEntry = cValue.entrySet.iterator.next»
302 private static final «jurPatternRef» «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «jurPatternRef».compile("«firstEntry.key.escapeJava»");
303 private static final String «Constants.MEMBER_REGEX_LIST»«fieldSuffix» = "«firstEntry.value.escapeJava»";
305 private static final «jurPatternRef»[] «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «CODEHELPERS.importedName».compilePatterns(«ImmutableList.importedName».of(
306 «FOR v : cValue.keySet SEPARATOR ", "»"«v.escapeJava»"«ENDFOR»));
307 private static final String[] «Constants.MEMBER_REGEX_LIST»«fieldSuffix» = { «
308 FOR v : cValue.values SEPARATOR ", "»"«v.escapeJava»"«ENDFOR» };
316 def private generateSetter(GeneratedProperty field) {
317 val returnType = field.returnType
318 if (returnType instanceof ParameterizedType) {
319 if (Types.isListType(returnType)) {
320 val arguments = returnType.actualTypeArguments
321 if (arguments.isEmpty) {
322 return generateListSetter(field, Types.objectType)
324 return generateListSetter(field, arguments.get(0))
325 } else if (Types.isMapType(returnType)) {
326 return generateMapSetter(field, returnType.actualTypeArguments.get(1))
329 return generateSimpleSetter(field, returnType)
332 def private generateListSetter(GeneratedProperty field, Type actualType) '''
333 «val restrictions = restrictionsForSetter(actualType)»
334 «IF restrictions !== null»
335 «generateCheckers(field, restrictions, actualType)»
337 public «type.getName» set«field.getName.toFirstUpper»(final «field.returnType.importedName» values) {
338 «IF restrictions !== null»
339 if (values != null) {
340 for («actualType.importedName» value : values) {
341 «checkArgument(field, restrictions, actualType, "value")»
345 this.«field.fieldName» = values;
351 def private generateMapSetter(GeneratedProperty field, Type actualType) '''
352 «val restrictions = restrictionsForSetter(actualType)»
353 «IF restrictions !== null»
354 «generateCheckers(field, restrictions, actualType)»
356 public «type.getName» set«field.name.toFirstUpper»(final «field.returnType.importedName» values) {
357 «IF restrictions !== null»
358 if (values != null) {
359 for («actualType.importedName» value : values.values()) {
360 «checkArgument(field, restrictions, actualType, "value")»
364 this.«field.fieldName» = values;
369 def private generateSimpleSetter(GeneratedProperty field, Type actualType) '''
370 «val restrictions = restrictionsForSetter(actualType)»
371 «IF restrictions !== null»
373 «generateCheckers(field, restrictions, actualType)»
376 «val setterName = "set" + field.getName.toFirstUpper»
377 public «type.getName» «setterName»(final «field.returnType.importedName» value) {
378 «IF restrictions !== null»
380 «checkArgument(field, restrictions, actualType, "value")»
383 this.«field.fieldName» = value;
389 * Template method which generates setter methods
391 * @return string with the setter methods
393 def private generateSetters() '''
394 «IF keyType !== null»
395 public «type.getName» withKey(final «keyType.importedName» key) {
400 «FOR property : properties»
401 «generateSetter(property)»
404 «IF augmentType !== null»
405 «val augmentTypeRef = augmentType.importedName»
406 «val jlClassRef = CLASS.importedName»
407 «val hashMapRef = JU_HASHMAP.importedName»
409 * Add an augmentation to this builder's product.
411 * @param augmentation augmentation to be added
412 * @return this builder
413 * @throws NullPointerException if {@code augmentation} is null
415 public «type.name» addAugmentation(«augmentTypeRef» augmentation) {
416 «jlClassRef»<? extends «augmentTypeRef»> augmentationType = augmentation.«DATA_CONTAINER_IMPLEMENTED_INTERFACE_NAME»();
417 if (!(this.«AUGMENTATION_FIELD» instanceof «hashMapRef»)) {
418 this.«AUGMENTATION_FIELD» = new «hashMapRef»<>();
421 this.«AUGMENTATION_FIELD».put(augmentationType, augmentation);
426 * Remove an augmentation from this builder's product. If this builder does not track such an augmentation
427 * type, this method does nothing.
429 * @param augmentationType augmentation type to be removed
430 * @return this builder
432 public «type.name» removeAugmentation(«jlClassRef»<? extends «augmentTypeRef»> augmentationType) {
433 if (this.«AUGMENTATION_FIELD» instanceof «hashMapRef») {
434 this.«AUGMENTATION_FIELD».remove(augmentationType);
441 private def createDescription(GeneratedType targetType) {
442 val target = targetType.importedName
444 Class that builds {@link «target»} instances. Overall design of the class is that of a
445 <a href="https://en.wikipedia.org/wiki/Fluent_interface">fluent interface</a>, where method chaining is used.
448 In general, this class is supposed to be used like this template:
451 «target» create«target»(int fooXyzzy, int barBaz) {
452 return new «target»Builder()
453 .setFoo(new FooBuilder().setXyzzy(fooXyzzy).build())
454 .setBar(new BarBuilder().setBaz(barBaz).build())
461 This pattern is supported by the immutable nature of «target», as instances can be freely passed around without
462 worrying about synchronization issues.
465 As a side note: method chaining results in:
467 <li>very efficient Java bytecode, as the method invocation result, in this case the Builder reference, is
468 on the stack, so further method invocations just need to fill method arguments for the next method
469 invocation, which is terminated by {@link #build()}, which is then returned from the method</li>
470 <li>better understanding by humans, as the scope of mutable state (the builder) is kept to a minimum and is
472 <li>better optimization opportunities, as the object scope is minimized in terms of invocation (rather than
473 method) stack, making <a href="https://en.wikipedia.org/wiki/Escape_analysis">escape analysis</a> a lot
474 easier. Given enough compiler (JIT/AOT) prowess, the cost of th builder object can be completely
479 @see «BUILDER.importedName»
483 override protected String formatDataForJavaDoc(GeneratedType type) {
484 val typeDescription = createDescription(type)
487 «IF !typeDescription.nullOrEmpty»
493 private def generateAugmentation() '''
494 @«SUPPRESS_WARNINGS.importedName»({ "unchecked", "checkstyle:methodTypeParameterName"})
495 public <E$$ extends «augmentType.importedName»> E$$ «AUGMENTABLE_AUGMENTATION_NAME»(«CLASS.importedName»<E$$> augmentationType) {
496 return (E$$) «AUGMENTATION_FIELD».get(«JU_OBJECTS.importedName».requireNonNull(augmentationType));
500 override protected generateCopyKeys(List<GeneratedProperty> keyProps) '''
501 this.key = base.«BindingMapping.IDENTIFIABLE_KEY_NAME»();
502 «FOR field : keyProps»
503 this.«field.fieldName» = base.«field.getterMethodName»();
507 override protected CharSequence generateCopyNonKeys(Collection<BuilderGeneratedProperty> props) '''
509 this.«field.fieldName» = base.«field.getterName»();
513 override protected generateCopyAugmentation(Type implType) {
514 val hashMapRef = JU_HASHMAP.importedName
515 val augmentTypeRef = augmentType.importedName
517 «JU_MAP.importedName»<«CLASS.importedName»<? extends «augmentTypeRef»>, «augmentTypeRef»> aug = base.augmentations();
518 if (!aug.isEmpty()) {
519 this.«AUGMENTATION_FIELD» = new «hashMapRef»<>(aug);