2 * Copyright (c) 2013 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.yang.types;
10 import static java.util.Objects.requireNonNull;
11 import static org.opendaylight.mdsal.binding.model.ri.BindingTypes.TYPE_OBJECT;
13 import com.google.common.base.CharMatcher;
14 import com.google.common.base.Preconditions;
15 import com.google.common.base.Strings;
16 import com.google.common.collect.ImmutableMap;
17 import com.google.common.collect.Iterables;
18 import java.util.ArrayList;
19 import java.util.Collection;
20 import java.util.Collections;
21 import java.util.HashMap;
22 import java.util.HashSet;
23 import java.util.Iterator;
24 import java.util.List;
26 import java.util.Optional;
28 import java.util.TreeMap;
29 import org.opendaylight.mdsal.binding.generator.BindingGeneratorUtil;
30 import org.opendaylight.mdsal.binding.generator.impl.reactor.SerialVersionHelper;
31 import org.opendaylight.mdsal.binding.model.api.AccessModifier;
32 import org.opendaylight.mdsal.binding.model.api.ConcreteType;
33 import org.opendaylight.mdsal.binding.model.api.Enumeration;
34 import org.opendaylight.mdsal.binding.model.api.GeneratedTransferObject;
35 import org.opendaylight.mdsal.binding.model.api.GeneratedType;
36 import org.opendaylight.mdsal.binding.model.api.JavaTypeName;
37 import org.opendaylight.mdsal.binding.model.api.Restrictions;
38 import org.opendaylight.mdsal.binding.model.api.Type;
39 import org.opendaylight.mdsal.binding.model.api.type.builder.EnumBuilder;
40 import org.opendaylight.mdsal.binding.model.api.type.builder.GeneratedPropertyBuilder;
41 import org.opendaylight.mdsal.binding.model.api.type.builder.GeneratedTOBuilder;
42 import org.opendaylight.mdsal.binding.model.api.type.builder.GeneratedTypeBuilder;
43 import org.opendaylight.mdsal.binding.model.api.type.builder.GeneratedTypeBuilderBase;
44 import org.opendaylight.mdsal.binding.model.api.type.builder.MethodSignatureBuilder;
45 import org.opendaylight.mdsal.binding.model.ri.BaseYangTypes;
46 import org.opendaylight.mdsal.binding.model.ri.BindingTypes;
47 import org.opendaylight.mdsal.binding.model.ri.TypeConstants;
48 import org.opendaylight.mdsal.binding.model.ri.Types;
49 import org.opendaylight.mdsal.binding.model.ri.generated.type.builder.AbstractEnumerationBuilder;
50 import org.opendaylight.mdsal.binding.model.ri.generated.type.builder.GeneratedPropertyBuilderImpl;
51 import org.opendaylight.mdsal.binding.spec.naming.BindingMapping;
52 import org.opendaylight.yangtools.yang.common.QName;
53 import org.opendaylight.yangtools.yang.common.Revision;
54 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
55 import org.opendaylight.yangtools.yang.model.api.Module;
56 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
57 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
58 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
59 import org.opendaylight.yangtools.yang.model.api.Status;
60 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
61 import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition;
62 import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition.Bit;
63 import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition;
64 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
65 import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
66 import org.opendaylight.yangtools.yang.model.api.type.PatternConstraint;
67 import org.opendaylight.yangtools.yang.model.api.type.StringTypeDefinition;
68 import org.opendaylight.yangtools.yang.model.api.type.UnionTypeDefinition;
69 import org.opendaylight.yangtools.yang.model.spi.ModuleDependencySort;
71 // FIXME: remove this class
72 @Deprecated(forRemoval = true)
73 abstract class AbstractTypeProvider implements TypeProvider {
74 private static final JavaTypeName DEPRECATED_ANNOTATION = JavaTypeName.create(Deprecated.class);
75 private static final CharMatcher DASH_COLON_MATCHER = CharMatcher.anyOf("-:");
78 * Contains the schema data red from YANG files.
80 private final SchemaContext schemaContext;
82 private final Map<String, Map<Optional<Revision>, Map<String, GeneratedType>>> genTypeDefsContextMap =
86 * The map which maps schema paths to JAVA <code>Type</code>.
88 private final Map<SchemaPath, Type> referencedTypes = new HashMap<>();
89 private final Map<Module, Set<GeneratedType>> additionalTypes = new HashMap<>();
92 * Creates new instance of class <code>TypeProviderImpl</code>.
94 * @param schemaContext contains the schema data red from YANG files
95 * @param renames renaming table
96 * @throws IllegalArgumentException if <code>schemaContext</code> equal null.
98 AbstractTypeProvider(final EffectiveModelContext schemaContext) {
99 this.schemaContext = requireNonNull(schemaContext);
101 resolveTypeDefsFromContext();
105 * Puts <code>refType</code> to map with key <code>refTypePath</code>.
107 * @param refTypePath schema path used as the map key
108 * @param refType type which represents the map value
109 * @throws IllegalArgumentException
111 * <li>if <code>refTypePath</code> equal null</li>
112 * <li>if <code>refType</code> equal null</li>
116 public void putReferencedType(final SchemaPath refTypePath, final Type refType) {
117 Preconditions.checkArgument(refTypePath != null,
118 "Path reference of Enumeration Type Definition cannot be NULL!");
119 Preconditions.checkArgument(refType != null, "Reference to Enumeration Type cannot be NULL!");
120 referencedTypes.put(refTypePath, refType);
123 public Map<Module, Set<GeneratedType>> getAdditionalTypes() {
124 return additionalTypes;
128 public Type javaTypeForSchemaDefinitionType(final TypeDefinition<?> typeDefinition, final SchemaNode parentNode,
129 final Restrictions restrictions) {
130 throw new UnsupportedOperationException();
134 * Converts <code>typeDefinition</code> to concrete JAVA <code>Type</code>.
136 * @param typeDefinition
137 * type definition which should be converted to JAVA
139 * @return JAVA <code>Type</code> which represents
140 * <code>typeDefinition</code>
141 * @throws IllegalArgumentException
143 * <li>if <code>typeDefinition</code> equal null</li>
144 * <li>if Q name of <code>typeDefinition</code></li>
145 * <li>if name of <code>typeDefinition</code></li>
148 public GeneratedType generatedTypeForExtendedDefinitionType(final TypeDefinition<?> typeDefinition,
149 final SchemaNode parentNode) {
150 Preconditions.checkArgument(typeDefinition != null, "Type Definition cannot be NULL!");
151 if (typeDefinition.getQName() == null) {
152 throw new IllegalArgumentException("Type Definition cannot have unspecified QName (QName cannot be NULL!)");
154 Preconditions.checkArgument(typeDefinition.getQName().getLocalName() != null,
155 "Type Definitions Local Name cannot be NULL!");
157 final TypeDefinition<?> baseTypeDef = baseTypeDefForExtendedType(typeDefinition);
158 if (baseTypeDef instanceof LeafrefTypeDefinition || baseTypeDef instanceof IdentityrefTypeDefinition) {
160 * This is backwards compatibility baggage from way back when. The problem at hand is inconsistency between
161 * the fact that identity is mapped to a Class, which is also returned from leaves which specify it like
174 * This results in getFoo() returning Class<? extends Iden>, which looks fine on the surface, but gets more
175 * dicey when we throw in:
189 * Now we have competing requirements: typedef would like us to use encapsulation to capture the defined
190 * type, while getBar() wants us to retain shape with getFoo(), as it should not matter how the identityref
193 * In this particular case getFoo() won just after the Binding Spec was frozen, hence we do not generate
194 * an encapsulation for identityref typedefs.
196 * In case you are thinking we could get by having foo-ref map to a subclass of Iden, that is not a good
197 * option, as it would look as though it is the product of a different construct:
203 * Leading to a rather nice namespace clash and also slight incompatibility with unknown third-party
204 * sub-identities of iden.
206 * The story behind leafrefs is probably similar, but that needs to be ascertained.
211 final Module module = findParentModule(schemaContext, parentNode);
212 if (module != null) {
213 final Map<Optional<Revision>, Map<String, GeneratedType>> modulesByDate = genTypeDefsContextMap.get(
215 final Map<String, GeneratedType> genTOs = modulesByDate.get(module.getRevision());
216 if (genTOs != null) {
217 return genTOs.get(typeDefinition.getQName().getLocalName());
224 * Gets base type definition for <code>extendTypeDef</code>. The method is
225 * recursively called until non <code>ExtendedType</code> type is found.
227 * @param extendTypeDef
228 * type definition for which is the base type definition sought
229 * @return type definition which is base type for <code>extendTypeDef</code>
230 * @throws IllegalArgumentException
231 * if <code>extendTypeDef</code> equal null
233 private static TypeDefinition<?> baseTypeDefForExtendedType(final TypeDefinition<?> extendTypeDef) {
234 Preconditions.checkArgument(extendTypeDef != null, "Type Definition reference cannot be NULL!");
236 TypeDefinition<?> ret = extendTypeDef;
237 while (ret.getBaseType() != null) {
238 ret = ret.getBaseType();
245 * Converts <code>enumTypeDef</code> to {@link Enumeration enumeration}.
247 * @param enumTypeDef enumeration type definition which is converted to enumeration
248 * @param enumName string with name which is used as the enumeration name
249 * @return enumeration type which is built with data (name, enum values) from <code>enumTypeDef</code>
250 * @throws IllegalArgumentException
252 * <li>if <code>enumTypeDef</code> equals null</li>
253 * <li>if enum values of <code>enumTypeDef</code> equal null</li>
254 * <li>if Q name of <code>enumTypeDef</code> equal null</li>
255 * <li>if name of <code>enumTypeDef</code> equal null</li>
258 private Enumeration provideTypeForEnum(final EnumTypeDefinition enumTypeDef, final String enumName,
259 final SchemaNode parentNode) {
260 Preconditions.checkArgument(enumTypeDef != null, "EnumTypeDefinition reference cannot be NULL!");
261 Preconditions.checkArgument(enumTypeDef.getValues() != null,
262 "EnumTypeDefinition MUST contain at least ONE value definition!");
263 Preconditions.checkArgument(enumTypeDef.getQName() != null, "EnumTypeDefinition MUST contain NON-NULL QName!");
264 Preconditions.checkArgument(enumTypeDef.getQName().getLocalName() != null,
265 "Local Name in EnumTypeDefinition QName cannot be NULL!");
267 final Module module = findParentModule(schemaContext, parentNode);
268 final AbstractEnumerationBuilder enumBuilder = newEnumerationBuilder(JavaTypeName.create(
269 BindingMapping.getRootPackageName(module.getQNameModule()), BindingMapping.getClassName(enumName)));
270 addEnumDescription(enumBuilder, enumTypeDef);
271 enumTypeDef.getReference().ifPresent(enumBuilder::setReference);
272 enumBuilder.setModuleName(module.getName());
273 enumBuilder.updateEnumPairsFromEnumTypeDef(enumTypeDef);
274 return enumBuilder.toInstance();
278 * Adds enumeration to <code>typeBuilder</code>. The enumeration data are taken from <code>enumTypeDef</code>.
280 * @param enumTypeDef enumeration type definition is source of enumeration data for <code>typeBuilder</code>
281 * @param enumName string with the name of enumeration
282 * @param typeBuilder generated type builder to which is enumeration added
283 * @return enumeration type which contains enumeration data form <code>enumTypeDef</code>
284 * @throws IllegalArgumentException
286 * <li>if <code>enumTypeDef</code> equals null</li>
287 * <li>if enum values of <code>enumTypeDef</code> equal null</li>
288 * <li>if Q name of <code>enumTypeDef</code> equal null</li>
289 * <li>if name of <code>enumTypeDef</code> equal null</li>
290 * <li>if name of <code>typeBuilder</code> equal null</li>
294 private Enumeration addInnerEnumerationToTypeBuilder(final EnumTypeDefinition enumTypeDef,
295 final String enumName, final GeneratedTypeBuilderBase<?> typeBuilder) {
296 Preconditions.checkArgument(enumTypeDef != null, "EnumTypeDefinition reference cannot be NULL!");
297 Preconditions.checkArgument(enumTypeDef.getValues() != null,
298 "EnumTypeDefinition MUST contain at least ONE value definition!");
299 Preconditions.checkArgument(enumTypeDef.getQName() != null, "EnumTypeDefinition MUST contain NON-NULL QName!");
300 Preconditions.checkArgument(enumTypeDef.getQName().getLocalName() != null,
301 "Local Name in EnumTypeDefinition QName cannot be NULL!");
302 Preconditions.checkArgument(typeBuilder != null, "Generated Type Builder reference cannot be NULL!");
304 final EnumBuilder enumBuilder = newEnumerationBuilder(
305 typeBuilder.getIdentifier().createEnclosed(BindingMapping.getClassName(enumName), "$"));
306 addEnumDescription(enumBuilder, enumTypeDef);
307 enumBuilder.updateEnumPairsFromEnumTypeDef(enumTypeDef);
308 final Enumeration ret = enumBuilder.toInstance();
309 typeBuilder.addEnumeration(ret);
314 public abstract void addEnumDescription(EnumBuilder enumBuilder, EnumTypeDefinition enumTypeDef);
316 public abstract AbstractEnumerationBuilder newEnumerationBuilder(JavaTypeName identifier);
318 public abstract GeneratedTOBuilder newGeneratedTOBuilder(JavaTypeName identifier);
320 public abstract GeneratedTypeBuilder newGeneratedTypeBuilder(JavaTypeName identifier);
323 * Converts the pattern constraints to the list of the strings which represents these constraints.
325 * @param patternConstraints list of pattern constraints
326 * @return list of strings which represents the constraint patterns
328 public abstract Map<String, String> resolveRegExpressions(List<PatternConstraint> patternConstraints);
330 abstract void addCodegenInformation(GeneratedTypeBuilderBase<?> genTOBuilder, TypeDefinition<?> typeDef);
333 * Converts the pattern constraints from <code>typedef</code> to the list of the strings which represents these
336 * @param typedef extended type in which are the pattern constraints sought
337 * @return list of strings which represents the constraint patterns
338 * @throws IllegalArgumentException if <code>typedef</code> equals null
341 private Map<String, String> resolveRegExpressionsFromTypedef(final TypeDefinition<?> typedef) {
342 if (!(typedef instanceof StringTypeDefinition)) {
343 return ImmutableMap.of();
346 // TODO: run diff against base ?
347 return resolveRegExpressions(((StringTypeDefinition) typedef).getPatternConstraints());
351 * Passes through all modules and through all its type definitions and convert it to generated types.
354 * The modules are first sorted by mutual dependencies. The modules are sequentially passed. All type definitions
355 * of a module are at the beginning sorted so that type definition with less amount of references to other type
356 * definition are processed first.<br>
357 * For each module is created mapping record in the map
358 * {@link AbstractTypeProvider#genTypeDefsContextMap genTypeDefsContextMap}
359 * which map current module name to the map which maps type names to returned types (generated types).
361 private void resolveTypeDefsFromContext() {
362 final List<Module> modulesSortedByDependency = ModuleDependencySort.sort(schemaContext.getModules());
364 for (Module module : modulesSortedByDependency) {
365 Map<Optional<Revision>, Map<String, GeneratedType>> dateTypeMap = genTypeDefsContextMap.computeIfAbsent(
366 module.getName(), key -> new HashMap<>());
367 dateTypeMap.put(module.getRevision(), Collections.emptyMap());
368 genTypeDefsContextMap.put(module.getName(), dateTypeMap);
371 for (Module module : modulesSortedByDependency) {
372 if (module != null) {
373 final String basePackageName = BindingMapping.getRootPackageName(module.getQNameModule());
374 if (basePackageName != null) {
375 final List<TypeDefinition<?>> typeDefinitions = TypedefResolver.getAllTypedefs(module);
376 for (TypeDefinition<?> typedef : sortTypeDefinitionAccordingDepth(typeDefinitions)) {
377 typedefToGeneratedType(basePackageName, module, typedef);
385 * Create Type for specified type definition.
387 * @param basePackageName string with name of package to which the module belongs
388 * @param module string with the name of the module for to which the <code>typedef</code> belongs
389 * @param typedef type definition of the node for which should be created JAVA <code>Type</code>
390 * (usually generated TO)
391 * @return JAVA <code>Type</code> representation of <code>typedef</code> or
392 * <code>null</code> value if <code>basePackageName</code> or
393 * <code>modulName</code> or <code>typedef</code> or Q name of
394 * <code>typedef</code> equals <code>null</code>
396 private Type typedefToGeneratedType(final String basePackageName, final Module module,
397 final TypeDefinition<?> typedef) {
398 final TypeDefinition<?> baseTypedef = typedef.getBaseType();
400 // See generatedTypeForExtendedDefinitionType() above for rationale behind this special case.
401 if (baseTypedef instanceof LeafrefTypeDefinition || baseTypedef instanceof IdentityrefTypeDefinition) {
405 final String typedefName = typedef.getQName().getLocalName();
407 final GeneratedType returnType;
408 if (baseTypedef.getBaseType() != null) {
409 returnType = provideGeneratedTOFromExtendedType(typedef, baseTypedef, basePackageName,
411 } else if (baseTypedef instanceof UnionTypeDefinition) {
412 final GeneratedTOBuilder genTOBuilder = provideGeneratedTOBuilderForUnionTypeDef(
413 JavaTypeName.create(basePackageName, BindingMapping.getClassName(typedef.getQName())),
414 (UnionTypeDefinition) baseTypedef, typedef);
415 genTOBuilder.setTypedef(true);
416 genTOBuilder.setIsUnion(true);
417 addUnitsToGenTO(genTOBuilder, typedef.getUnits().orElse(null));
418 makeSerializable(genTOBuilder);
419 returnType = genTOBuilder.build();
421 // Define a corresponding union builder. Typedefs are always anchored at a Java package root,
422 // so we are placing the builder alongside the union.
423 final GeneratedTOBuilder unionBuilder = newGeneratedTOBuilder(
424 JavaTypeName.create(genTOBuilder.getPackageName(), genTOBuilder.getName() + "Builder"));
425 unionBuilder.setIsUnionBuilder(true);
426 final MethodSignatureBuilder method = unionBuilder.addMethod("getDefaultInstance");
427 method.setReturnType(returnType);
428 method.addParameter(Types.STRING, "defaultValue");
429 method.setAccessModifier(AccessModifier.PUBLIC);
430 method.setStatic(true);
431 additionalTypes.computeIfAbsent(module, key -> new HashSet<>()).add(unionBuilder.build());
432 } else if (baseTypedef instanceof EnumTypeDefinition) {
433 // enums are automatically Serializable
434 final EnumTypeDefinition enumTypeDef = (EnumTypeDefinition) baseTypedef;
435 // TODO units for typedef enum
436 returnType = provideTypeForEnum(enumTypeDef, typedefName, typedef);
437 } else if (baseTypedef instanceof BitsTypeDefinition) {
438 final GeneratedTOBuilder genTOBuilder = provideGeneratedTOBuilderForBitsTypeDefinition(
439 JavaTypeName.create(basePackageName, BindingMapping.getClassName(typedef.getQName())),
440 (BitsTypeDefinition) baseTypedef, module.getName());
441 genTOBuilder.setTypedef(true);
442 addUnitsToGenTO(genTOBuilder, typedef.getUnits().orElse(null));
443 makeSerializable(genTOBuilder);
444 returnType = genTOBuilder.build();
446 final Type javaType = javaTypeForSchemaDefinitionType(baseTypedef, typedef);
447 returnType = wrapJavaTypeIntoTO(basePackageName, typedef, javaType, module.getName());
449 if (returnType != null) {
450 final Map<Optional<Revision>, Map<String, GeneratedType>> modulesByDate =
451 genTypeDefsContextMap.get(module.getName());
452 final Optional<Revision> moduleRevision = module.getRevision();
453 Map<String, GeneratedType> typeMap = modulesByDate.get(moduleRevision);
454 if (typeMap != null) {
455 if (typeMap.isEmpty()) {
456 typeMap = new HashMap<>(4);
457 modulesByDate.put(moduleRevision, typeMap);
459 typeMap.put(typedefName, returnType);
467 * Wraps base YANG type to generated TO.
469 * @param basePackageName string with name of package to which the module belongs
470 * @param typedef type definition which is converted to the TO
471 * @param javaType JAVA <code>Type</code> to which is <code>typedef</code> mapped
472 * @return generated transfer object which represent<code>javaType</code>
474 private GeneratedTransferObject wrapJavaTypeIntoTO(final String basePackageName, final TypeDefinition<?> typedef,
475 final Type javaType, final String moduleName) {
476 requireNonNull(javaType, "javaType cannot be null");
478 final GeneratedTOBuilder genTOBuilder = typedefToTransferObject(basePackageName, typedef, moduleName);
479 genTOBuilder.setRestrictions(BindingGeneratorUtil.getRestrictions(typedef));
480 final GeneratedPropertyBuilder genPropBuilder = genTOBuilder.addProperty(TypeConstants.VALUE_PROP);
481 genPropBuilder.setReturnType(javaType);
483 genTOBuilder.addEqualsIdentity(genPropBuilder);
484 genTOBuilder.addHashIdentity(genPropBuilder);
485 genTOBuilder.addToStringProperty(genPropBuilder);
486 genTOBuilder.addImplementsType(BindingTypes.scalarTypeObject(javaType));
487 if (typedef.getStatus() == Status.DEPRECATED) {
488 genTOBuilder.addAnnotation(DEPRECATED_ANNOTATION);
490 if (javaType instanceof ConcreteType && "String".equals(javaType.getName()) && typedef.getBaseType() != null) {
491 addStringRegExAsConstant(genTOBuilder, resolveRegExpressionsFromTypedef(typedef));
493 addUnitsToGenTO(genTOBuilder, typedef.getUnits().orElse(null));
494 genTOBuilder.setTypedef(true);
495 makeSerializable(genTOBuilder);
496 return genTOBuilder.build();
500 * Converts output list of generated TO builders to one TO builder (first
501 * from list) which contains the remaining builders as its enclosing TO.
503 * @param typeName new type identifier
504 * @param typedef type definition which should be of type {@link UnionTypeDefinition}
505 * @return generated TO builder with the list of enclosed generated TO builders
507 public GeneratedTOBuilder provideGeneratedTOBuilderForUnionTypeDef(final JavaTypeName typeName,
508 final UnionTypeDefinition typedef, final TypeDefinition<?> parentNode) {
509 final List<GeneratedTOBuilder> builders = provideGeneratedTOBuildersForUnionTypeDef(typeName, typedef,
511 Preconditions.checkState(!builders.isEmpty(), "No GeneratedTOBuilder objects generated from union %s", typedef);
513 final GeneratedTOBuilder resultTOBuilder = builders.remove(0);
514 builders.forEach(builder -> resultTOBuilder.addEnclosingTransferObject(builder.build()));
515 return resultTOBuilder;
519 * Converts <code>typedef</code> to generated TO with <code>typeDefName</code>. Every union type from
520 * <code>typedef</code> is added to generated TO builder as property.
522 * @param typeName new type identifier
523 * @param typedef type definition which should be of type <code>UnionTypeDefinition</code>
524 * @return generated TO builder which represents <code>typedef</code>
525 * @throws NullPointerException
527 * <li>if <code>basePackageName</code> is null</li>
528 * <li>if <code>typedef</code> is null</li>
529 * <li>if Qname of <code>typedef</code> is null</li>
532 public List<GeneratedTOBuilder> provideGeneratedTOBuildersForUnionTypeDef(final JavaTypeName typeName,
533 final UnionTypeDefinition typedef, final SchemaNode parentNode) {
534 requireNonNull(typedef, "Type Definition cannot be NULL!");
535 requireNonNull(typedef.getQName(), "Type definition QName cannot be NULL!");
537 final List<GeneratedTOBuilder> generatedTOBuilders = new ArrayList<>();
538 final List<TypeDefinition<?>> unionTypes = typedef.getTypes();
539 final Module module = findParentModule(schemaContext, parentNode);
541 final GeneratedTOBuilder unionGenTOBuilder = newGeneratedTOBuilder(typeName);
542 unionGenTOBuilder.setIsUnion(true);
543 unionGenTOBuilder.setSchemaPath(typedef.getPath());
544 unionGenTOBuilder.setModuleName(module.getName());
545 unionGenTOBuilder.addImplementsType(TYPE_OBJECT);
546 addCodegenInformation(unionGenTOBuilder, typedef);
547 generatedTOBuilders.add(unionGenTOBuilder);
549 // Pattern string is the key, XSD regex is the value. The reason for this choice is that the pattern carries
550 // also negation information and hence guarantees uniqueness.
551 final Map<String, String> expressions = new HashMap<>();
552 for (TypeDefinition<?> unionType : unionTypes) {
553 final String unionTypeName = unionType.getQName().getLocalName();
555 // If we have a base type we should follow the type definition backwards, except for identityrefs, as those
556 // do not follow type encapsulation -- we use the general case for that.
557 if (unionType.getBaseType() != null && !(unionType instanceof IdentityrefTypeDefinition)) {
558 resolveExtendedSubtypeAsUnion(unionGenTOBuilder, unionType, expressions, parentNode);
559 } else if (unionType instanceof UnionTypeDefinition) {
560 generatedTOBuilders.addAll(resolveUnionSubtypeAsUnion(unionGenTOBuilder,
561 (UnionTypeDefinition) unionType, parentNode));
562 } else if (unionType instanceof EnumTypeDefinition) {
563 final Enumeration enumeration = addInnerEnumerationToTypeBuilder((EnumTypeDefinition) unionType,
564 unionTypeName, unionGenTOBuilder);
565 updateUnionTypeAsProperty(unionGenTOBuilder, enumeration, unionTypeName);
567 final Type javaType = javaTypeForSchemaDefinitionType(unionType, parentNode);
568 updateUnionTypeAsProperty(unionGenTOBuilder, javaType, unionTypeName);
571 addStringRegExAsConstant(unionGenTOBuilder, expressions);
573 storeGenTO(typedef, unionGenTOBuilder, parentNode);
575 return generatedTOBuilders;
579 * Wraps code which handles the case when union subtype is also of the type <code>UnionType</code>.
582 * In this case the new generated TO is created for union subtype (recursive call of method
583 * {@link #provideGeneratedTOBuildersForUnionTypeDef(String, UnionTypeDefinition, String, SchemaNode)}
584 * provideGeneratedTOBuilderForUnionTypeDef} and in parent TO builder <code>parentUnionGenTOBuilder</code> is
585 * created property which type is equal to new generated TO.
587 * @param parentUnionGenTOBuilder generated TO builder to which is the property with the child union subtype added
588 * @param basePackageName string with the name of the module package
589 * @param unionSubtype type definition which represents union subtype
590 * @return list of generated TO builders. The number of the builders can be bigger one due to recursive call of
591 * <code>provideGeneratedTOBuildersForUnionTypeDef</code> method.
593 private List<GeneratedTOBuilder> resolveUnionSubtypeAsUnion(final GeneratedTOBuilder parentUnionGenTOBuilder,
594 final UnionTypeDefinition unionSubtype, final SchemaNode parentNode) {
595 final JavaTypeName newTOBuilderName = parentUnionGenTOBuilder.getIdentifier().createSibling(
596 provideAvailableNameForGenTOBuilder(parentUnionGenTOBuilder.getName()));
597 final List<GeneratedTOBuilder> subUnionGenTOBUilders = provideGeneratedTOBuildersForUnionTypeDef(
598 newTOBuilderName, unionSubtype, parentNode);
600 final GeneratedPropertyBuilder propertyBuilder;
601 propertyBuilder = parentUnionGenTOBuilder.addProperty(BindingMapping.getPropertyName(
602 newTOBuilderName.simpleName()));
603 propertyBuilder.setReturnType(subUnionGenTOBUilders.get(0).build());
604 parentUnionGenTOBuilder.addEqualsIdentity(propertyBuilder);
605 parentUnionGenTOBuilder.addToStringProperty(propertyBuilder);
607 return subUnionGenTOBUilders;
611 * Wraps code which handle case when union subtype is of the type <code>ExtendedType</code>. If TO for this type
612 * already exists it is used for the creation of the property in <code>parentUnionGenTOBuilder</code>. Otherwise
613 * the base type is used for the property creation.
615 * @param parentUnionGenTOBuilder generated TO builder in which new property is created
616 * @param unionSubtype type definition of the <code>ExtendedType</code> type which represents union subtype
617 * @param expressions list of strings with the regular expressions
618 * @param parentNode parent Schema Node for Extended Subtype
620 private void resolveExtendedSubtypeAsUnion(final GeneratedTOBuilder parentUnionGenTOBuilder,
621 final TypeDefinition<?> unionSubtype, final Map<String, String> expressions, final SchemaNode parentNode) {
622 final String unionTypeName = unionSubtype.getQName().getLocalName();
623 final Type genTO = findGenTO(unionTypeName, unionSubtype);
625 updateUnionTypeAsProperty(parentUnionGenTOBuilder, genTO, genTO.getName());
629 final TypeDefinition<?> baseType = baseTypeDefForExtendedType(unionSubtype);
630 if (unionTypeName.equals(baseType.getQName().getLocalName())) {
631 final Type javaType = baseJavaTypeForSchema(baseType, parentNode,
632 BindingGeneratorUtil.getRestrictions(unionSubtype));
633 if (javaType != null) {
634 updateUnionTypeAsProperty(parentUnionGenTOBuilder, javaType, unionTypeName);
636 } else if (baseType instanceof LeafrefTypeDefinition) {
637 final Type javaType = javaTypeForSchemaDefinitionType(baseType, parentNode);
638 boolean typeExist = false;
639 for (GeneratedPropertyBuilder generatedPropertyBuilder : parentUnionGenTOBuilder.getProperties()) {
640 final Type origType = ((GeneratedPropertyBuilderImpl) generatedPropertyBuilder).getReturnType();
641 if (origType != null && javaType != null && javaType == origType) {
646 if (!typeExist && javaType != null) {
647 updateUnionTypeAsProperty(parentUnionGenTOBuilder, javaType,
648 BindingMapping.getUnionLeafrefMemberName(parentUnionGenTOBuilder.getName(), javaType.getName()));
651 if (baseType instanceof StringTypeDefinition) {
652 expressions.putAll(resolveRegExpressionsFromTypedef(unionSubtype));
656 private static Type baseJavaTypeForSchema(final TypeDefinition<?> type, final SchemaNode parentNode,
657 final Restrictions restrictions) {
658 final String typeName = type.getQName().getLocalName();
659 final Type mapped = BaseYangTypes.javaTypeForYangType(typeName);
660 return mapped == null || restrictions == null ? mapped : Types.restrictedType(mapped, restrictions);
664 * Searches for generated TO for <code>searchedTypeDef</code> type definition
665 * in {@link #genTypeDefsContextMap genTypeDefsContextMap}.
667 * @param searchedTypeName string with name of <code>searchedTypeDef</code>
668 * @return generated TO for <code>searchedTypeDef</code> or <code>null</code> it it doesn't exist
670 private Type findGenTO(final String searchedTypeName, final SchemaNode parentNode) {
671 final Module typeModule = findParentModule(schemaContext, parentNode);
672 if (typeModule != null && typeModule.getName() != null) {
673 final Map<Optional<Revision>, Map<String, GeneratedType>> modulesByDate = genTypeDefsContextMap.get(
674 typeModule.getName());
675 final Map<String, GeneratedType> genTOs = modulesByDate.get(typeModule.getRevision());
676 if (genTOs != null) {
677 return genTOs.get(searchedTypeName);
684 * Stores generated TO created from <code>genTOBuilder</code> for <code>newTypeDef</code>
685 * to {@link #genTypeDefsContextMap genTypeDefsContextMap} if the module for <code>newTypeDef</code> exists.
687 * @param newTypeDef type definition for which is <code>genTOBuilder</code> created
688 * @param genTOBuilder generated TO builder which is converted to generated TO and stored
690 private void storeGenTO(final TypeDefinition<?> newTypeDef, final GeneratedTOBuilder genTOBuilder,
691 final SchemaNode parentNode) {
692 if (!(newTypeDef instanceof UnionTypeDefinition)) {
693 final Module parentModule = findParentModule(schemaContext, parentNode);
694 if (parentModule != null && parentModule.getName() != null) {
695 final Map<Optional<Revision>, Map<String, GeneratedType>> modulesByDate = genTypeDefsContextMap.get(
696 parentModule.getName());
697 final Map<String, GeneratedType> genTOsMap = modulesByDate.get(parentModule.getRevision());
698 genTOsMap.put(newTypeDef.getQName().getLocalName(), genTOBuilder.build());
704 * Adds a new property with the name <code>propertyName</code> and with type <code>type</code>
705 * to <code>unonGenTransObject</code>.
707 * @param unionGenTransObject generated TO to which should be property added
708 * @param type JAVA <code>type</code> of the property which should be added to <code>unionGentransObject</code>
709 * @param propertyName string with name of property which should be added to <code>unionGentransObject</code>
711 private static void updateUnionTypeAsProperty(final GeneratedTOBuilder unionGenTransObject, final Type type,
712 final String propertyName) {
713 if (unionGenTransObject != null && type != null && !unionGenTransObject.containsProperty(propertyName)) {
714 final GeneratedPropertyBuilder propBuilder = unionGenTransObject
715 .addProperty(BindingMapping.getPropertyName(propertyName));
716 propBuilder.setReturnType(type);
718 unionGenTransObject.addEqualsIdentity(propBuilder);
719 unionGenTransObject.addHashIdentity(propBuilder);
720 unionGenTransObject.addToStringProperty(propBuilder);
725 * Converts <code>typedef</code> to the generated TO builder.
727 * @param basePackageName string with name of package to which the module belongs
728 * @param typedef type definition from which is the generated TO builder created
729 * @return generated TO builder which contains data from <code>typedef</code> and <code>basePackageName</code>
731 private GeneratedTOBuilder typedefToTransferObject(final String basePackageName,
732 final TypeDefinition<?> typedef, final String moduleName) {
733 final JavaTypeName name = JavaTypeName.create(
734 packageNameForGeneratedType(basePackageName, typedef.getPath()),
735 BindingMapping.getClassName(typedef.getQName().getLocalName()));
737 final GeneratedTOBuilder newType = newGeneratedTOBuilder(name);
738 newType.setSchemaPath(typedef.getPath());
739 newType.setModuleName(moduleName);
740 addCodegenInformation(newType, typedef);
745 * Creates package name from specified <code>basePackageName</code> (package name for module)
746 * and <code>schemaPath</code>. Resulting package name is concatenation of <code>basePackageName</code>
747 * and all local names of YANG nodes which are parents of some node for which <code>schemaPath</code> is specified.
749 * @param basePackageName string with package name of the module, MUST be normalized, otherwise this method may
750 * return an invalid string.
751 * @param schemaPath list of names of YANG nodes which are parents of some node + name of this node
752 * @return string with valid JAVA package name
753 * @throws NullPointerException if any of the arguments are null
755 private static String packageNameForGeneratedType(final String basePackageName, final SchemaPath schemaPath) {
756 final int size = Iterables.size(schemaPath.getPathTowardsRoot()) - 1;
758 return basePackageName;
761 return generateNormalizedPackageName(basePackageName, schemaPath.getPathFromRoot(), size);
764 private static String generateNormalizedPackageName(final String base, final Iterable<QName> path, final int size) {
765 final StringBuilder builder = new StringBuilder(base);
766 final Iterator<QName> iterator = path.iterator();
767 for (int i = 0; i < size; ++i) {
769 final String nodeLocalName = iterator.next().getLocalName();
770 // FIXME: Collon ":" is invalid in node local name as per RFC6020, identifier statement.
771 builder.append(DASH_COLON_MATCHER.replaceFrom(nodeLocalName, '.'));
773 return BindingMapping.normalizePackageName(builder.toString());
778 * Converts <code>typeDef</code> which should be of the type <code>BitsTypeDefinition</code>
779 * to <code>GeneratedTOBuilder</code>. All the bits of the typeDef are added to returning generated TO as
782 * @param typeName new type identifier
783 * @param typeDef type definition from which is the generated TO builder created
784 * @return generated TO builder which represents <code>typeDef</code>
785 * @throws IllegalArgumentException
787 * <li>if <code>typeDef</code> equals null</li>
788 * <li>if <code>basePackageName</code> equals null</li>
791 public GeneratedTOBuilder provideGeneratedTOBuilderForBitsTypeDefinition(final JavaTypeName typeName,
792 final BitsTypeDefinition typeDef, final String moduleName) {
793 final GeneratedTOBuilder genTOBuilder = newGeneratedTOBuilder(typeName);
794 genTOBuilder.setSchemaPath(typeDef.getPath());
795 genTOBuilder.setModuleName(moduleName);
796 genTOBuilder.setBaseType(typeDef);
797 genTOBuilder.addImplementsType(TYPE_OBJECT);
798 addCodegenInformation(genTOBuilder, typeDef);
800 for (Bit bit : typeDef.getBits()) {
801 final String name = bit.getName();
802 GeneratedPropertyBuilder genPropertyBuilder = genTOBuilder.addProperty(
803 BindingMapping.getPropertyName(name));
804 genPropertyBuilder.setReadOnly(true);
805 genPropertyBuilder.setReturnType(BaseYangTypes.BOOLEAN_TYPE);
807 genTOBuilder.addEqualsIdentity(genPropertyBuilder);
808 genTOBuilder.addHashIdentity(genPropertyBuilder);
809 genTOBuilder.addToStringProperty(genPropertyBuilder);
816 * Adds to the <code>genTOBuilder</code> the constant which contains regular expressions from
817 * the <code>regularExpressions</code>.
819 * @param genTOBuilder generated TO builder to which are <code>regular expressions</code> added
820 * @param expressions list of string which represent regular expressions
822 private static void addStringRegExAsConstant(final GeneratedTOBuilder genTOBuilder,
823 final Map<String, String> expressions) {
824 if (!expressions.isEmpty()) {
825 genTOBuilder.addConstant(Types.listTypeFor(BaseYangTypes.STRING_TYPE), TypeConstants.PATTERN_CONSTANT_NAME,
826 ImmutableMap.copyOf(expressions));
831 * Creates generated TO with data about inner extended type <code>innerExtendedType</code>, about the package name
832 * <code>typedefName</code> and about the generated TO name <code>typedefName</code>.
835 * It is assumed that <code>innerExtendedType</code> is already present in
836 * {@link AbstractTypeProvider#genTypeDefsContextMap genTypeDefsContextMap} to be possible set it as extended type
837 * for the returning generated TO.
839 * @param typedef Type Definition
840 * @param innerExtendedType extended type which is part of some other extended type
841 * @param basePackageName string with the package name of the module
842 * @param moduleName Module Name
843 * @return generated TO which extends generated TO for <code>innerExtendedType</code>
844 * @throws IllegalArgumentException
846 * <li>if <code>extendedType</code> equals null</li>
847 * <li>if <code>basePackageName</code> equals null</li>
848 * <li>if <code>typedefName</code> equals null</li>
851 private GeneratedTransferObject provideGeneratedTOFromExtendedType(final TypeDefinition<?> typedef,
852 final TypeDefinition<?> innerExtendedType, final String basePackageName, final String moduleName) {
853 Preconditions.checkArgument(innerExtendedType != null, "Extended type cannot be NULL!");
854 Preconditions.checkArgument(basePackageName != null, "String with base package name cannot be NULL!");
856 final GeneratedTOBuilder genTOBuilder = newGeneratedTOBuilder(JavaTypeName.create(basePackageName,
857 BindingMapping.getClassName(typedef.getQName())));
858 genTOBuilder.setSchemaPath(typedef.getPath());
859 genTOBuilder.setModuleName(moduleName);
860 genTOBuilder.setTypedef(true);
861 addCodegenInformation(genTOBuilder, typedef);
863 final Restrictions r = BindingGeneratorUtil.getRestrictions(typedef);
864 genTOBuilder.setRestrictions(r);
865 addStringRegExAsConstant(genTOBuilder, resolveRegExpressionsFromTypedef(typedef));
867 if (typedef.getStatus() == Status.DEPRECATED) {
868 genTOBuilder.addAnnotation(DEPRECATED_ANNOTATION);
871 if (baseTypeDefForExtendedType(innerExtendedType) instanceof UnionTypeDefinition) {
872 genTOBuilder.setIsUnion(true);
875 Map<Optional<Revision>, Map<String, GeneratedType>> modulesByDate = null;
876 Map<String, GeneratedType> typeMap = null;
877 final Module parentModule = findParentModule(schemaContext, innerExtendedType);
878 if (parentModule != null) {
879 modulesByDate = genTypeDefsContextMap.get(parentModule.getName());
880 typeMap = modulesByDate.get(parentModule.getRevision());
883 if (typeMap != null) {
884 final String innerTypeDef = innerExtendedType.getQName().getLocalName();
885 final GeneratedType type = typeMap.get(innerTypeDef);
886 if (type instanceof GeneratedTransferObject) {
887 genTOBuilder.setExtendsType((GeneratedTransferObject) type);
890 addUnitsToGenTO(genTOBuilder, typedef.getUnits().orElse(null));
891 makeSerializable(genTOBuilder);
893 return genTOBuilder.build();
897 * Add {@link java.io.Serializable} to implemented interfaces of this TO. Also compute and add serialVersionUID
900 * @param gto transfer object which needs to be made serializable
902 private static void makeSerializable(final GeneratedTOBuilder gto) {
903 gto.addImplementsType(Types.serializableType());
904 final GeneratedPropertyBuilder prop = new GeneratedPropertyBuilderImpl("serialVersionUID");
905 prop.setValue(Long.toString(SerialVersionHelper.computeDefaultSUID(gto)));
910 * Finds out for each type definition how many immersion (depth) is necessary to get to the base type. Every type
911 * definition is inserted to the map which key is depth and value is list of type definitions with equal depth.
912 * In next step are lists from this map concatenated to one list in ascending order according to their depth. All
913 * type definitions are in the list behind all type definitions on which depends.
915 * @param unsortedTypeDefinitions list of type definitions which should be sorted by depth
916 * @return list of type definitions sorted according their each other dependencies (type definitions which are
917 * dependent on other type definitions are in list behind them).
919 private static List<TypeDefinition<?>> sortTypeDefinitionAccordingDepth(
920 final Collection<TypeDefinition<?>> unsortedTypeDefinitions) {
921 final List<TypeDefinition<?>> sortedTypeDefinition = new ArrayList<>();
923 final Map<Integer, List<TypeDefinition<?>>> typeDefinitionsDepths = new TreeMap<>();
924 for (TypeDefinition<?> unsortedTypeDefinition : unsortedTypeDefinitions) {
925 final Integer depth = getTypeDefinitionDepth(unsortedTypeDefinition);
926 List<TypeDefinition<?>> typeDefinitionsConcreteDepth =
927 typeDefinitionsDepths.computeIfAbsent(depth, k -> new ArrayList<>());
928 typeDefinitionsConcreteDepth.add(unsortedTypeDefinition);
931 // SortedMap guarantees order corresponding to keys in ascending order
932 for (List<TypeDefinition<?>> v : typeDefinitionsDepths.values()) {
933 sortedTypeDefinition.addAll(v);
936 return sortedTypeDefinition;
940 * Returns how many immersion is necessary to get from the type definition to the base type.
942 * @param typeDefinition type definition for which is depth sought.
943 * @return number of immersions which are necessary to get from the type definition to the base type
945 private static int getTypeDefinitionDepth(final TypeDefinition<?> typeDefinition) {
946 // FIXME: rewrite this in a non-recursive manner
947 if (typeDefinition == null) {
950 final TypeDefinition<?> baseType = typeDefinition.getBaseType();
951 if (baseType == null) {
956 if (baseType.getBaseType() != null) {
957 depth = depth + getTypeDefinitionDepth(baseType);
958 } else if (baseType instanceof UnionTypeDefinition) {
959 final List<TypeDefinition<?>> childTypeDefinitions = ((UnionTypeDefinition) baseType).getTypes();
960 int maxChildDepth = 0;
962 for (TypeDefinition<?> childTypeDefinition : childTypeDefinitions) {
963 childDepth = childDepth + getTypeDefinitionDepth(childTypeDefinition);
964 if (childDepth > maxChildDepth) {
965 maxChildDepth = childDepth;
968 return maxChildDepth;
974 * Returns string which contains the same value as <code>name</code> but integer suffix is incremented by one. If
975 * <code>name</code> contains no number suffix, a new suffix initialized at 1 is added. A suffix is actually
976 * composed of a '$' marker, which is safe, as no YANG identifier can contain '$', and a unsigned decimal integer.
978 * @param name string with name of augmented node
979 * @return string with the number suffix incremented by one (or 1 is added)
981 private static String provideAvailableNameForGenTOBuilder(final String name) {
982 final int dollar = name.indexOf('$');
987 final int newSuffix = Integer.parseUnsignedInt(name.substring(dollar + 1)) + 1;
988 Preconditions.checkState(newSuffix > 0, "Suffix counter overflow");
989 return name.substring(0, dollar + 1) + newSuffix;
992 public static void addUnitsToGenTO(final GeneratedTOBuilder to, final String units) {
993 if (!Strings.isNullOrEmpty(units)) {
994 to.addConstant(Types.STRING, "_UNITS", "\"" + units + "\"");
995 final GeneratedPropertyBuilder prop = new GeneratedPropertyBuilderImpl("UNITS");
996 prop.setReturnType(Types.STRING);
997 to.addToStringProperty(prop);
1002 * Returns parent Yang Module for specified Schema Context in which Schema
1003 * Node is declared. If the Schema Node is not present in Schema Context the
1004 * operation will return <code>null</code>.
1006 * @param context Schema Context
1007 * @param schemaNode Schema Node
1008 * @return Yang Module for specified Schema Context and Schema Node, if Schema Node is NOT present, the method will
1009 * return <code>null</code>
1010 * @throws NullPointerException if any of the arguments is null
1012 private static Module findParentModule(final SchemaContext context, final SchemaNode schemaNode) {
1013 return context.findModule(schemaNode.getQName().getModule()).orElse(null);