2 * Copyright (c) 2021 PANTHEON.tech, s.r.o. 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.generator.impl.reactor;
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static com.google.common.base.Verify.verify;
12 import static com.google.common.base.Verify.verifyNotNull;
14 import com.google.common.collect.ImmutableMap;
15 import com.google.common.collect.Maps;
16 import java.util.ArrayList;
17 import java.util.HashMap;
18 import java.util.List;
20 import java.util.Optional;
21 import java.util.stream.Collectors;
22 import org.eclipse.jdt.annotation.NonNull;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.opendaylight.mdsal.binding.generator.BindingGeneratorUtil;
25 import org.opendaylight.mdsal.binding.generator.impl.reactor.TypeReference.ResolvedLeafref;
26 import org.opendaylight.mdsal.binding.model.api.ConcreteType;
27 import org.opendaylight.mdsal.binding.model.api.Enumeration;
28 import org.opendaylight.mdsal.binding.model.api.GeneratedProperty;
29 import org.opendaylight.mdsal.binding.model.api.GeneratedTransferObject;
30 import org.opendaylight.mdsal.binding.model.api.GeneratedType;
31 import org.opendaylight.mdsal.binding.model.api.JavaTypeName;
32 import org.opendaylight.mdsal.binding.model.api.Restrictions;
33 import org.opendaylight.mdsal.binding.model.api.Type;
34 import org.opendaylight.mdsal.binding.model.api.type.builder.GeneratedPropertyBuilder;
35 import org.opendaylight.mdsal.binding.model.api.type.builder.GeneratedTOBuilder;
36 import org.opendaylight.mdsal.binding.model.api.type.builder.GeneratedTypeBuilderBase;
37 import org.opendaylight.mdsal.binding.model.api.type.builder.MethodSignatureBuilder;
38 import org.opendaylight.mdsal.binding.model.ri.BaseYangTypes;
39 import org.opendaylight.mdsal.binding.model.ri.BindingTypes;
40 import org.opendaylight.mdsal.binding.model.ri.TypeConstants;
41 import org.opendaylight.mdsal.binding.model.ri.Types;
42 import org.opendaylight.mdsal.binding.model.ri.generated.type.builder.AbstractEnumerationBuilder;
43 import org.opendaylight.mdsal.binding.model.ri.generated.type.builder.GeneratedPropertyBuilderImpl;
44 import org.opendaylight.mdsal.binding.runtime.api.RuntimeType;
45 import org.opendaylight.mdsal.binding.spec.naming.BindingMapping;
46 import org.opendaylight.yangtools.concepts.Immutable;
47 import org.opendaylight.yangtools.yang.binding.RegexPatterns;
48 import org.opendaylight.yangtools.yang.binding.TypeObject;
49 import org.opendaylight.yangtools.yang.common.QName;
50 import org.opendaylight.yangtools.yang.common.YangConstants;
51 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
52 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
53 import org.opendaylight.yangtools.yang.model.api.stmt.BaseEffectiveStatement;
54 import org.opendaylight.yangtools.yang.model.api.stmt.LengthEffectiveStatement;
55 import org.opendaylight.yangtools.yang.model.api.stmt.PathEffectiveStatement;
56 import org.opendaylight.yangtools.yang.model.api.stmt.PatternEffectiveStatement;
57 import org.opendaylight.yangtools.yang.model.api.stmt.PatternExpression;
58 import org.opendaylight.yangtools.yang.model.api.stmt.RangeEffectiveStatement;
59 import org.opendaylight.yangtools.yang.model.api.stmt.TypeEffectiveStatement;
60 import org.opendaylight.yangtools.yang.model.api.stmt.ValueRange;
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.ModifierKind;
65 import org.opendaylight.yangtools.yang.model.api.type.PatternConstraint;
66 import org.opendaylight.yangtools.yang.model.api.type.StringTypeDefinition;
67 import org.opendaylight.yangtools.yang.model.api.type.TypeDefinitions;
68 import org.slf4j.Logger;
69 import org.slf4j.LoggerFactory;
72 * Common base class for {@link TypedefGenerator} and {@link AbstractTypeAwareGenerator}. This encompasses three
73 * different statements with two different semantics:
75 * <li>{@link TypedefGenerator}s always result in a generated {@link TypeObject}, even if the semantics is exactly
76 * the same as its base type. This aligns with {@code typedef} defining a new type.<li>
77 * <li>{@link LeafGenerator}s and {@link LeafListGenerator}s, on the other hand, do not generate a {@link TypeObject}
78 * unless absolutely necassary. This aligns with {@code leaf} and {@code leaf-list} being mapped onto a property
79 * of its parent type.<li>
83 * To throw a bit of confusion into the mix, there are three exceptions to those rules:
86 * {@code identityref} definitions never result in a type definition being emitted. The reason for this has to do
87 * with identity type mapping as well as history of our codebase.
90 * The problem at hand is inconsistency between the fact that identity is mapped to a {@link Class}, which is also
91 * returned from leaves which specify it like this:
105 * which results in fine-looking
109 * Class<? extends Iden> getFoo();
115 * This gets more dicey if we decide to extend the previous snippet to also include:
133 * Now we have competing requirements: {@code typedef} would like us to use encapsulation to capture the defined
134 * type, while {@code getBar()} wants us to retain shape with getFoo(), as it should not matter how the
135 * {@code identityref} was formed. We need to pick between:
140 * public class BarRef extends ScalarTypeObject<Class<? extends Iden>> {
141 * Class<? extends Iden> getValue() {
156 * Class<? extends Iden> getBar();
164 * Here the second option is more user-friendly, as the type system works along the lines of <b>reference</b>
165 * semantics, treating and {@code Bar.getBar()} and {@code Foo.getFoo()} as equivalent. The first option would
166 * force users to go through explicit encapsulation, for no added benefit as the {@code typedef} cannot possibly add
167 * anything useful to the actual type semantics.
170 * {@code leafref} definitions never result in a type definition being emitted. The reasons for this are similar to
171 * {@code identityref}, but have an additional twist: a {@leafref} can target a relative path, which may only be
172 * resolved at a particular instantiation.
174 * Take the example of the following model:
207 * The {@code typedef ref} points to outside of the grouping, and hence the type of {@code leaf foo} is polymorphic:
208 * the definition in {@code grouping grp} needs to use {@code Object}, whereas the instantiations in
209 * {@code container bar} and {@code container baz} need to use {@code String} and {@link Integer} respectively.
210 * Expressing the resulting interface contracts requires return type specialization and run-time checks. An
211 * intermediate class generated for the typedef would end up being a hindrance without any benefit.
214 * {@code enumeration} definitions never result in a derived type. This is because these are mapped to Java
215 * {@code enum}, which does not allow subclassing.
220 * At the end of the day, the mechanic translation rules are giving way to correctly mapping the semantics -- which in
221 * both of the exception cases boil down to tracking type indirection. Intermediate constructs involved in tracking
222 * type indirection in YANG constructs is therefore explicitly excluded from the generated Java code, but the Binding
223 * Specification still takes them into account when determining types as outlined above.
225 abstract class AbstractTypeObjectGenerator<S extends EffectiveStatement<?, ?>, R extends RuntimeType>
226 extends AbstractDependentGenerator<S, R> {
227 private static final class UnionDependencies implements Immutable {
228 private final Map<EffectiveStatement<?, ?>, TypeReference> identityTypes = new HashMap<>();
229 private final Map<EffectiveStatement<?, ?>, TypeReference> leafTypes = new HashMap<>();
230 private final Map<QName, TypedefGenerator> baseTypes = new HashMap<>();
232 UnionDependencies(final TypeEffectiveStatement<?> type, final GeneratorContext context) {
233 resolveUnionDependencies(context, type);
236 private void resolveUnionDependencies(final GeneratorContext context, final TypeEffectiveStatement<?> union) {
237 for (EffectiveStatement<?, ?> stmt : union.effectiveSubstatements()) {
238 if (stmt instanceof TypeEffectiveStatement<?> type) {
239 final QName typeName = type.argument();
240 if (TypeDefinitions.IDENTITYREF.equals(typeName)) {
241 if (!identityTypes.containsKey(stmt)) {
242 identityTypes.put(stmt, TypeReference.identityRef(
243 type.streamEffectiveSubstatements(BaseEffectiveStatement.class)
244 .map(BaseEffectiveStatement::argument)
245 .map(context::resolveIdentity)
246 .collect(Collectors.toUnmodifiableList())));
248 } else if (TypeDefinitions.LEAFREF.equals(typeName)) {
249 if (!leafTypes.containsKey(stmt)) {
250 leafTypes.put(stmt, TypeReference.leafRef(context.resolveLeafref(
251 type.findFirstEffectiveSubstatementArgument(PathEffectiveStatement.class)
254 } else if (TypeDefinitions.UNION.equals(typeName)) {
255 resolveUnionDependencies(context, type);
256 } else if (!isBuiltinName(typeName) && !baseTypes.containsKey(typeName)) {
257 baseTypes.put(typeName, context.resolveTypedef(typeName));
264 private static final Logger LOG = LoggerFactory.getLogger(AbstractTypeObjectGenerator.class);
265 static final ImmutableMap<QName, Type> SIMPLE_TYPES = ImmutableMap.<QName, Type>builder()
266 .put(TypeDefinitions.BINARY, BaseYangTypes.BINARY_TYPE)
267 .put(TypeDefinitions.BOOLEAN, BaseYangTypes.BOOLEAN_TYPE)
268 .put(TypeDefinitions.DECIMAL64, BaseYangTypes.DECIMAL64_TYPE)
269 .put(TypeDefinitions.EMPTY, BaseYangTypes.EMPTY_TYPE)
270 .put(TypeDefinitions.INSTANCE_IDENTIFIER, BaseYangTypes.INSTANCE_IDENTIFIER)
271 .put(TypeDefinitions.INT8, BaseYangTypes.INT8_TYPE)
272 .put(TypeDefinitions.INT16, BaseYangTypes.INT16_TYPE)
273 .put(TypeDefinitions.INT32, BaseYangTypes.INT32_TYPE)
274 .put(TypeDefinitions.INT64, BaseYangTypes.INT64_TYPE)
275 .put(TypeDefinitions.STRING, BaseYangTypes.STRING_TYPE)
276 .put(TypeDefinitions.UINT8, BaseYangTypes.UINT8_TYPE)
277 .put(TypeDefinitions.UINT16, BaseYangTypes.UINT16_TYPE)
278 .put(TypeDefinitions.UINT32, BaseYangTypes.UINT32_TYPE)
279 .put(TypeDefinitions.UINT64, BaseYangTypes.UINT64_TYPE)
282 private final TypeEffectiveStatement<?> type;
284 // FIXME: these fields should be better-controlled with explicit sequencing guards. It it currently stands, we are
285 // expending two (or more) additional fields to express downstream linking. If we had the concept of
286 // resolution step (an enum), we could just get by with a simple queue of Step/Callback pairs, which would
287 // trigger as needed. See how we manage baseGen/inferred fields.
290 * The generator corresponding to our YANG base type. It produces the superclass of our encapsulated type. If it is
291 * {@code null}, this generator is the root of the hierarchy.
293 private TypedefGenerator baseGen;
294 private TypeReference refType;
295 private List<GeneratedType> auxiliaryGeneratedTypes = List.of();
296 private UnionDependencies unionDependencies;
297 private List<AbstractTypeObjectGenerator<?, ?>> inferred = List.of();
300 * The type of single-element return type of the getter method associated with this generator. This is retained for
301 * run-time type purposes. It may be uninitialized, in which case this object must have a generated type.
303 private Type methodReturnTypeElement;
305 AbstractTypeObjectGenerator(final S statement, final AbstractCompositeGenerator<?, ?> parent) {
306 super(statement, parent);
307 type = statement().findFirstEffectiveSubstatement(TypeEffectiveStatement.class).orElseThrow();
311 public final List<GeneratedType> auxiliaryGeneratedTypes() {
312 return auxiliaryGeneratedTypes;
316 final void linkDependencies(final GeneratorContext context) {
317 verify(inferred != null, "Duplicate linking of %s", this);
319 final QName typeName = type.argument();
320 if (isBuiltinName(typeName)) {
321 verify(inferred.isEmpty(), "Unexpected non-empty downstreams in %s", this);
326 final AbstractExplicitGenerator<S, R> prev = previous();
328 verify(prev instanceof AbstractTypeObjectGenerator, "Unexpected previous %s", prev);
329 ((AbstractTypeObjectGenerator<S, R>) prev).linkInferred(this);
331 linkBaseGen(context.resolveTypedef(typeName));
335 private void linkInferred(final AbstractTypeObjectGenerator<?, ?> downstream) {
336 if (inferred == null) {
337 downstream.linkBaseGen(verifyNotNull(baseGen, "Mismatch on linking between %s and %s", this, downstream));
341 if (inferred.isEmpty()) {
342 inferred = new ArrayList<>(2);
344 inferred.add(downstream);
347 private void linkBaseGen(final TypedefGenerator upstreamBaseGen) {
348 verify(baseGen == null, "Attempted to replace base %s with %s in %s", baseGen, upstreamBaseGen, this);
349 final List<AbstractTypeObjectGenerator<?, ?>> downstreams = verifyNotNull(inferred,
350 "Duplicated linking of %s", this);
351 baseGen = verifyNotNull(upstreamBaseGen);
352 baseGen.addDerivedGenerator(this);
355 for (AbstractTypeObjectGenerator<?, ?> downstream : downstreams) {
356 downstream.linkBaseGen(upstreamBaseGen);
360 void bindTypeDefinition(final GeneratorContext context) {
361 if (baseGen != null) {
362 // We have registered with baseGen, it will push the type to us
366 final QName arg = type.argument();
367 if (TypeDefinitions.IDENTITYREF.equals(arg)) {
368 refType = TypeReference.identityRef(type.streamEffectiveSubstatements(BaseEffectiveStatement.class)
369 .map(BaseEffectiveStatement::argument)
370 .map(context::resolveIdentity)
371 .collect(Collectors.toUnmodifiableList()));
372 } else if (TypeDefinitions.LEAFREF.equals(arg)) {
373 final AbstractTypeObjectGenerator<?, ?> targetGenerator = context.resolveLeafref(
374 type.findFirstEffectiveSubstatementArgument(PathEffectiveStatement.class).orElseThrow());
375 checkArgument(targetGenerator != this, "Effective model contains self-referencing leaf %s",
376 statement().argument());
377 refType = TypeReference.leafRef(targetGenerator);
378 } else if (TypeDefinitions.UNION.equals(arg)) {
379 unionDependencies = new UnionDependencies(type, context);
380 LOG.trace("Resolved union {} to dependencies {}", type, unionDependencies);
383 LOG.trace("Resolved base {} to generator {}", type, refType);
384 bindDerivedGenerators(refType);
387 final void bindTypeDefinition(final @Nullable TypeReference reference) {
389 LOG.trace("Resolved derived {} to generator {}", type, refType);
392 private static boolean isBuiltinName(final QName typeName) {
393 return YangConstants.RFC6020_YANG_MODULE.equals(typeName.getModule());
396 abstract void bindDerivedGenerators(@Nullable TypeReference reference);
399 final ClassPlacement classPlacement() {
400 if (refType != null) {
401 // Reference types never create a new type
402 return ClassPlacement.NONE;
404 if (isDerivedEnumeration()) {
405 // Types derived from an enumeration never create a new type, as that would have to be a subclass of an enum
406 // and since enums are final, that can never happen.
407 return ClassPlacement.NONE;
409 return classPlacementImpl();
412 @NonNull ClassPlacement classPlacementImpl() {
413 // TODO: make this a lot more accurate by comparing the effective delta between the base type and the effective
414 // restricted type. We should not be generating a type for constructs like:
417 // type uint8 { range 0..255; }
423 // type uint8 { range 0..100; }
427 // type foo { range 0..100; }
430 // Which is relatively easy to do for integral types, but is way more problematic for 'pattern'
431 // restrictions. Nevertheless we can define the mapping in a way which can be implemented with relative
433 return baseGen != null || SIMPLE_TYPES.containsKey(type.argument()) || isAddedByUses() || isAugmenting()
434 ? ClassPlacement.NONE : ClassPlacement.MEMBER;
438 final GeneratedType getGeneratedType(final TypeBuilderFactory builderFactory) {
439 // For derived enumerations defer to base type
440 return isDerivedEnumeration() ? baseGen.getGeneratedType(builderFactory)
441 : super.getGeneratedType(builderFactory);
444 final boolean isEnumeration() {
445 return baseGen != null ? baseGen.isEnumeration() : TypeDefinitions.ENUMERATION.equals(type.argument());
448 final boolean isDerivedEnumeration() {
449 return baseGen != null && baseGen.isEnumeration();
453 Type methodReturnType(final TypeBuilderFactory builderFactory) {
454 return methodReturnElementType(builderFactory);
458 final Type runtimeJavaType() {
459 if (methodReturnTypeElement != null) {
460 return methodReturnTypeElement;
462 final var genType = generatedType();
463 if (genType.isPresent()) {
464 return genType.orElseThrow();
466 final var prev = verifyNotNull(previous(), "No previous generator for %s", this);
467 return prev.runtimeJavaType();
470 final @NonNull Type methodReturnElementType(final @NonNull TypeBuilderFactory builderFactory) {
471 var local = methodReturnTypeElement;
473 methodReturnTypeElement = local = createMethodReturnElementType(builderFactory);
478 private @NonNull Type createMethodReturnElementType(final @NonNull TypeBuilderFactory builderFactory) {
479 final GeneratedType generatedType = tryGeneratedType(builderFactory);
480 if (generatedType != null) {
481 // We have generated a type here, so return it. This covers 'bits', 'enumeration' and 'union'.
482 return generatedType;
485 if (refType != null) {
486 // This is a reference type of some kind. Defer to its judgement as to what the return type is.
487 return refType.methodReturnType(builderFactory);
490 final AbstractExplicitGenerator<?, ?> prev = previous();
492 // We have been added through augment/uses, defer to the original definition
493 return prev.methodReturnType(builderFactory);
497 if (baseGen == null) {
498 final QName qname = type.argument();
499 baseType = verifyNotNull(SIMPLE_TYPES.get(qname), "Cannot resolve type %s in %s", qname, this);
501 // We are derived from a base generator. Defer to its type for return.
502 baseType = baseGen.getGeneratedType(builderFactory);
505 return restrictType(baseType, computeRestrictions(), builderFactory);
508 private static @NonNull Type restrictType(final @NonNull Type baseType, final Restrictions restrictions,
509 final TypeBuilderFactory builderFactory) {
510 if (restrictions == null || restrictions.isEmpty()) {
511 // No additional restrictions, return base type
515 if (!(baseType instanceof GeneratedTransferObject gto)) {
516 // This is a simple Java type, just wrap it with new restrictions
517 return Types.restrictedType(baseType, restrictions);
520 // Base type is a GTO, we need to re-adjust it with new restrictions
521 final GeneratedTOBuilder builder = builderFactory.newGeneratedTOBuilder(gto.getIdentifier());
522 final GeneratedTransferObject parent = gto.getSuperType();
523 if (parent != null) {
524 builder.setExtendsType(parent);
526 builder.setRestrictions(restrictions);
527 for (GeneratedProperty gp : gto.getProperties()) {
528 builder.addProperty(gp.getName())
529 .setValue(gp.getValue())
530 .setReadOnly(gp.isReadOnly())
531 .setAccessModifier(gp.getAccessModifier())
532 .setReturnType(gp.getReturnType())
533 .setFinal(gp.isFinal())
534 .setStatic(gp.isStatic());
536 return builder.build();
540 final void addAsGetterMethodOverride(final GeneratedTypeBuilderBase<?> builder,
541 final TypeBuilderFactory builderFactory) {
542 if (!(refType instanceof ResolvedLeafref)) {
543 // We are not dealing with a leafref or have nothing to add
547 final AbstractTypeObjectGenerator<?, ?> prev =
548 (AbstractTypeObjectGenerator<?, ?>) verifyNotNull(previous(), "Missing previous link in %s", this);
549 if (prev.refType instanceof ResolvedLeafref) {
550 // We should be already inheriting the correct type
554 // Note: this may we wrapped for leaf-list, hence we need to deal with that
555 final Type myType = methodReturnType(builderFactory);
556 LOG.trace("Override of {} to {}", this, myType);
557 final MethodSignatureBuilder getter = constructGetter(builder, myType);
558 getter.addAnnotation(OVERRIDE_ANNOTATION);
559 annotateDeprecatedIfNecessary(getter);
562 final @Nullable Restrictions computeRestrictions() {
563 final List<ValueRange> length = type.findFirstEffectiveSubstatementArgument(LengthEffectiveStatement.class)
565 final List<ValueRange> range = type.findFirstEffectiveSubstatementArgument(RangeEffectiveStatement.class)
567 final List<PatternExpression> patterns = type.streamEffectiveSubstatements(PatternEffectiveStatement.class)
568 .map(PatternEffectiveStatement::argument)
569 .collect(Collectors.toUnmodifiableList());
571 if (length.isEmpty() && range.isEmpty() && patterns.isEmpty()) {
575 return BindingGeneratorUtil.getRestrictions(extractTypeDefinition());
579 final GeneratedType createTypeImpl(final TypeBuilderFactory builderFactory) {
580 if (baseGen != null) {
581 final GeneratedType baseType = baseGen.getGeneratedType(builderFactory);
582 verify(baseType instanceof GeneratedTransferObject, "Unexpected base type %s", baseType);
583 return createDerivedType(builderFactory, (GeneratedTransferObject) baseType);
586 // FIXME: why do we need this boolean?
587 final boolean isTypedef = this instanceof TypedefGenerator;
588 final QName arg = type.argument();
589 if (TypeDefinitions.BITS.equals(arg)) {
590 return createBits(builderFactory, typeName(), currentModule(), extractTypeDefinition(), isTypedef);
591 } else if (TypeDefinitions.ENUMERATION.equals(arg)) {
592 return createEnumeration(builderFactory, typeName(), currentModule(),
593 (EnumTypeDefinition) extractTypeDefinition());
594 } else if (TypeDefinitions.UNION.equals(arg)) {
595 final List<GeneratedType> tmp = new ArrayList<>(1);
596 final GeneratedTransferObject ret = createUnion(tmp, builderFactory, statement(), unionDependencies,
597 typeName(), currentModule(), type, isTypedef, extractTypeDefinition());
598 auxiliaryGeneratedTypes = List.copyOf(tmp);
601 return createSimple(builderFactory, typeName(), currentModule(),
602 verifyNotNull(SIMPLE_TYPES.get(arg), "Unhandled type %s", arg), extractTypeDefinition());
606 private static @NonNull GeneratedTransferObject createBits(final TypeBuilderFactory builderFactory,
607 final JavaTypeName typeName, final ModuleGenerator module, final TypeDefinition<?> typedef,
608 final boolean isTypedef) {
609 final GeneratedTOBuilder builder = builderFactory.newGeneratedTOBuilder(typeName);
610 builder.setTypedef(isTypedef);
611 builder.addImplementsType(BindingTypes.TYPE_OBJECT);
612 builder.setBaseType(typedef);
614 for (Bit bit : ((BitsTypeDefinition) typedef).getBits()) {
615 final String name = bit.getName();
616 GeneratedPropertyBuilder genPropertyBuilder = builder.addProperty(BindingMapping.getPropertyName(name));
617 genPropertyBuilder.setReadOnly(true);
618 genPropertyBuilder.setReturnType(BaseYangTypes.BOOLEAN_TYPE);
620 builder.addEqualsIdentity(genPropertyBuilder);
621 builder.addHashIdentity(genPropertyBuilder);
622 builder.addToStringProperty(genPropertyBuilder);
625 // builder.setSchemaPath(typedef.getPath());
626 builder.setModuleName(module.statement().argument().getLocalName());
627 builderFactory.addCodegenInformation(typedef, builder);
628 annotateDeprecatedIfNecessary(typedef, builder);
629 makeSerializable(builder);
630 return builder.build();
633 private static @NonNull Enumeration createEnumeration(final TypeBuilderFactory builderFactory,
634 final JavaTypeName typeName, final ModuleGenerator module, final EnumTypeDefinition typedef) {
635 // TODO units for typedef enum
636 final AbstractEnumerationBuilder builder = builderFactory.newEnumerationBuilder(typeName);
638 typedef.getDescription().map(BindingGeneratorUtil::encodeAngleBrackets)
639 .ifPresent(builder::setDescription);
640 typedef.getReference().ifPresent(builder::setReference);
642 builder.setModuleName(module.statement().argument().getLocalName());
643 builder.updateEnumPairsFromEnumTypeDef(typedef);
644 return builder.toInstance();
647 private static @NonNull GeneratedType createSimple(final TypeBuilderFactory builderFactory,
648 final JavaTypeName typeName, final ModuleGenerator module, final Type javaType,
649 final TypeDefinition<?> typedef) {
650 final String moduleName = module.statement().argument().getLocalName();
651 final GeneratedTOBuilder builder = builderFactory.newGeneratedTOBuilder(typeName);
652 builder.setTypedef(true);
653 builder.addImplementsType(BindingTypes.scalarTypeObject(javaType));
655 final GeneratedPropertyBuilder genPropBuilder = builder.addProperty(TypeConstants.VALUE_PROP);
656 genPropBuilder.setReturnType(javaType);
657 builder.addEqualsIdentity(genPropBuilder);
658 builder.addHashIdentity(genPropBuilder);
659 builder.addToStringProperty(genPropBuilder);
661 builder.setRestrictions(BindingGeneratorUtil.getRestrictions(typedef));
663 // builder.setSchemaPath(typedef.getPath());
664 builder.setModuleName(moduleName);
665 builderFactory.addCodegenInformation(typedef, builder);
667 annotateDeprecatedIfNecessary(typedef, builder);
669 if (javaType instanceof ConcreteType
670 // FIXME: This looks very suspicious: we should by checking for Types.STRING
671 && "String".equals(javaType.getName()) && typedef.getBaseType() != null) {
672 addStringRegExAsConstant(builder, resolveRegExpressions(typedef));
674 addUnits(builder, typedef);
676 makeSerializable(builder);
677 return builder.build();
680 private static @NonNull GeneratedTransferObject createUnion(final List<GeneratedType> auxiliaryGeneratedTypes,
681 final TypeBuilderFactory builderFactory, final EffectiveStatement<?, ?> definingStatement,
682 final UnionDependencies dependencies, final JavaTypeName typeName, final ModuleGenerator module,
683 final TypeEffectiveStatement<?> type, final boolean isTypedef, final TypeDefinition<?> typedef) {
684 final GeneratedUnionBuilder builder = builderFactory.newGeneratedUnionBuilder(typeName);
685 builder.addImplementsType(BindingTypes.TYPE_OBJECT);
686 builder.setIsUnion(true);
688 // builder.setSchemaPath(typedef.getPath());
689 builder.setModuleName(module.statement().argument().getLocalName());
690 builderFactory.addCodegenInformation(definingStatement, builder);
692 annotateDeprecatedIfNecessary(definingStatement, builder);
694 // Pattern string is the key, XSD regex is the value. The reason for this choice is that the pattern carries
695 // also negation information and hence guarantees uniqueness.
696 final Map<String, String> expressions = new HashMap<>();
698 // Linear list of properties generated from subtypes. We need this information for runtime types, as it allows
699 // direct mapping of type to corresponding property -- without having to resort to re-resolving the leafrefs
701 final List<String> typeProperties = new ArrayList<>();
703 for (EffectiveStatement<?, ?> stmt : type.effectiveSubstatements()) {
704 if (stmt instanceof TypeEffectiveStatement<?> subType) {
705 final QName subName = subType.argument();
706 final String localName = subName.getLocalName();
708 String propSource = localName;
709 final Type generatedType;
710 if (TypeDefinitions.UNION.equals(subName)) {
711 final JavaTypeName subUnionName = typeName.createEnclosed(
712 provideAvailableNameForGenTOBuilder(typeName.simpleName()));
713 final GeneratedTransferObject subUnion = createUnion(auxiliaryGeneratedTypes, builderFactory,
714 definingStatement, dependencies, subUnionName, module, subType, isTypedef,
715 subType.getTypeDefinition());
716 builder.addEnclosingTransferObject(subUnion);
717 propSource = subUnionName.simpleName();
718 generatedType = subUnion;
719 } else if (TypeDefinitions.ENUMERATION.equals(subName)) {
720 final Enumeration subEnumeration = createEnumeration(builderFactory,
721 typeName.createEnclosed(BindingMapping.getClassName(localName), "$"), module,
722 (EnumTypeDefinition) subType.getTypeDefinition());
723 builder.addEnumeration(subEnumeration);
724 generatedType = subEnumeration;
725 } else if (TypeDefinitions.BITS.equals(subName)) {
726 final GeneratedTransferObject subBits = createBits(builderFactory,
727 typeName.createEnclosed(BindingMapping.getClassName(localName), "$"), module,
728 subType.getTypeDefinition(), isTypedef);
729 builder.addEnclosingTransferObject(subBits);
730 generatedType = subBits;
731 } else if (TypeDefinitions.IDENTITYREF.equals(subName)) {
732 generatedType = verifyNotNull(dependencies.identityTypes.get(stmt),
733 "Cannot resolve identityref %s in %s", stmt, definingStatement)
734 .methodReturnType(builderFactory);
735 } else if (TypeDefinitions.LEAFREF.equals(subName)) {
736 generatedType = verifyNotNull(dependencies.leafTypes.get(stmt),
737 "Cannot resolve leafref %s in %s", stmt, definingStatement)
738 .methodReturnType(builderFactory);
740 Type baseType = SIMPLE_TYPES.get(subName);
741 if (baseType == null) {
742 // This has to be a reference to a typedef, let's lookup it up and pick up its type
743 final AbstractTypeObjectGenerator<?, ?> baseGen = verifyNotNull(
744 dependencies.baseTypes.get(subName), "Cannot resolve base type %s in %s", subName,
746 baseType = baseGen.methodReturnType(builderFactory);
748 // FIXME: This is legacy behaviour for leafrefs:
749 if (baseGen.refType instanceof TypeReference.Leafref) {
750 // if there already is a compatible property, do not generate a new one
751 final Type search = baseType;
753 final String matching = builder.getProperties().stream()
754 .filter(prop -> search == ((GeneratedPropertyBuilderImpl) prop).getReturnType())
756 .map(GeneratedPropertyBuilder::getName)
758 if (matching != null) {
759 typeProperties.add(matching);
763 // ... otherwise generate this weird property name
764 propSource = BindingMapping.getUnionLeafrefMemberName(builder.getName(),
769 expressions.putAll(resolveRegExpressions(subType.getTypeDefinition()));
771 generatedType = restrictType(baseType,
772 BindingGeneratorUtil.getRestrictions(type.getTypeDefinition()), builderFactory);
775 final String propName = BindingMapping.getPropertyName(propSource);
776 typeProperties.add(propName);
778 if (builder.containsProperty(propName)) {
780 * FIXME: this is not okay, as we are ignoring multiple base types. For example in the case of:
791 * We are ending up losing the information about 8..10 being an alternative. This is also the case
792 * for leafrefs -- we are performing property compression as well (see above). While it is alluring
793 * to merge these into 'length 1..5|8..10', that may not be generally feasible.
795 * We should resort to a counter of conflicting names, i.e. the second string would be mapped to
796 * 'string1' or similar.
801 final GeneratedPropertyBuilder propBuilder = builder
802 .addProperty(propName)
803 .setReturnType(generatedType);
805 builder.addEqualsIdentity(propBuilder);
806 builder.addHashIdentity(propBuilder);
807 builder.addToStringProperty(propBuilder);
811 // Record property names if needed
812 builder.setTypePropertyNames(typeProperties);
814 addStringRegExAsConstant(builder, expressions);
815 addUnits(builder, typedef);
817 makeSerializable(builder);
818 return builder.build();
821 // FIXME: we should not rely on TypeDefinition
822 abstract @NonNull TypeDefinition<?> extractTypeDefinition();
824 abstract @NonNull GeneratedTransferObject createDerivedType(@NonNull TypeBuilderFactory builderFactory,
825 @NonNull GeneratedTransferObject baseType);
828 * Adds to the {@code genTOBuilder} the constant which contains regular expressions from the {@code expressions}.
830 * @param genTOBuilder generated TO builder to which are {@code regular expressions} added
831 * @param expressions list of string which represent regular expressions
833 static void addStringRegExAsConstant(final GeneratedTOBuilder genTOBuilder, final Map<String, String> expressions) {
834 if (!expressions.isEmpty()) {
835 genTOBuilder.addConstant(Types.listTypeFor(BaseYangTypes.STRING_TYPE), TypeConstants.PATTERN_CONSTANT_NAME,
836 ImmutableMap.copyOf(expressions));
841 * Converts the pattern constraints from {@code typedef} to the list of the strings which represents these
844 * @param typedef extended type in which are the pattern constraints sought
845 * @return list of strings which represents the constraint patterns
846 * @throws IllegalArgumentException if <code>typedef</code> equals null
848 static Map<String, String> resolveRegExpressions(final TypeDefinition<?> typedef) {
849 return typedef instanceof StringTypeDefinition stringTypedef
850 // TODO: run diff against base ?
851 ? resolveRegExpressions(stringTypedef.getPatternConstraints())
856 * Converts the pattern constraints to the list of the strings which represents these constraints.
858 * @param patternConstraints list of pattern constraints
859 * @return list of strings which represents the constraint patterns
861 private static Map<String, String> resolveRegExpressions(final List<PatternConstraint> patternConstraints) {
862 if (patternConstraints.isEmpty()) {
863 return ImmutableMap.of();
866 final Map<String, String> regExps = Maps.newHashMapWithExpectedSize(patternConstraints.size());
867 for (PatternConstraint patternConstraint : patternConstraints) {
868 String regEx = patternConstraint.getJavaPatternString();
870 // The pattern can be inverted
871 final Optional<ModifierKind> optModifier = patternConstraint.getModifier();
872 if (optModifier.isPresent()) {
873 regEx = applyModifier(optModifier.get(), regEx);
876 regExps.put(regEx, patternConstraint.getRegularExpressionString());
883 * Returns string which contains the same value as <code>name</code> but integer suffix is incremented by one. If
884 * <code>name</code> contains no number suffix, a new suffix initialized at 1 is added. A suffix is actually
885 * composed of a '$' marker, which is safe, as no YANG identifier can contain '$', and a unsigned decimal integer.
887 * @param name string with name of augmented node
888 * @return string with the number suffix incremented by one (or 1 is added)
890 private static String provideAvailableNameForGenTOBuilder(final String name) {
891 final int dollar = name.indexOf('$');
896 final int newSuffix = Integer.parseUnsignedInt(name.substring(dollar + 1)) + 1;
897 verify(newSuffix > 0, "Suffix counter overflow");
898 return name.substring(0, dollar + 1) + newSuffix;
901 private static String applyModifier(final ModifierKind modifier, final String pattern) {
902 return switch (modifier) {
903 case INVERT_MATCH -> RegexPatterns.negatePatternString(pattern);