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.spec.naming.BindingMapping.AUGMENTABLE_AUGMENTATION_NAME
12 import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.AUGMENTATION_FIELD
14 import com.google.common.collect.ImmutableList
15 import java.util.ArrayList
16 import java.util.Collection
17 import java.util.HashMap
18 import java.util.HashSet
22 import java.util.regex.Pattern
23 import org.opendaylight.mdsal.binding.model.api.GeneratedProperty
24 import org.opendaylight.mdsal.binding.model.api.GeneratedTransferObject
25 import org.opendaylight.mdsal.binding.model.api.GeneratedType
26 import org.opendaylight.mdsal.binding.model.api.JavaTypeName
27 import org.opendaylight.mdsal.binding.model.api.ParameterizedType
28 import org.opendaylight.mdsal.binding.model.api.Type
29 import org.opendaylight.mdsal.binding.model.util.TypeConstants
30 import org.opendaylight.mdsal.binding.model.util.Types
31 import org.opendaylight.mdsal.binding.spec.naming.BindingMapping
32 import org.opendaylight.yangtools.concepts.Builder
33 import org.opendaylight.yangtools.yang.binding.AugmentationHolder
34 import org.opendaylight.yangtools.yang.binding.CodeHelpers
35 import org.opendaylight.yangtools.yang.binding.DataObject
38 * Template for generating JAVA builder classes.
40 class BuilderTemplate extends AbstractBuilderTemplate {
42 * Constant used as suffix for builder name.
44 public static val BUILDER = "Builder";
46 static val AUGMENTATION_FIELD_UPPER = AUGMENTATION_FIELD.toFirstUpper
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 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);
98 «new BuilderImplTemplate(this, type.enclosedTypes.get(0)).body»
103 * Generate default constructor and constructor for every implemented interface from uses statements.
105 def private generateConstructorsFromIfcs() '''
106 public «type.name»() {
108 «IF (!(targetType instanceof GeneratedTransferObject))»
109 «FOR impl : targetType.implements»
110 «generateConstructorFromIfc(impl)»
116 * Generate constructor with argument of given type.
118 def private Object generateConstructorFromIfc(Type impl) '''
119 «IF (impl instanceof GeneratedType)»
120 «IF impl.hasNonDefaultMethods»
121 public «type.name»(«impl.fullyQualifiedName» arg) {
122 «printConstructorPropertySetter(impl)»
125 «FOR implTypeImplement : impl.implements»
126 «generateConstructorFromIfc(implTypeImplement)»
131 def private Object printConstructorPropertySetter(Type implementedIfc) '''
132 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
133 «val ifc = implementedIfc as GeneratedType»
134 «FOR getter : ifc.nonDefaultMethods»
135 «IF BindingMapping.isGetterMethodName(getter.name)»
136 this._«getter.propertyNameFromGetter» = arg.«getter.name»();
139 «FOR impl : ifc.implements»
140 «printConstructorPropertySetter(impl)»
146 * Generate 'fieldsFrom' method to set builder properties based on type of given argument.
148 def private generateMethodFieldsFrom() '''
149 «IF (!(targetType instanceof GeneratedTransferObject))»
150 «IF targetType.hasImplementsFromUses»
151 «val List<Type> done = targetType.getBaseIfcs»
152 «generateMethodFieldsFromComment(targetType)»
153 public void fieldsFrom(«DataObject.importedName» arg) {
154 boolean isValidArg = false;
155 «FOR impl : targetType.getAllIfcs»
156 «generateIfCheck(impl, done)»
158 «CodeHelpers.importedName».validValue(isValidArg, arg, "«targetType.getAllIfcs.toListOfNames»");
164 def private generateMethodFieldsFromComment(GeneratedType type) '''
166 * Set fields from given grouping argument. Valid argument is instance of one of following types:
168 «FOR impl : type.getAllIfcs»
169 * <li>«impl.fullyQualifiedName»</li>
173 * @param arg grouping object
174 * @throws IllegalArgumentException if given argument is none of valid types
179 * Method is used to find out if given type implements any interface from uses.
181 def boolean hasImplementsFromUses(GeneratedType type) {
183 for (impl : type.getAllIfcs) {
184 if (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods) {
191 def private generateIfCheck(Type impl, List<Type> done) '''
192 «IF (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods)»
193 «val implType = impl as GeneratedType»
194 if (arg instanceof «implType.fullyQualifiedName») {
195 «printPropertySetter(implType)»
201 def private printPropertySetter(Type implementedIfc) '''
202 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
203 «val ifc = implementedIfc as GeneratedType»
204 «FOR getter : ifc.nonDefaultMethods»
205 «IF BindingMapping.isGetterMethodName(getter.name)»
206 this._«getter.propertyNameFromGetter» = ((«implementedIfc.fullyQualifiedName»)arg).«getter.name»();
212 private def List<Type> getBaseIfcs(GeneratedType type) {
213 val List<Type> baseIfcs = new ArrayList();
214 for (ifc : type.implements) {
215 if (ifc instanceof GeneratedType && (ifc as GeneratedType).hasNonDefaultMethods) {
222 private def Set<Type> getAllIfcs(Type type) {
223 val Set<Type> baseIfcs = new HashSet()
224 if (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject)) {
225 val ifc = type as GeneratedType
226 for (impl : ifc.implements) {
227 if (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods) {
230 baseIfcs.addAll(impl.getAllIfcs)
236 private def List<String> toListOfNames(Collection<Type> types) {
237 val List<String> names = new ArrayList
239 names.add(type.fullyQualifiedName)
244 def private constantsDeclarations() '''
245 «FOR c : type.getConstantDefinitions»
246 «IF c.getName.startsWith(TypeConstants.PATTERN_CONSTANT_NAME)»
247 «val cValue = c.value as Map<String, String>»
248 «val String fieldSuffix = c.getName.substring(TypeConstants.PATTERN_CONSTANT_NAME.length)»
249 «IF cValue.size == 1»
250 «val firstEntry = cValue.entrySet.iterator.next»
251 private static final «Pattern.importedName» «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «Pattern.importedName».compile("«firstEntry.key.escapeJava»");
252 private static final String «Constants.MEMBER_REGEX_LIST»«fieldSuffix» = "«firstEntry.value.escapeJava»";
254 private static final «Pattern.importedName»[] «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «CodeHelpers.importedName».compilePatterns(«ImmutableList.importedName».of(
255 «FOR v : cValue.keySet SEPARATOR ", "»"«v.escapeJava»"«ENDFOR»));
256 private static final String[] «Constants.MEMBER_REGEX_LIST»«fieldSuffix» = { «
257 FOR v : cValue.values SEPARATOR ", "»"«v.escapeJava»"«ENDFOR» };
265 def private generateListSetter(GeneratedProperty field, Type actualType) '''
266 «val restrictions = restrictionsForSetter(actualType)»
267 «IF restrictions !== null»
268 «generateCheckers(field, restrictions, actualType)»
270 public «type.getName» set«field.getName.toFirstUpper»(final «field.returnType.importedName» values) {
271 «IF restrictions !== null»
272 if (values != null) {
273 for («actualType.getFullyQualifiedName» value : values) {
274 «checkArgument(field, restrictions, actualType, "value")»
278 this.«field.fieldName» = values;
284 def private generateSetter(GeneratedProperty field, Type actualType) '''
285 «val restrictions = restrictionsForSetter(actualType)»
286 «IF restrictions !== null»
288 «generateCheckers(field, restrictions, actualType)»
291 «val setterName = "set" + field.getName.toFirstUpper»
292 public «type.getName» «setterName»(final «field.returnType.importedName» value) {
293 «IF restrictions !== null»
295 «checkArgument(field, restrictions, actualType, "value")»
298 this.«field.fieldName» = value;
301 «val uintType = UINT_TYPES.get(field.returnType)»
302 «IF uintType !== null»
305 * Utility migration setter.
307 * @param value field value in legacy type
308 * @return this builder
309 * @deprecated Use {#link «setterName»(«field.returnType.importedName»)} instead.
311 @Deprecated(forRemoval = true)
312 public «type.getName» «setterName»(final «uintType.importedName» value) {
313 return «setterName»(«CodeHelpers.importedName».compatUint(value));
318 private def Type getActualType(ParameterizedType ptype) {
319 return ptype.getActualTypeArguments.get(0)
323 * Template method which generates setter methods
325 * @return string with the setter methods
327 def private generateSetters() '''
328 «IF keyType !== null»
329 public «type.getName» withKey(final «keyType.importedName» key) {
334 «FOR property : properties»
335 «IF property.returnType instanceof ParameterizedType && Types.isListType(property.returnType)»
336 «generateListSetter(property, getActualType(property.returnType as ParameterizedType))»
338 «generateSetter(property, property.returnType)»
342 «IF augmentType !== null»
343 «val augmentTypeRef = augmentType.importedName»
344 public «type.name» add«AUGMENTATION_FIELD_UPPER»(«Class.importedName»<? extends «augmentTypeRef»> augmentationType, «augmentTypeRef» augmentationValue) {
345 if (augmentationValue == null) {
346 return remove«AUGMENTATION_FIELD_UPPER»(augmentationType);
349 if (!(this.«AUGMENTATION_FIELD» instanceof «HashMap.importedName»)) {
350 this.«AUGMENTATION_FIELD» = new «HashMap.importedName»<>();
353 this.«AUGMENTATION_FIELD».put(augmentationType, augmentationValue);
357 public «type.name» remove«AUGMENTATION_FIELD_UPPER»(«Class.importedName»<? extends «augmentTypeRef»> augmentationType) {
358 if (this.«AUGMENTATION_FIELD» instanceof «HashMap.importedName») {
359 this.«AUGMENTATION_FIELD».remove(augmentationType);
366 private def createDescription(GeneratedType targetType) {
367 val target = type.importedName
369 Class that builds {@link «target»} instances. Overall design of the class is that of a
370 <a href="https://en.wikipedia.org/wiki/Fluent_interface">fluent interface</a>, where method chaining is used.
373 In general, this class is supposed to be used like this template:
376 «target» createTarget(int fooXyzzy, int barBaz) {
377 return new «target»Builder()
378 .setFoo(new FooBuilder().setXyzzy(fooXyzzy).build())
379 .setBar(new BarBuilder().setBaz(barBaz).build())
386 This pattern is supported by the immutable nature of «target», as instances can be freely passed around without
387 worrying about synchronization issues.
390 As a side note: method chaining results in:
392 <li>very efficient Java bytecode, as the method invocation result, in this case the Builder reference, is
393 on the stack, so further method invocations just need to fill method arguments for the next method
394 invocation, which is terminated by {@link #build()}, which is then returned from the method</li>
395 <li>better understanding by humans, as the scope of mutable state (the builder) is kept to a minimum and is
397 <li>better optimization oportunities, as the object scope is minimized in terms of invocation (rather than
398 method) stack, making <a href="https://en.wikipedia.org/wiki/Escape_analysis">escape analysis</a> a lot
399 easier. Given enough compiler (JIT/AOT) prowess, the cost of th builder object can be completely
404 @see «Builder.importedName»
408 override protected String formatDataForJavaDoc(GeneratedType type) {
409 val typeDescription = createDescription(type)
412 «IF !typeDescription.nullOrEmpty»
418 private def generateAugmentation() '''
419 @«SuppressWarnings.importedName»({ "unchecked", "checkstyle:methodTypeParameterName"})
420 public <E$$ extends «augmentType.importedName»> E$$ «AUGMENTABLE_AUGMENTATION_NAME»(«Class.importedName»<E$$> augmentationType) {
421 return (E$$) «AUGMENTATION_FIELD».get(«CodeHelpers.importedName».nonNullValue(augmentationType, "augmentationType"));
425 override protected generateCopyKeys(List<GeneratedProperty> keyProps) '''
426 this.key = base.«BindingMapping.IDENTIFIABLE_KEY_NAME»();
427 «FOR field : keyProps»
428 this.«field.fieldName» = base.«field.getterMethodName»();
432 override protected generateCopyAugmentation(Type implType) {
433 val augmentationHolderRef = AugmentationHolder.importedName
434 val typeRef = targetType.importedName
435 val hashMapRef = HashMap.importedName
436 val augmentTypeRef = augmentType.importedName
438 if (base instanceof «augmentationHolderRef») {
439 @SuppressWarnings("unchecked")
440 «Map.importedName»<«Class.importedName»<? extends «augmentTypeRef»>, «augmentTypeRef»> aug =((«augmentationHolderRef»<«typeRef»>) base).augmentations();
441 if (!aug.isEmpty()) {
442 this.«AUGMENTATION_FIELD» = new «hashMapRef»<>(aug);
448 private static def hasNonDefaultMethods(GeneratedType type) {
449 !type.methodDefinitions.isEmpty && type.methodDefinitions.exists([def | !def.isDefault])
452 private static def nonDefaultMethods(GeneratedType type) {
453 type.methodDefinitions.filter([def | !def.isDefault])