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.yangtools.sal.java.api.generator
10 import com.google.common.collect.ImmutableSortedSet
11 import com.google.common.collect.Range
12 import java.util.ArrayList
13 import java.util.Arrays
14 import java.util.Collection
15 import java.util.Collections
16 import java.util.HashMap
17 import java.util.HashSet
18 import java.util.LinkedHashSet
22 import org.opendaylight.yangtools.binding.generator.util.ReferencedTypeImpl
23 import org.opendaylight.yangtools.binding.generator.util.Types
24 import org.opendaylight.yangtools.binding.generator.util.generated.type.builder.GeneratedTOBuilderImpl
25 import org.opendaylight.yangtools.sal.binding.model.api.ConcreteType
26 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedProperty
27 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedTransferObject
28 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedType
29 import org.opendaylight.yangtools.sal.binding.model.api.MethodSignature
30 import org.opendaylight.yangtools.sal.binding.model.api.Type
31 import org.opendaylight.yangtools.yang.binding.Augmentable
32 import org.opendaylight.yangtools.yang.binding.DataObject
33 import org.opendaylight.yangtools.yang.binding.Identifiable
34 import org.opendaylight.yangtools.concepts.Builder
35 import org.opendaylight.yangtools.yang.binding.AugmentationHolder
38 * Template for generating JAVA builder classes.
41 class BuilderTemplate extends BaseTemplate {
44 * Constant with the name of the concrete method.
46 val static GET_AUGMENTATION_METHOD_NAME = "getAugmentation"
49 * Constant with the suffix for builder classes.
51 val static BUILDER = 'Builder'
54 * Constant with the name of the BuilderFor interface
56 val static BUILDERFOR = Builder.simpleName;
59 * Constant with suffix for the classes which are generated from the builder classes.
61 val static IMPL = 'Impl'
64 * Generated property is set if among methods is found one with the name GET_AUGMENTATION_METHOD_NAME
66 var GeneratedProperty augmentField
69 * Set of class attributes (fields) which are derived from the getter methods names
71 val Set<GeneratedProperty> properties
73 private static val METHOD_COMPARATOR = new AlphabeticallyTypeMemberComparator<MethodSignature>();
76 * Constructs new instance of this class.
77 * @throws IllegalArgumentException if <code>genType</code> equals <code>null</code>
79 new(GeneratedType genType) {
81 this.properties = propertiesFromMethods(createMethods)
82 importMap.put(Builder.simpleName, Builder.package.name)
86 * Returns set of method signature instances which contains all the methods of the <code>genType</code>
87 * and all the methods of the implemented interfaces.
89 * @returns set of method signature instances
91 def private Set<MethodSignature> createMethods() {
92 val Set<MethodSignature> methods = new LinkedHashSet();
93 methods.addAll(type.methodDefinitions)
94 collectImplementedMethods(methods, type.implements)
95 val Set<MethodSignature> sortedMethods = ImmutableSortedSet.orderedBy(METHOD_COMPARATOR).addAll(methods).build()
101 * Adds to the <code>methods</code> set all the methods of the <code>implementedIfcs</code>
102 * and recursively their implemented interfaces.
104 * @param methods set of method signatures
105 * @param implementedIfcs list of implemented interfaces
107 def private void collectImplementedMethods(Set<MethodSignature> methods, List<Type> implementedIfcs) {
108 if (implementedIfcs == null || implementedIfcs.empty) {
111 for (implementedIfc : implementedIfcs) {
112 if ((implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))) {
113 val ifc = implementedIfc as GeneratedType
114 methods.addAll(ifc.methodDefinitions)
115 collectImplementedMethods(methods, ifc.implements)
116 } else if (implementedIfc.fullyQualifiedName == Augmentable.name) {
117 for (m : Augmentable.methods) {
118 if (m.name == GET_AUGMENTATION_METHOD_NAME) {
119 val fullyQualifiedName = m.returnType.name
120 val pkg = fullyQualifiedName.package
121 val name = fullyQualifiedName.name
122 val tmpGenTO = new GeneratedTOBuilderImpl(pkg, name)
123 val refType = new ReferencedTypeImpl(pkg, name)
124 val generic = new ReferencedTypeImpl(type.packageName, type.name)
125 val parametrizedReturnType = Types.parameterizedTypeFor(refType, generic)
126 tmpGenTO.addMethod(m.name).setReturnType(parametrizedReturnType)
127 augmentField = tmpGenTO.toInstance.methodDefinitions.first.propertyFromGetter
135 * Returns the first element of the list <code>elements</code>.
137 * @param list of elements
139 def private <E> first(List<E> elements) {
144 * Returns the name of the package from <code>fullyQualifiedName</code>.
146 * @param fullyQualifiedName string with fully qualified type name (package + type)
147 * @return string with the package name
149 def private String getPackage(String fullyQualifiedName) {
150 val lastDotIndex = fullyQualifiedName.lastIndexOf(Constants.DOT)
151 return if (lastDotIndex == -1) "" else fullyQualifiedName.substring(0, lastDotIndex)
155 * Returns the name of tye type from <code>fullyQualifiedName</code>
157 * @param fullyQualifiedName string with fully qualified type name (package + type)
158 * @return string with the name of the type
160 def private String getName(String fullyQualifiedName) {
161 val lastDotIndex = fullyQualifiedName.lastIndexOf(Constants.DOT)
162 return if (lastDotIndex == -1) fullyQualifiedName else fullyQualifiedName.substring(lastDotIndex + 1)
166 * Creates set of generated property instances from getter <code>methods</code>.
168 * @param set of method signature instances which should be transformed to list of properties
169 * @return set of generated property instances which represents the getter <code>methods</code>
171 def private propertiesFromMethods(Collection<MethodSignature> methods) {
172 if (methods == null || methods.isEmpty()) {
173 return Collections.emptySet
175 val Set<GeneratedProperty> result = new LinkedHashSet
177 val createdField = m.propertyFromGetter
178 if (createdField != null) {
179 result.add(createdField)
186 * Creates generated property instance from the getter <code>method</code> name and return type.
188 * @param method method signature from which is the method name and return type obtained
189 * @return generated property instance for the getter <code>method</code>
190 * @throws IllegalArgumentException<ul>
191 * <li>if the <code>method</code> equals <code>null</code></li>
192 * <li>if the name of the <code>method</code> equals <code>null</code></li>
193 * <li>if the name of the <code>method</code> is empty</li>
194 * <li>if the return type of the <code>method</code> equals <code>null</code></li>
197 def private GeneratedProperty propertyFromGetter(MethodSignature method) {
198 if (method == null || method.name == null || method.name.empty || method.returnType == null) {
199 throw new IllegalArgumentException("Method, method name, method return type reference cannot be NULL or empty!")
202 if(Types.BOOLEAN.equals(method.returnType)) {
205 if (method.name.startsWith(prefix)) {
206 val fieldName = method.getName().substring(prefix.length()).toFirstLower
207 val tmpGenTO = new GeneratedTOBuilderImpl("foo", "foo")
208 tmpGenTO.addProperty(fieldName).setReturnType(method.returnType)
209 return tmpGenTO.toInstance.properties.first
214 * Template method which generates JAVA class body for builder class and for IMPL class.
216 * @return string with JAVA source code
219 «wrapToDocumentation(formatDataForJavaDoc(type))»
220 public class «type.name»«BUILDER» implements «BUILDERFOR» <«type.importedName»> {
222 «generateFields(false)»
224 «generateAugmentField(false)»
226 «generateConstructorsFromIfcs(type)»
228 «generateCopyConstructor(false)»
230 «generateMethodFieldsFrom(type)»
232 «generateGetters(false)»
236 public «type.name» build() {
237 return new «type.name»«IMPL»(this);
240 private static final class «type.name»«IMPL» implements «type.name» {
242 «implementedInterfaceGetter»
244 «generateFields(true)»
246 «generateAugmentField(true)»
248 «generateCopyConstructor(true)»
250 «generateGetters(true)»
256 «generateToString(properties)»
263 * Generate default constructor and constructor for every implemented interface from uses statements.
265 def private generateConstructorsFromIfcs(Type type) '''
266 public «type.name»«BUILDER»() {
268 «IF (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject))»
269 «val ifc = type as GeneratedType»
270 «FOR impl : ifc.implements»
271 «generateConstructorFromIfc(impl)»
277 * Generate constructor with argument of given type.
279 def private Object generateConstructorFromIfc(Type impl) '''
280 «IF (impl instanceof GeneratedType)»
281 «IF !(impl.methodDefinitions.empty)»
282 public «type.name»«BUILDER»(«impl.fullyQualifiedName» arg) {
283 «printConstructorPropertySetter(impl)»
286 «FOR implTypeImplement : impl.implements»
287 «generateConstructorFromIfc(implTypeImplement)»
292 def private Object printConstructorPropertySetter(Type implementedIfc) '''
293 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
294 «val ifc = implementedIfc as GeneratedType»
295 «FOR getter : ifc.methodDefinitions»
296 this._«getter.propertyNameFromGetter» = arg.«getter.name»();
298 «FOR impl : ifc.implements»
299 «printConstructorPropertySetter(impl)»
305 * Generate 'fieldsFrom' method to set builder properties based on type of given argument.
307 def private generateMethodFieldsFrom(Type type) '''
308 «IF (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject))»
309 «val ifc = type as GeneratedType»
310 «IF ifc.hasImplementsFromUses»
311 «val List<Type> done = ifc.getBaseIfcs»
312 «generateMethodFieldsFromComment(ifc)»
313 public void fieldsFrom(«DataObject.importedName» arg) {
314 boolean isValidArg = false;
315 «FOR impl : ifc.getAllIfcs»
316 «generateIfCheck(impl, done)»
319 throw new IllegalArgumentException(
320 "expected one of: «ifc.getAllIfcs.toListOfNames» \n" +
329 def private generateMethodFieldsFromComment(GeneratedType type) '''
331 *Set fields from given grouping argument. Valid argument is instance of one of following types:
333 «FOR impl : type.getAllIfcs»
334 * <li>«impl.fullyQualifiedName»</li>
338 * @param arg grouping object
339 * @throws IllegalArgumentException if given argument is none of valid types
344 * Method is used to find out if given type implements any interface from uses.
346 def boolean hasImplementsFromUses(GeneratedType type) {
348 for (impl : type.getAllIfcs) {
349 if ((impl instanceof GeneratedType) && !((impl as GeneratedType).methodDefinitions.empty)) {
356 def private generateIfCheck(Type impl, List<Type> done) '''
357 «IF (impl instanceof GeneratedType) && !((impl as GeneratedType).methodDefinitions.empty)»
358 «val implType = impl as GeneratedType»
359 if (arg instanceof «implType.fullyQualifiedName») {
360 «printPropertySetter(implType)»
366 def private printPropertySetter(Type implementedIfc) '''
367 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
368 «val ifc = implementedIfc as GeneratedType»
369 «FOR getter : ifc.methodDefinitions»
370 this._«getter.propertyNameFromGetter» = ((«implementedIfc.fullyQualifiedName»)arg).«getter.name»();
375 private def List<Type> getBaseIfcs(GeneratedType type) {
376 val List<Type> baseIfcs = new ArrayList();
377 for (ifc : type.implements) {
378 if (ifc instanceof GeneratedType && !(ifc as GeneratedType).methodDefinitions.empty) {
385 private def Set<Type> getAllIfcs(Type type) {
386 val Set<Type> baseIfcs = new HashSet()
387 if (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject)) {
388 val ifc = type as GeneratedType
389 for (impl : ifc.implements) {
390 if (impl instanceof GeneratedType && !(impl as GeneratedType).methodDefinitions.empty) {
393 baseIfcs.addAll(impl.getAllIfcs)
399 private def List<String> toListOfNames(Collection<Type> types) {
400 val List<String> names = new ArrayList
402 names.add(type.fullyQualifiedName)
408 * Template method which generates class attributes.
410 * @param boolean value which specify whether field is|isn't final
411 * @return string with class attributes and their types
413 def private generateFields(boolean _final) '''
414 «IF properties !== null»
416 private«IF _final» final«ENDIF» «f.returnType.importedName» «f.fieldName»;
417 «val restrictions = f.returnType.restrictions»
418 «IF !_final && restrictions != null»
419 «IF !(restrictions.lengthConstraints.empty)»
420 private static «List.importedName»<«Range.importedName»<«f.returnType.importedNumber»>> «f.fieldName»_length;
422 «IF !(restrictions.rangeConstraints.empty)»
423 private static «List.importedName»<«Range.importedName»<«f.returnType.importedNumber»>> «f.fieldName»_range;
430 def private generateAugmentField(boolean isPrivate) '''
431 «IF augmentField != null»
432 «IF isPrivate»private «ENDIF»«Map.importedName»<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> «augmentField.name» = «Collections.importedName».emptyMap();
437 * Template method which generates setter methods
439 * @return string with the setter methods
441 def private generateSetters() '''
442 «FOR field : properties SEPARATOR '\n'»
443 «val length = field.fieldName + "_length"»
444 «val range = field.fieldName + "_range"»
445 public «type.name»«BUILDER» set«field.name.toFirstUpper»(«field.returnType.importedName» value) {
446 «generateRestrictions(field, "value", length, range)»
447 this.«field.fieldName» = value;
450 «generateLengthMethod(length, field.returnType, type.name+BUILDER, length)»
451 «generateRangeMethod(range, field.returnType.restrictions, field.returnType, type.name+BUILDER, range)»
453 «IF augmentField != null»
455 public «type.name»«BUILDER» add«augmentField.name.toFirstUpper»(«Class.importedName»<? extends «augmentField.returnType.importedName»> augmentationType, «augmentField.returnType.importedName» augmentation) {
456 if (augmentation == null) {
457 return remove«augmentField.name.toFirstUpper»(augmentationType);
460 if (!(this.«augmentField.name» instanceof «HashMap.importedName»)) {
461 this.«augmentField.name» = new «HashMap.importedName»<>();
464 this.«augmentField.name».put(augmentationType, augmentation);
468 public «type.name»«BUILDER» remove«augmentField.name.toFirstUpper»(«Class.importedName»<? extends «augmentField.returnType.importedName»> augmentationType) {
469 if (this.«augmentField.name» instanceof «HashMap.importedName») {
470 this.«augmentField.name».remove(augmentationType);
477 def generateRestrictions(GeneratedProperty field, String paramName, String lengthGetter, String rangeGetter) '''
478 «val Type type = field.returnType»
479 «IF type instanceof ConcreteType»
480 «createRestrictions(type, paramName, type.name.contains("["), lengthGetter, rangeGetter)»
481 «ELSEIF type instanceof GeneratedTransferObject»
482 «createRestrictions(type, paramName, isArrayType(type as GeneratedTransferObject), lengthGetter, rangeGetter)»
486 def private createRestrictions(Type type, String paramName, boolean isArray, String lengthGetter, String rangeGetter) '''
487 «val restrictions = type.getRestrictions»
488 «IF restrictions !== null»
489 «val boolean isNestedType = !(type instanceof ConcreteType)»
490 «IF !restrictions.lengthConstraints.empty»
491 «generateLengthRestriction(type, paramName, lengthGetter, isNestedType, isArray)»
493 «IF !restrictions.rangeConstraints.empty»
494 «generateRangeRestriction(type, paramName, rangeGetter, isNestedType)»
499 def private generateLengthRestriction(Type type, String paramName, String getterName, boolean isNestedType, boolean isArray) '''
500 «val restrictions = type.getRestrictions»
501 if («paramName» != null) {
502 «val clazz = restrictions.lengthConstraints.iterator.next.min.class»
503 «printLengthConstraint(type, clazz, paramName, isNestedType, isArray)»
504 boolean isValidLength = false;
505 for («Range.importedName»<«clazz.importedNumber»> r : «getterName»()) {
506 if (r.contains(_constraint)) {
507 isValidLength = true;
510 if (!isValidLength) {
511 throw new IllegalArgumentException(String.format("Invalid length: %s, expected: %s.", «paramName», «getterName»));
516 def private generateRangeRestriction(Type type, String paramName, String getterName, boolean isNestedType) '''
517 if («paramName» != null) {
518 «printRangeConstraint(type, paramName, isNestedType)»
519 boolean isValidRange = false;
520 for («Range.importedName»<«type.importedNumber»> r : «getterName»()) {
521 if (r.contains(_constraint)) {
526 throw new IllegalArgumentException(String.format("Invalid range: %s, expected: %s.", «paramName», «getterName»));
531 def private CharSequence generateCopyConstructor(boolean impl) '''
532 «IF impl»private«ELSE»public«ENDIF» «type.name»«IF impl»«IMPL»«ELSE»«BUILDER»«ENDIF»(«type.name»«IF impl»«BUILDER»«ENDIF» base) {
533 «val allProps = new ArrayList(properties)»
534 «val isList = implementsIfc(type, Types.parameterizedTypeFor(Types.typeForClass(Identifiable), type))»
535 «val keyType = type.getKey»
536 «IF isList && keyType != null»
537 «val keyProps = new ArrayList((keyType as GeneratedTransferObject).properties)»
538 «Collections.sort(keyProps,
540 return p1.name.compareTo(p2.name)
543 «FOR field : keyProps»
544 «removeProperty(allProps, field.name)»
546 «removeProperty(allProps, "key")»
547 if (base.getKey() == null) {
548 this._key = new «keyType.importedName»(
549 «FOR keyProp : keyProps SEPARATOR ", "»
550 base.«keyProp.getterMethodName»()
553 «FOR field : keyProps»
554 this.«field.fieldName» = base.«field.getterMethodName»();
557 this._key = base.getKey();
558 «FOR field : keyProps»
559 this.«field.fieldName» = _key.«field.getterMethodName»();
563 «FOR field : allProps»
564 this.«field.fieldName» = base.«field.getterMethodName»();
566 «IF augmentField != null»
568 switch (base.«augmentField.name».size()) {
570 this.«augmentField.name» = «Collections.importedName».emptyMap();
573 final «Map.importedName».Entry<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> e = base.«augmentField.name».entrySet().iterator().next();
574 this.«augmentField.name» = «Collections.importedName».<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»>singletonMap(e.getKey(), e.getValue());
577 this.«augmentField.name» = new «HashMap.importedName»<>(base.«augmentField.name»);
580 if (base instanceof «type.name»«IMPL») {
581 «type.name»«IMPL» impl = («type.name»«IMPL») base;
582 if (!impl.«augmentField.name».isEmpty()) {
583 this.«augmentField.name» = new «HashMap.importedName»<>(impl.«augmentField.name»);
585 } else if (base instanceof «AugmentationHolder.importedName») {
586 @SuppressWarnings("unchecked")
587 «AugmentationHolder.importedName»<«type.importedName»> casted =(«AugmentationHolder.importedName»<«type.importedName»>) base;
588 if (!casted.augmentations().isEmpty()) {
589 this.«augmentField.name» = new «HashMap.importedName»<>(casted.augmentations());
597 private def boolean implementsIfc(GeneratedType type, Type impl) {
598 for (Type ifc : type.implements) {
599 if (ifc.equals(impl)) {
606 private def Type getKey(GeneratedType type) {
607 for (m : type.methodDefinitions) {
608 if ("getKey".equals(m.name)) {
615 private def void removeProperty(Collection<GeneratedProperty> props, String name) {
616 var GeneratedProperty toRemove = null
618 if (p.name.equals(name)) {
622 if (toRemove != null) {
623 props.remove(toRemove);
628 * Template method which generate getter methods for IMPL class.
630 * @return string with getter methods
632 def private generateGetters(boolean addOverride) '''
633 «IF !properties.empty»
634 «FOR field : properties SEPARATOR '\n'»
635 «IF addOverride»@Override«ENDIF»
639 «IF augmentField != null»
641 @SuppressWarnings("unchecked")
642 «IF addOverride»@Override«ENDIF»
643 public <E extends «augmentField.returnType.importedName»> E get«augmentField.name.toFirstUpper»(«Class.importedName»<E> augmentationType) {
644 if (augmentationType == null) {
645 throw new IllegalArgumentException("Augmentation Type reference cannot be NULL!");
647 return (E) «augmentField.name».get(augmentationType);
653 * Template method which generates the method <code>hashCode()</code>.
655 * @return string with the <code>hashCode()</code> method definition in JAVA format
657 def protected generateHashCode() '''
658 «IF !properties.empty || augmentField != null»
660 public int hashCode() {
661 final int prime = 31;
663 «FOR property : properties»
664 «IF property.returnType.name.contains("[")»
665 result = prime * result + ((«property.fieldName» == null) ? 0 : «Arrays.importedName».hashCode(«property.fieldName»));
667 result = prime * result + ((«property.fieldName» == null) ? 0 : «property.fieldName».hashCode());
670 «IF augmentField != null»
671 result = prime * result + ((«augmentField.name» == null) ? 0 : «augmentField.name».hashCode());
679 * Template method which generates the method <code>equals()</code>.
681 * @return string with the <code>equals()</code> method definition in JAVA format
683 def protected generateEquals() '''
684 «IF !properties.empty || augmentField != null»
686 public boolean equals(«Object.importedName» obj) {
690 if (!(obj instanceof «DataObject.importedName»)) {
693 if (!«type.importedName».class.equals(((«DataObject.importedName»)obj).getImplementedInterface())) {
696 «type.importedName» other = («type.importedName»)obj;
697 «FOR property : properties»
698 «val fieldName = property.fieldName»
699 if («fieldName» == null) {
700 if (other.«property.getterMethodName»() != null) {
703 «IF property.returnType.name.contains("[")»
704 } else if(!«Arrays.importedName».equals(«fieldName», other.«property.getterMethodName»())) {
706 } else if(!«fieldName».equals(other.«property.getterMethodName»())) {
711 «IF augmentField != null»
712 if (getClass() == obj.getClass()) {
713 // Simple case: we are comparing against self
714 «type.name»«IMPL» otherImpl = («type.name»«IMPL») obj;
715 «val fieldName = augmentField.name»
716 if («fieldName» == null) {
717 if (otherImpl.«fieldName» != null) {
720 } else if(!«fieldName».equals(otherImpl.«fieldName»)) {
724 // Hard case: compare our augments with presence there...
725 for («Map.importedName».Entry<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> e : «augmentField.name».entrySet()) {
726 if (!e.getValue().equals(other.getAugmentation(e.getKey()))) {
730 // .. and give the other one the chance to do the same
731 if (!obj.equals(this)) {
741 def override generateToString(Collection<GeneratedProperty> properties) '''
742 «IF !(properties === null)»
744 public «String.importedName» toString() {
745 «StringBuilder.importedName» builder = new «StringBuilder.importedName» ("«type.name» [");
746 boolean first = true;
748 «FOR property : properties»
749 if («property.fieldName» != null) {
753 builder.append(", ");
755 builder.append("«property.fieldName»=");
756 «IF property.returnType.name.contains("[")»
757 builder.append(«Arrays.importedName».toString(«property.fieldName»));
759 builder.append(«property.fieldName»);
763 «IF augmentField != null»
767 builder.append(", ");
769 builder.append("«augmentField.name»=");
770 builder.append(«augmentField.name».values());
772 return builder.append(']').toString();
777 override protected getFullyQualifiedName() {
778 '''«type.fullyQualifiedName»Builder'''.toString
781 def implementedInterfaceGetter() '''
782 public «Class.importedName»<«type.importedName»> getImplementedInterface() {
783 return «type.importedName».class;
787 private def createDescription(GeneratedType type) {
789 Class that builds {@link «type.importedName»} instances.
791 @see «type.importedName»
795 override def protected String formatDataForJavaDoc(GeneratedType type) {
796 val typeDescription = createDescription(type)
799 «IF !typeDescription.nullOrEmpty»