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 java.util.Objects.requireNonNull
11 import static org.opendaylight.mdsal.binding.model.ri.BaseYangTypes.BINARY_TYPE
12 import static org.opendaylight.mdsal.binding.model.ri.BaseYangTypes.BOOLEAN_TYPE
13 import static org.opendaylight.mdsal.binding.model.ri.BaseYangTypes.DECIMAL64_TYPE
14 import static org.opendaylight.mdsal.binding.model.ri.BaseYangTypes.EMPTY_TYPE
15 import static org.opendaylight.mdsal.binding.model.ri.BaseYangTypes.INSTANCE_IDENTIFIER
16 import static org.opendaylight.mdsal.binding.model.ri.BaseYangTypes.INT16_TYPE
17 import static org.opendaylight.mdsal.binding.model.ri.BaseYangTypes.INT32_TYPE
18 import static org.opendaylight.mdsal.binding.model.ri.BaseYangTypes.INT64_TYPE
19 import static org.opendaylight.mdsal.binding.model.ri.BaseYangTypes.INT8_TYPE
20 import static org.opendaylight.mdsal.binding.model.ri.BaseYangTypes.STRING_TYPE
21 import static org.opendaylight.mdsal.binding.model.ri.BaseYangTypes.UINT16_TYPE
22 import static org.opendaylight.mdsal.binding.model.ri.BaseYangTypes.UINT32_TYPE
23 import static org.opendaylight.mdsal.binding.model.ri.BaseYangTypes.UINT64_TYPE
24 import static org.opendaylight.mdsal.binding.model.ri.BaseYangTypes.UINT8_TYPE
25 import static org.opendaylight.mdsal.binding.model.ri.BindingTypes.SCALAR_TYPE_OBJECT
26 import static org.opendaylight.mdsal.binding.model.ri.BindingTypes.BITS_TYPE_OBJECT
27 import static org.opendaylight.mdsal.binding.model.ri.Types.STRING;
28 import static extension org.apache.commons.text.StringEscapeUtils.escapeJava
30 import com.google.common.base.Preconditions
31 import com.google.common.collect.ImmutableList
32 import com.google.common.collect.ImmutableSet
33 import com.google.common.collect.Lists
34 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings
35 import java.util.ArrayList
36 import java.util.Base64;
37 import java.util.Collection
38 import java.util.Comparator
42 import javax.management.ConstructorParameters
43 import org.opendaylight.mdsal.binding.model.api.ConcreteType
44 import org.opendaylight.mdsal.binding.model.api.Constant
45 import org.opendaylight.mdsal.binding.model.api.Enumeration
46 import org.opendaylight.mdsal.binding.model.api.GeneratedProperty
47 import org.opendaylight.mdsal.binding.model.api.GeneratedTransferObject
48 import org.opendaylight.mdsal.binding.model.api.JavaTypeName
49 import org.opendaylight.mdsal.binding.model.api.Restrictions
50 import org.opendaylight.mdsal.binding.model.api.Type
51 import org.opendaylight.mdsal.binding.model.ri.TypeConstants
52 import org.opendaylight.mdsal.binding.model.ri.Types
53 import org.opendaylight.yangtools.yang.binding.contract.Naming
54 import org.opendaylight.yangtools.yang.common.Empty
55 import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition
58 * Template for generating JAVA class.
60 class ClassTemplate extends BaseTemplate {
61 static val Comparator<GeneratedProperty> PROP_COMPARATOR = Comparator.comparing([prop | prop.name])
62 static val VALUEOF_TYPES = Set.of(
74 * {@code java.lang.Boolean} as a JavaTypeName.
76 static val BOOLEAN = JavaTypeName.create(Boolean)
79 * {@code com.google.common.collect.ImmutableSet} as a JavaTypeName.
81 static val IMMUTABLE_SET = JavaTypeName.create(ImmutableSet)
83 protected val List<GeneratedProperty> properties
84 protected val List<GeneratedProperty> finalProperties
85 protected val List<GeneratedProperty> parentProperties
86 protected val List<GeneratedProperty> allProperties
87 protected val Restrictions restrictions
90 * List of enumeration which are generated as JAVA enum type.
92 protected val List<Enumeration> enums
95 * List of constant instances which are generated as JAVA public static final attributes.
97 protected val List<Constant> consts
99 protected val GeneratedTransferObject genTO
101 val AbstractRangeGenerator<?> rangeGenerator
104 * Creates instance of this class with concrete <code>genType</code>.
106 * @param genType generated transfer object which will be transformed to JAVA class source code
108 new(GeneratedTransferObject genType) {
109 this(new TopLevelJavaGeneratedType(genType), genType)
113 * Creates instance of this class with concrete <code>genType</code>.
115 * @param genType generated transfer object which will be transformed to JAVA class source code
117 new(AbstractJavaGeneratedType javaType, GeneratedTransferObject genType) {
118 super(javaType, genType)
120 this.properties = genType.properties
121 this.finalProperties = GeneratorUtil.resolveReadOnlyPropertiesFromTO(genTO.properties)
122 this.parentProperties = GeneratorUtil.getPropertiesOfAllParents(genTO)
123 this.restrictions = genType.restrictions
125 val sorted = new ArrayList();
126 sorted.addAll(properties);
127 sorted.addAll(parentProperties);
128 sorted.sort(PROP_COMPARATOR);
130 this.allProperties = sorted
131 this.enums = genType.enumerations
132 this.consts = genType.constantDefinitions
134 if (restrictions !== null && restrictions.rangeConstraint.present) {
135 rangeGenerator = requireNonNull(AbstractRangeGenerator.forType(TypeUtils.encapsulatedValueType(genType)))
137 rangeGenerator = null
142 * Generates JAVA class source code (class body only).
144 * @return string with JAVA class body source code
146 def CharSequence generateAsInnerClass() {
147 return generateBody(true)
150 override protected body() {
155 * Template method which generates class body.
157 * @param isInnerClass boolean value which specify if generated class is|isn't inner
158 * @return string with class source code in JAVA format
160 def protected generateBody(boolean isInnerClass) '''
161 «type.formatDataForJavaDoc.wrapToDocumentation»
162 «annotationDeclaration»
164 «generatedAnnotation»
166 «generateClassDeclaration(isInnerClass)» {
168 «innerClassesDeclarations»
170 «constantsDeclarations»
173 «IF restrictions !== null»
174 «IF restrictions.lengthConstraint.present»
175 «LengthGenerator.generateLengthChecker("_value", TypeUtils.encapsulatedValueType(genTO),
176 restrictions.lengthConstraint.orElseThrow, this)»
178 «IF restrictions.rangeConstraint.present»
179 «rangeGenerator.generateRangeChecker("_value", restrictions.rangeConstraint.orElseThrow, this)»
189 «IF isBitsTypeObject»
190 «validNamesAndValues»
197 «generateToString(genTO.toStringIdentifiers)»
202 def protected propertyMethods() {
203 if (properties.empty) {
206 isScalarTypeObject ? scalarTypeObjectValue(properties.get(0)) : defaultProperties
209 def private isScalarTypeObject() {
210 for (impl : genTO.implements) {
211 if (SCALAR_TYPE_OBJECT.identifier.equals(impl.identifier)) {
218 def private isBitsTypeObject() {
220 while (wlk !== null) {
221 for (impl : wlk.implements) {
222 if (BITS_TYPE_OBJECT.identifier.equals(impl.identifier)) {
231 def private defaultProperties() '''
232 «FOR field : properties SEPARATOR "\n"»
240 def private scalarTypeObjectValue(GeneratedProperty field) '''
241 @«OVERRIDE.importedName»
242 public «field.returnType.importedName» «Naming.SCALAR_TYPE_OBJECT_GET_VALUE_NAME»() {
243 return «field.fieldName»«field.cloneCall»;
247 def private validNamesAndValues() {
249 if (TypeConstants.VALID_NAMES_NAME.equals(c.name)) {
250 return validNamesAndValues(c.value as BitsTypeDefinition)
256 def private validNamesAndValues(BitsTypeDefinition typedef) '''
258 @«OVERRIDE.importedName»
259 public «IMMUTABLE_SET.importedName»<«STRING.importedName»> validNames() {
260 return «TypeConstants.VALID_NAMES_NAME»;
263 @«OVERRIDE.importedName»
264 public boolean[] values() {
265 return new boolean[] {
266 «FOR bit : typedef.bits SEPARATOR ','»
267 «Naming.getPropertyName(bit.name).getterMethodName»()
274 * Template method which generates inner classes inside this interface.
276 * @return string with the source code for inner classes in JAVA format
278 def protected innerClassesDeclarations() '''
279 «IF !type.enclosedTypes.empty»
280 «FOR innerClass : type.enclosedTypes SEPARATOR "\n"»
281 «generateInnerClass(innerClass)»
286 def protected constructors() '''
288 «genUnionConstructor»
289 «ELSEIF genTO.typedef && allProperties.size == 1 && allProperties.get(0).name.equals(TypeConstants.VALUE_PROP)»
292 «allValuesConstructor»
295 «IF !allProperties.empty»
298 «IF properties.empty && !parentProperties.empty »
303 def allValuesConstructor() '''
304 public «type.name»(«allProperties.asArgumentsDeclaration») {
305 «IF !parentProperties.empty»
306 super(«parentProperties.asArguments»);
308 «FOR p : allProperties»
309 «generateRestrictions(type, p.fieldName, p.returnType)»
313 «val fieldName = p.fieldName»
314 «IF p.returnType.name.endsWith("[]")»
315 this.«fieldName» = «fieldName» == null ? null : «fieldName».clone();
317 this.«fieldName» = «fieldName»;
323 def private typedefConstructor() '''
324 @«ConstructorParameters.importedName»("«TypeConstants.VALUE_PROP»")
325 public «type.name»(«allProperties.asArgumentsDeclaration») {
326 «IF !parentProperties.empty»
327 super(«parentProperties.asArguments»);
329 «FOR p : allProperties»
330 «generateRestrictions(type, p.fieldName, p.returnType)»
333 * If we have patterns, we need to apply them to the value field. This is a sad consequence of how this code is
336 «CODEHELPERS.importedName».requireValue(_value);
337 «genPatternEnforcer("_value")»
340 «val fieldName = p.fieldName»
341 this.«fieldName» = «fieldName»«p.cloneCall»;
346 def protected genUnionConstructor() '''
347 «FOR p : allProperties»
348 «val List<GeneratedProperty> other = new ArrayList(properties)»
350 «genConstructor(p, other)»
355 def protected genConstructor(GeneratedProperty property, Iterable<GeneratedProperty> other) '''
356 public «type.name»(«property.returnType.importedName + " " + property.name») {
357 «IF !parentProperties.empty»
358 super(«parentProperties.asArguments»);
361 «val fieldName = property.fieldName»
362 «generateRestrictions(type, fieldName, property.returnType)»
364 this.«fieldName» = «property.name»;
366 this.«p.fieldName» = null;
371 def private genPatternEnforcer(String ref) '''
373 «IF TypeConstants.PATTERN_CONSTANT_NAME.equals(c.name)»
374 «CODEHELPERS.importedName».checkPattern(«ref», «Constants.MEMBER_PATTERN_LIST», «Constants.MEMBER_REGEX_LIST»);
379 def private static paramValue(Type returnType, String paramName) {
380 if (returnType instanceof ConcreteType) {
383 return paramName + ".getValue()"
387 def generateRestrictions(Type type, String paramName, Type returnType) '''
388 «val restrictions = type.restrictions»
389 «IF restrictions !== null»
390 «IF restrictions.lengthConstraint.present || restrictions.rangeConstraint.present»
391 if («paramName» != null) {
392 «IF restrictions.lengthConstraint.present»
393 «LengthGenerator.generateLengthCheckerCall(paramName, paramValue(returnType, paramName))»
395 «IF restrictions.rangeConstraint.present»
396 «rangeGenerator.generateRangeCheckerCall(paramName, paramValue(returnType, paramName))»
403 def protected copyConstructor() '''
405 * Creates a copy from Source Object.
407 * @param source Source object
409 public «type.name»(«type.name» source) {
410 «IF !parentProperties.empty»
414 «val fieldName = p.fieldName»
415 this.«fieldName» = source.«fieldName»;
420 def protected parentConstructor() '''
422 * Creates a new instance from «genTO.superType.importedName»
424 * @param source Source object
426 public «type.name»(«genTO.superType.importedName» source) {
428 «genPatternEnforcer("getValue()")»
432 def protected defaultInstance() '''
433 «IF genTO.typedef && !allProperties.empty && !genTO.unionType»
434 «val prop = allProperties.get(0)»
435 «val propType = prop.returnType»
436 «IF !(INSTANCE_IDENTIFIER.identifier.equals(propType.identifier))»
437 public static «genTO.name» getDefaultInstance(final String defaultValue) {
438 «IF propType.equals(Types.primitiveBooleanType())»
440 «ELSEIF VALUEOF_TYPES.contains(propType)»
441 return new «genTO.name»(«propType.importedName».valueOf(defaultValue));
442 «ELSEIF STRING_TYPE.equals(propType)»
443 return new «genTO.name»(defaultValue);
444 «ELSEIF BINARY_TYPE.equals(propType)»
445 return new «genTO.name»(«Base64.importedName».getDecoder().decode(defaultValue));
446 «ELSEIF EMPTY_TYPE.equals(propType)»
447 «Preconditions.importedName».checkArgument(defaultValue.isEmpty(), "Invalid value %s", defaultValue);
448 return new «genTO.name»(«Empty.importedName».value());
450 return new «genTO.name»(new «propType.importedName»(defaultValue));
457 @SuppressFBWarnings(value = "DLS_DEAD_LOCAL_STORE", justification = "FOR with SEPARATOR, not needing for value")
458 def protected bitsArgs() '''
459 «JU_LIST.importedName»<«STRING.importedName»> properties = «Lists.importedName».newArrayList(«allProperties.propsAsArgs»);
460 if (!properties.contains(defaultValue)) {
461 throw new «IAE.importedName»("invalid default parameter");
464 return new «genTO.name»(
465 «FOR prop : allProperties SEPARATOR ","»
466 properties.get(i++).equals(defaultValue) ? true : false
471 def protected propsAsArgs(Iterable<GeneratedProperty> properties) '''
472 «FOR prop : properties SEPARATOR ","»
478 * Template method which generates JAVA class declaration.
480 * @param isInnerClass boolean value which specify if generated class is|isn't inner
481 * @return string with class declaration in JAVA format
483 def protected generateClassDeclaration(boolean isInnerClass) '''
487 ELSEIF (type.abstract)»«
491 ENDIF»class «type.name»«
492 IF (genTO.superType !== null)»«
493 " extends "»«genTO.superType.importedName»«
495 «IF (!type.implements.empty)»«
497 FOR type : type.implements SEPARATOR ", "»«
504 * Template method which generates JAVA enum type.
506 * @return string with inner enum source code in JAVA format
508 def protected enumDeclarations() '''
510 «FOR e : enums SEPARATOR "\n"»
511 «new EnumTemplate(javaType.getEnclosedType(e.identifier), e).generateAsInnerClass»
516 def protected suidDeclaration() '''
517 «IF genTO.SUID !== null»
519 private static final long serialVersionUID = «genTO.SUID.value»L;
523 def protected annotationDeclaration() '''
524 «IF genTO.getAnnotations !== null»
525 «FOR e : genTO.getAnnotations»
532 * Template method which generates JAVA constants.
534 * @return string with constants in JAVA format
536 def protected constantsDeclarations() '''
539 «IF TypeConstants.PATTERN_CONSTANT_NAME.equals(c.name)»
540 «val cValue = c.value as Map<String, String>»
541 «val jurPatternRef = JUR_PATTERN.importedName»
542 public static final «JU_LIST.importedName»<String> «TypeConstants.PATTERN_CONSTANT_NAME» = «ImmutableList.importedName».of(«
543 FOR v : cValue.keySet SEPARATOR ", "»"«v.escapeJava»"«ENDFOR»);
544 «IF cValue.size == 1»
545 private static final «jurPatternRef» «Constants.MEMBER_PATTERN_LIST» = «jurPatternRef».compile(«TypeConstants.PATTERN_CONSTANT_NAME».get(0));
546 private static final String «Constants.MEMBER_REGEX_LIST» = "«cValue.values.iterator.next.escapeJava»";
548 private static final «jurPatternRef»[] «Constants.MEMBER_PATTERN_LIST» = «CODEHELPERS.importedName».compilePatterns(«TypeConstants.PATTERN_CONSTANT_NAME»);
549 private static final String[] «Constants.MEMBER_REGEX_LIST» = { «
550 FOR v : cValue.values SEPARATOR ", "»"«v.escapeJava»"«ENDFOR» };
552 «ELSEIF TypeConstants.VALID_NAMES_NAME.equals(c.name)»
553 «val cValue = c.value as BitsTypeDefinition»
554 «val immutableSet = IMMUTABLE_SET.importedName»
555 protected static final «immutableSet»<«STRING.importedName»> «TypeConstants.VALID_NAMES_NAME» = «immutableSet».of(«
556 FOR bit : cValue.bits SEPARATOR ", "»"«bit.name»"«ENDFOR»);
565 * Template method which generates JAVA class attributes.
567 * @return string with the class attributes in JAVA format
569 def protected generateFields() '''
570 «IF !properties.empty»
572 private«IF isReadOnly(f)» final«ENDIF» «f.returnType.importedName» «f.fieldName»;
577 protected def isReadOnly(GeneratedProperty field) {
578 return field.readOnly
582 * Template method which generates the method <code>hashCode()</code>.
584 * @return string with the <code>hashCode()</code> method definition in JAVA format
586 def protected generateHashCode() {
587 val props = genTO.hashCodeIdentifiers
588 val size = props.size
593 @«OVERRIDE.importedName»
594 public int hashCode() {
596 final int prime = 31;
598 «FOR property : props»
599 result = prime * result + «property.importedHashCodeUtilClass».hashCode(«property.fieldName»);
603 «val prop = props.get(0)»
604 «IF prop.returnType.equals(Types.primitiveBooleanType())»
605 return «BOOLEAN.importedName».hashCode(«prop.fieldName»);
607 return «CODEHELPERS.importedName».wrapperHashCode(«prop.fieldName»);
614 def private importedHashCodeUtilClass(GeneratedProperty prop) {
615 val propType = prop.returnType
616 if (propType.equals(Types.primitiveBooleanType)) {
617 return BOOLEAN.importedName
619 return propType.importedUtilClass
623 * Template method which generates the method <code>equals()</code>.
625 * @return string with the <code>equals()</code> method definition in JAVA format
627 def private generateEquals() '''
628 «IF !genTO.equalsIdentifiers.empty»
629 @«OVERRIDE.importedName»
630 public final boolean equals(«OBJECT.importedName» obj) {
631 return this == obj || obj instanceof «type.name» other
632 «FOR property : genTO.equalsIdentifiers»
633 «val fieldName = property.fieldName»
634 «val type = property.returnType»
635 «IF type.equals(Types.primitiveBooleanType)»
636 && «fieldName» == other.«fieldName»«
638 && «type.importedUtilClass».equals(«fieldName», other.«fieldName»)«
645 def private generateToString(Collection<GeneratedProperty> properties) '''
646 «IF !properties.empty»
647 @«OVERRIDE.importedName»
648 public «STRING.importedName» toString() {
649 final var helper = «MOREOBJECTS.importedName».toStringHelper(«type.importedName».class);
650 «FOR property : properties»
651 «CODEHELPERS.importedName».«property.valueAppender»(helper, "«property.name»", «property.fieldName»);
653 return helper.toString();
658 def private valueAppender(GeneratedProperty prop) {
659 if (prop.returnType.equals(Types.primitiveBooleanType())) {
665 def GeneratedProperty getPropByName(String name) {
666 for (GeneratedProperty prop : allProperties) {
667 if (prop.name.equals(name)) {