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.base.VerifyException;
15 import com.google.common.collect.ImmutableMap;
16 import com.google.common.collect.Maps;
17 import java.util.ArrayList;
18 import java.util.HashMap;
19 import java.util.List;
21 import java.util.Optional;
22 import java.util.stream.Collectors;
23 import org.eclipse.jdt.annotation.NonNull;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.opendaylight.mdsal.binding.generator.BindingGeneratorUtil;
26 import org.opendaylight.mdsal.binding.generator.impl.reactor.TypeReference.ResolvedLeafref;
27 import org.opendaylight.mdsal.binding.model.api.ConcreteType;
28 import org.opendaylight.mdsal.binding.model.api.Enumeration;
29 import org.opendaylight.mdsal.binding.model.api.GeneratedProperty;
30 import org.opendaylight.mdsal.binding.model.api.GeneratedTransferObject;
31 import org.opendaylight.mdsal.binding.model.api.GeneratedType;
32 import org.opendaylight.mdsal.binding.model.api.JavaTypeName;
33 import org.opendaylight.mdsal.binding.model.api.Restrictions;
34 import org.opendaylight.mdsal.binding.model.api.Type;
35 import org.opendaylight.mdsal.binding.model.api.YangSourceDefinition;
36 import org.opendaylight.mdsal.binding.model.api.type.builder.GeneratedPropertyBuilder;
37 import org.opendaylight.mdsal.binding.model.api.type.builder.GeneratedTOBuilder;
38 import org.opendaylight.mdsal.binding.model.api.type.builder.GeneratedTypeBuilderBase;
39 import org.opendaylight.mdsal.binding.model.api.type.builder.MethodSignatureBuilder;
40 import org.opendaylight.mdsal.binding.model.ri.BaseYangTypes;
41 import org.opendaylight.mdsal.binding.model.ri.BindingTypes;
42 import org.opendaylight.mdsal.binding.model.ri.TypeConstants;
43 import org.opendaylight.mdsal.binding.model.ri.Types;
44 import org.opendaylight.mdsal.binding.model.ri.generated.type.builder.AbstractEnumerationBuilder;
45 import org.opendaylight.mdsal.binding.model.ri.generated.type.builder.GeneratedPropertyBuilderImpl;
46 import org.opendaylight.mdsal.binding.runtime.api.RuntimeType;
47 import org.opendaylight.yangtools.binding.lib.RegexPatterns;
48 import org.opendaylight.yangtools.binding.lib.TypeObject;
49 import org.opendaylight.yangtools.binding.lib.contract.Naming;
50 import org.opendaylight.yangtools.concepts.Immutable;
51 import org.opendaylight.yangtools.yang.common.QName;
52 import org.opendaylight.yangtools.yang.common.YangConstants;
53 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
54 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
55 import org.opendaylight.yangtools.yang.model.api.stmt.BaseEffectiveStatement;
56 import org.opendaylight.yangtools.yang.model.api.stmt.LengthEffectiveStatement;
57 import org.opendaylight.yangtools.yang.model.api.stmt.PathEffectiveStatement;
58 import org.opendaylight.yangtools.yang.model.api.stmt.PatternEffectiveStatement;
59 import org.opendaylight.yangtools.yang.model.api.stmt.PatternExpression;
60 import org.opendaylight.yangtools.yang.model.api.stmt.RangeEffectiveStatement;
61 import org.opendaylight.yangtools.yang.model.api.stmt.TypeEffectiveStatement;
62 import org.opendaylight.yangtools.yang.model.api.stmt.ValueRange;
63 import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition;
64 import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition.Bit;
65 import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition;
66 import org.opendaylight.yangtools.yang.model.api.type.ModifierKind;
67 import org.opendaylight.yangtools.yang.model.api.type.PatternConstraint;
68 import org.opendaylight.yangtools.yang.model.api.type.StringTypeDefinition;
69 import org.opendaylight.yangtools.yang.model.api.type.TypeDefinitions;
70 import org.slf4j.Logger;
71 import org.slf4j.LoggerFactory;
74 * Common base class for {@link TypedefGenerator} and {@link AbstractTypeAwareGenerator}. This encompasses three
75 * different statements with two different semantics:
77 * <li>{@link TypedefGenerator}s always result in a generated {@link TypeObject}, even if the semantics is exactly
78 * the same as its base type. This aligns with {@code typedef} defining a new type.<li>
79 * <li>{@link LeafGenerator}s and {@link LeafListGenerator}s, on the other hand, do not generate a {@link TypeObject}
80 * unless absolutely necassary. This aligns with {@code leaf} and {@code leaf-list} being mapped onto a property
81 * of its parent type.<li>
85 * To throw a bit of confusion into the mix, there are three exceptions to those rules:
88 * {@code identityref} definitions never result in a type definition being emitted. The reason for this has to do
89 * with identity type mapping as well as history of our codebase.
92 * The problem at hand is inconsistency between the fact that identity is mapped to a {@link Class}, which is also
93 * returned from leaves which specify it like this:
107 * which results in fine-looking
111 * Class<? extends Iden> getFoo();
117 * This gets more dicey if we decide to extend the previous snippet to also include:
135 * Now we have competing requirements: {@code typedef} would like us to use encapsulation to capture the defined
136 * type, while {@code getBar()} wants us to retain shape with getFoo(), as it should not matter how the
137 * {@code identityref} was formed. We need to pick between:
142 * public class BarRef extends ScalarTypeObject<Class<? extends Iden>> {
143 * Class<? extends Iden> getValue() {
158 * Class<? extends Iden> getBar();
166 * Here the second option is more user-friendly, as the type system works along the lines of <b>reference</b>
167 * semantics, treating and {@code Bar.getBar()} and {@code Foo.getFoo()} as equivalent. The first option would
168 * force users to go through explicit encapsulation, for no added benefit as the {@code typedef} cannot possibly add
169 * anything useful to the actual type semantics.
172 * {@code leafref} definitions never result in a type definition being emitted. The reasons for this are similar to
173 * {@code identityref}, but have an additional twist: a {@leafref} can target a relative path, which may only be
174 * resolved at a particular instantiation.
176 * Take the example of the following model:
209 * The {@code typedef ref} points to outside of the grouping, and hence the type of {@code leaf foo} is polymorphic:
210 * the definition in {@code grouping grp} needs to use {@code Object}, whereas the instantiations in
211 * {@code container bar} and {@code container baz} need to use {@code String} and {@link Integer} respectively.
212 * Expressing the resulting interface contracts requires return type specialization and run-time checks. An
213 * intermediate class generated for the typedef would end up being a hindrance without any benefit.
216 * {@code enumeration} definitions never result in a derived type. This is because these are mapped to Java
217 * {@code enum}, which does not allow subclassing.
222 * At the end of the day, the mechanic translation rules are giving way to correctly mapping the semantics -- which in
223 * both of the exception cases boil down to tracking type indirection. Intermediate constructs involved in tracking
224 * type indirection in YANG constructs is therefore explicitly excluded from the generated Java code, but the Binding
225 * Specification still takes them into account when determining types as outlined above.
227 abstract class AbstractTypeObjectGenerator<S extends EffectiveStatement<?, ?>, R extends RuntimeType>
228 extends AbstractDependentGenerator<S, R> {
229 private static final class UnionDependencies implements Immutable {
230 private final Map<EffectiveStatement<?, ?>, TypeReference> identityTypes = new HashMap<>();
231 private final Map<EffectiveStatement<?, ?>, TypeReference> leafTypes = new HashMap<>();
232 private final Map<QName, TypedefGenerator> baseTypes = new HashMap<>();
234 UnionDependencies(final TypeEffectiveStatement<?> type, final GeneratorContext context) {
235 resolveUnionDependencies(context, type);
238 private void resolveUnionDependencies(final GeneratorContext context, final TypeEffectiveStatement<?> union) {
239 for (EffectiveStatement<?, ?> stmt : union.effectiveSubstatements()) {
240 if (stmt instanceof TypeEffectiveStatement<?> type) {
241 final QName typeName = type.argument();
242 if (TypeDefinitions.IDENTITYREF.equals(typeName)) {
243 if (!identityTypes.containsKey(stmt)) {
244 identityTypes.put(stmt, TypeReference.identityRef(
245 type.streamEffectiveSubstatements(BaseEffectiveStatement.class)
246 .map(BaseEffectiveStatement::argument)
247 .map(context::resolveIdentity)
248 .collect(Collectors.toUnmodifiableList())));
250 } else if (TypeDefinitions.LEAFREF.equals(typeName)) {
251 if (!leafTypes.containsKey(stmt)) {
252 leafTypes.put(stmt, TypeReference.leafRef(context.resolveLeafref(
253 type.findFirstEffectiveSubstatementArgument(PathEffectiveStatement.class)
256 } else if (TypeDefinitions.UNION.equals(typeName)) {
257 resolveUnionDependencies(context, type);
258 } else if (!isBuiltinName(typeName) && !baseTypes.containsKey(typeName)) {
259 baseTypes.put(typeName, context.resolveTypedef(typeName));
266 private static final Logger LOG = LoggerFactory.getLogger(AbstractTypeObjectGenerator.class);
267 static final ImmutableMap<QName, Type> SIMPLE_TYPES = ImmutableMap.<QName, Type>builder()
268 .put(TypeDefinitions.BINARY, BaseYangTypes.BINARY_TYPE)
269 .put(TypeDefinitions.BOOLEAN, BaseYangTypes.BOOLEAN_TYPE)
270 .put(TypeDefinitions.DECIMAL64, BaseYangTypes.DECIMAL64_TYPE)
271 .put(TypeDefinitions.EMPTY, BaseYangTypes.EMPTY_TYPE)
272 .put(TypeDefinitions.INSTANCE_IDENTIFIER, BaseYangTypes.INSTANCE_IDENTIFIER)
273 .put(TypeDefinitions.INT8, BaseYangTypes.INT8_TYPE)
274 .put(TypeDefinitions.INT16, BaseYangTypes.INT16_TYPE)
275 .put(TypeDefinitions.INT32, BaseYangTypes.INT32_TYPE)
276 .put(TypeDefinitions.INT64, BaseYangTypes.INT64_TYPE)
277 .put(TypeDefinitions.STRING, BaseYangTypes.STRING_TYPE)
278 .put(TypeDefinitions.UINT8, BaseYangTypes.UINT8_TYPE)
279 .put(TypeDefinitions.UINT16, BaseYangTypes.UINT16_TYPE)
280 .put(TypeDefinitions.UINT32, BaseYangTypes.UINT32_TYPE)
281 .put(TypeDefinitions.UINT64, BaseYangTypes.UINT64_TYPE)
284 private final TypeEffectiveStatement<?> type;
286 // FIXME: these fields should be better-controlled with explicit sequencing guards. It it currently stands, we are
287 // expending two (or more) additional fields to express downstream linking. If we had the concept of
288 // resolution step (an enum), we could just get by with a simple queue of Step/Callback pairs, which would
289 // trigger as needed. See how we manage baseGen/inferred fields.
292 * The generator corresponding to our YANG base type. It produces the superclass of our encapsulated type. If it is
293 * {@code null}, this generator is the root of the hierarchy.
295 private TypedefGenerator baseGen;
296 private TypeReference refType;
297 private List<GeneratedType> auxiliaryGeneratedTypes = List.of();
298 private UnionDependencies unionDependencies;
299 private List<AbstractTypeObjectGenerator<?, ?>> inferred = List.of();
302 * The type of single-element return type of the getter method associated with this generator. This is retained for
303 * run-time type purposes. It may be uninitialized, in which case this object must have a generated type.
305 private Type methodReturnTypeElement;
307 AbstractTypeObjectGenerator(final S statement, final AbstractCompositeGenerator<?, ?> parent) {
308 super(statement, parent);
309 type = statement().findFirstEffectiveSubstatement(TypeEffectiveStatement.class).orElseThrow();
313 public final List<GeneratedType> auxiliaryGeneratedTypes() {
314 return auxiliaryGeneratedTypes;
318 final void linkDependencies(final GeneratorContext context) {
319 verify(inferred != null, "Duplicate linking of %s", this);
321 final QName typeName = type.argument();
322 if (isBuiltinName(typeName)) {
323 verify(inferred.isEmpty(), "Unexpected non-empty downstreams in %s", this);
328 final AbstractExplicitGenerator<S, R> prev = previous();
330 verify(prev instanceof AbstractTypeObjectGenerator, "Unexpected previous %s", prev);
331 ((AbstractTypeObjectGenerator<S, R>) prev).linkInferred(this);
333 linkBaseGen(context.resolveTypedef(typeName));
337 private void linkInferred(final AbstractTypeObjectGenerator<?, ?> downstream) {
338 if (inferred == null) {
339 downstream.linkBaseGen(verifyNotNull(baseGen, "Mismatch on linking between %s and %s", this, downstream));
343 if (inferred.isEmpty()) {
344 inferred = new ArrayList<>(2);
346 inferred.add(downstream);
349 private void linkBaseGen(final TypedefGenerator upstreamBaseGen) {
350 verify(baseGen == null, "Attempted to replace base %s with %s in %s", baseGen, upstreamBaseGen, this);
351 final List<AbstractTypeObjectGenerator<?, ?>> downstreams = verifyNotNull(inferred,
352 "Duplicated linking of %s", this);
353 baseGen = verifyNotNull(upstreamBaseGen);
354 baseGen.addDerivedGenerator(this);
357 for (AbstractTypeObjectGenerator<?, ?> downstream : downstreams) {
358 downstream.linkBaseGen(upstreamBaseGen);
362 void bindTypeDefinition(final GeneratorContext context) {
363 if (baseGen != null) {
364 // We have registered with baseGen, it will push the type to us
368 final QName arg = type.argument();
369 if (TypeDefinitions.IDENTITYREF.equals(arg)) {
370 refType = TypeReference.identityRef(type.streamEffectiveSubstatements(BaseEffectiveStatement.class)
371 .map(BaseEffectiveStatement::argument)
372 .map(context::resolveIdentity)
373 .collect(Collectors.toUnmodifiableList()));
374 } else if (TypeDefinitions.LEAFREF.equals(arg)) {
375 refType = resolveLeafref(context);
376 } else if (TypeDefinitions.UNION.equals(arg)) {
377 unionDependencies = new UnionDependencies(type, context);
378 LOG.trace("Resolved union {} to dependencies {}", type, unionDependencies);
381 LOG.trace("Resolved base {} to generator {}", type, refType);
382 bindDerivedGenerators(refType);
385 final void bindTypeDefinition(final @Nullable TypeReference reference) {
387 LOG.trace("Resolved derived {} to generator {}", type, refType);
390 private @NonNull TypeReference resolveLeafref(final GeneratorContext context) {
391 final AbstractTypeObjectGenerator<?, ?> targetGenerator;
393 targetGenerator = context.resolveLeafref(
394 type.findFirstEffectiveSubstatementArgument(PathEffectiveStatement.class).orElseThrow());
395 } catch (IllegalArgumentException e) {
396 return TypeReference.leafRef(e);
399 checkArgument(targetGenerator != this, "Effective model contains self-referencing leaf %s",
400 statement().argument());
401 return TypeReference.leafRef(targetGenerator);
404 private static boolean isBuiltinName(final QName typeName) {
405 return YangConstants.RFC6020_YANG_MODULE.equals(typeName.getModule());
408 abstract void bindDerivedGenerators(@Nullable TypeReference reference);
411 final ClassPlacement classPlacement() {
412 if (refType != null) {
413 // Reference types never create a new type
414 return ClassPlacement.NONE;
416 if (isDerivedEnumeration()) {
417 // Types derived from an enumeration never create a new type, as that would have to be a subclass of an enum
418 // and since enums are final, that can never happen.
419 return ClassPlacement.NONE;
421 return classPlacementImpl();
424 @NonNull ClassPlacement classPlacementImpl() {
425 // TODO: make this a lot more accurate by comparing the effective delta between the base type and the effective
426 // restricted type. We should not be generating a type for constructs like:
429 // type uint8 { range 0..255; }
435 // type uint8 { range 0..100; }
439 // type foo { range 0..100; }
442 // Which is relatively easy to do for integral types, but is way more problematic for 'pattern'
443 // restrictions. Nevertheless we can define the mapping in a way which can be implemented with relative
445 return baseGen != null || SIMPLE_TYPES.containsKey(type.argument()) || isAddedByUses() || isAugmenting()
446 ? ClassPlacement.NONE : ClassPlacement.MEMBER;
450 final GeneratedType getGeneratedType(final TypeBuilderFactory builderFactory) {
451 // For derived enumerations defer to base type
452 return isDerivedEnumeration() ? baseGen.getGeneratedType(builderFactory)
453 : super.getGeneratedType(builderFactory);
456 final boolean isEnumeration() {
457 return baseGen != null ? baseGen.isEnumeration() : TypeDefinitions.ENUMERATION.equals(type.argument());
460 final boolean isDerivedEnumeration() {
461 return baseGen != null && baseGen.isEnumeration();
465 Type methodReturnType(final TypeBuilderFactory builderFactory) {
466 return methodReturnElementType(builderFactory);
470 final Type runtimeJavaType() {
471 if (methodReturnTypeElement != null) {
472 return methodReturnTypeElement;
474 final var genType = generatedType();
475 if (genType != null) {
478 final var prev = verifyNotNull(previous(), "No previous generator for %s", this);
479 return prev.runtimeJavaType();
482 final @NonNull Type methodReturnElementType(final @NonNull TypeBuilderFactory builderFactory) {
483 var local = methodReturnTypeElement;
485 methodReturnTypeElement = local = createMethodReturnElementType(builderFactory);
490 private @NonNull Type createMethodReturnElementType(final @NonNull TypeBuilderFactory builderFactory) {
491 final GeneratedType generatedType = tryGeneratedType(builderFactory);
492 if (generatedType != null) {
493 // We have generated a type here, so return it. This covers 'bits', 'enumeration' and 'union'.
494 return generatedType;
497 if (refType != null) {
498 // This is a reference type of some kind. Defer to its judgement as to what the return type is.
499 return refType.methodReturnType(builderFactory);
502 final AbstractExplicitGenerator<?, ?> prev = previous();
504 // We have been added through augment/uses, defer to the original definition
505 return prev.methodReturnType(builderFactory);
509 if (baseGen == null) {
510 final QName qname = type.argument();
511 baseType = verifyNotNull(SIMPLE_TYPES.get(qname), "Cannot resolve type %s in %s", qname, this);
513 // We are derived from a base generator. Defer to its type for return.
514 baseType = baseGen.getGeneratedType(builderFactory);
517 return restrictType(baseType, computeRestrictions(), builderFactory);
520 private static @NonNull Type restrictType(final @NonNull Type baseType, final Restrictions restrictions,
521 final TypeBuilderFactory builderFactory) {
522 if (restrictions == null || restrictions.isEmpty()) {
523 // No additional restrictions, return base type
527 if (!(baseType instanceof GeneratedTransferObject gto)) {
528 // This is a simple Java type, just wrap it with new restrictions
529 return Types.restrictedType(baseType, restrictions);
532 // Base type is a GTO, we need to re-adjust it with new restrictions
533 final GeneratedTOBuilder builder = builderFactory.newGeneratedTOBuilder(gto.getIdentifier());
534 final GeneratedTransferObject parent = gto.getSuperType();
535 if (parent != null) {
536 builder.setExtendsType(parent);
538 builder.setRestrictions(restrictions);
539 for (GeneratedProperty gp : gto.getProperties()) {
540 builder.addProperty(gp.getName())
541 .setValue(gp.getValue())
542 .setReadOnly(gp.isReadOnly())
543 .setAccessModifier(gp.getAccessModifier())
544 .setReturnType(gp.getReturnType())
545 .setFinal(gp.isFinal())
546 .setStatic(gp.isStatic());
548 return builder.build();
552 final void addAsGetterMethodOverride(final GeneratedTypeBuilderBase<?> builder,
553 final TypeBuilderFactory builderFactory) {
554 if (!(refType instanceof ResolvedLeafref)) {
555 // We are not dealing with a leafref or have nothing to add
559 final AbstractTypeObjectGenerator<?, ?> prev =
560 (AbstractTypeObjectGenerator<?, ?>) verifyNotNull(previous(), "Missing previous link in %s", this);
561 if (prev.refType instanceof ResolvedLeafref) {
562 // We should be already inheriting the correct type
566 // Note: this may we wrapped for leaf-list, hence we need to deal with that
567 final Type myType = methodReturnType(builderFactory);
568 LOG.trace("Override of {} to {}", this, myType);
569 final MethodSignatureBuilder getter = constructGetter(builder, myType);
570 getter.addAnnotation(OVERRIDE_ANNOTATION);
571 annotateDeprecatedIfNecessary(getter);
574 final @Nullable Restrictions computeRestrictions() {
575 final List<ValueRange> length = type.findFirstEffectiveSubstatementArgument(LengthEffectiveStatement.class)
577 final List<ValueRange> range = type.findFirstEffectiveSubstatementArgument(RangeEffectiveStatement.class)
579 final List<PatternExpression> patterns = type.streamEffectiveSubstatements(PatternEffectiveStatement.class)
580 .map(PatternEffectiveStatement::argument)
581 .collect(Collectors.toUnmodifiableList());
583 if (length.isEmpty() && range.isEmpty() && patterns.isEmpty()) {
587 return BindingGeneratorUtil.getRestrictions(extractTypeDefinition());
591 final GeneratedType createTypeImpl(final TypeBuilderFactory builderFactory) {
592 if (baseGen != null) {
593 final GeneratedType baseType = baseGen.getGeneratedType(builderFactory);
594 verify(baseType instanceof GeneratedTransferObject, "Unexpected base type %s", baseType);
595 return createDerivedType(builderFactory, (GeneratedTransferObject) baseType);
598 // FIXME: why do we need this boolean?
599 final boolean isTypedef = this instanceof TypedefGenerator;
600 final QName arg = type.argument();
601 if (TypeDefinitions.BITS.equals(arg)) {
602 return createBits(builderFactory, statement(), typeName(), currentModule(),
603 (BitsTypeDefinition) extractTypeDefinition(), isTypedef);
604 } else if (TypeDefinitions.ENUMERATION.equals(arg)) {
605 return createEnumeration(builderFactory, statement(), typeName(), currentModule(),
606 (EnumTypeDefinition) extractTypeDefinition());
607 } else if (TypeDefinitions.UNION.equals(arg)) {
608 final List<GeneratedType> tmp = new ArrayList<>(1);
609 final GeneratedTransferObject ret = createUnion(tmp, builderFactory, statement(), unionDependencies,
610 typeName(), currentModule(), type, isTypedef, extractTypeDefinition());
611 auxiliaryGeneratedTypes = List.copyOf(tmp);
614 return createSimple(builderFactory, statement(), typeName(), currentModule(),
615 verifyNotNull(SIMPLE_TYPES.get(arg), "Unhandled type %s", arg), extractTypeDefinition());
619 private static @NonNull GeneratedTransferObject createBits(final TypeBuilderFactory builderFactory,
620 final EffectiveStatement<?, ?> definingStatement, final JavaTypeName typeName, final ModuleGenerator module,
621 final BitsTypeDefinition typedef, final boolean isTypedef) {
622 final GeneratedTOBuilder builder = builderFactory.newGeneratedTOBuilder(typeName);
623 builder.setTypedef(isTypedef);
624 builder.addImplementsType(BindingTypes.BITS_TYPE_OBJECT);
625 builder.setBaseType(typedef);
626 YangSourceDefinition.of(module.statement(), definingStatement).ifPresent(builder::setYangSourceDefinition);
628 for (Bit bit : typedef.getBits()) {
629 final String name = bit.getName();
630 GeneratedPropertyBuilder genPropertyBuilder = builder.addProperty(Naming.getPropertyName(name));
631 genPropertyBuilder.setReadOnly(true);
632 genPropertyBuilder.setReturnType(Types.primitiveBooleanType());
634 builder.addEqualsIdentity(genPropertyBuilder);
635 builder.addHashIdentity(genPropertyBuilder);
636 builder.addToStringProperty(genPropertyBuilder);
638 builder.addConstant(Types.immutableSetTypeFor(Types.STRING), TypeConstants.VALID_NAMES_NAME, typedef);
640 // builder.setSchemaPath(typedef.getPath());
641 builder.setModuleName(module.statement().argument().getLocalName());
642 builderFactory.addCodegenInformation(typedef, builder);
643 annotateDeprecatedIfNecessary(typedef, builder);
644 makeSerializable(builder);
645 return builder.build();
648 private static @NonNull Enumeration createEnumeration(final TypeBuilderFactory builderFactory,
649 final EffectiveStatement<?, ?> definingStatement, final JavaTypeName typeName,
650 final ModuleGenerator module, final EnumTypeDefinition typedef) {
651 // TODO units for typedef enum
652 final AbstractEnumerationBuilder builder = builderFactory.newEnumerationBuilder(typeName);
653 YangSourceDefinition.of(module.statement(), definingStatement).ifPresent(builder::setYangSourceDefinition);
655 typedef.getDescription().map(BindingGeneratorUtil::encodeAngleBrackets)
656 .ifPresent(builder::setDescription);
657 typedef.getReference().ifPresent(builder::setReference);
659 builder.setModuleName(module.statement().argument().getLocalName());
660 builder.updateEnumPairsFromEnumTypeDef(typedef);
661 return builder.toInstance();
664 private static @NonNull GeneratedType createSimple(final TypeBuilderFactory builderFactory,
665 final EffectiveStatement<?, ?> definingStatement, final JavaTypeName typeName, final ModuleGenerator module,
666 final Type javaType, final TypeDefinition<?> typedef) {
667 final String moduleName = module.statement().argument().getLocalName();
668 final GeneratedTOBuilder builder = builderFactory.newGeneratedTOBuilder(typeName);
669 builder.setTypedef(true);
670 builder.addImplementsType(BindingTypes.scalarTypeObject(javaType));
671 YangSourceDefinition.of(module.statement(), definingStatement).ifPresent(builder::setYangSourceDefinition);
673 final GeneratedPropertyBuilder genPropBuilder = builder.addProperty(TypeConstants.VALUE_PROP);
674 genPropBuilder.setReturnType(javaType);
675 builder.addEqualsIdentity(genPropBuilder);
676 builder.addHashIdentity(genPropBuilder);
677 builder.addToStringProperty(genPropBuilder);
679 builder.setRestrictions(BindingGeneratorUtil.getRestrictions(typedef));
681 // builder.setSchemaPath(typedef.getPath());
682 builder.setModuleName(moduleName);
683 builderFactory.addCodegenInformation(typedef, builder);
685 annotateDeprecatedIfNecessary(typedef, builder);
687 if (javaType instanceof ConcreteType
688 // FIXME: This looks very suspicious: we should by checking for Types.STRING
689 && "String".equals(javaType.getName()) && typedef.getBaseType() != null) {
690 addStringRegExAsConstant(builder, resolveRegExpressions(typedef));
692 addUnits(builder, typedef);
694 makeSerializable(builder);
695 return builder.build();
698 private static @NonNull GeneratedTransferObject createUnion(final List<GeneratedType> auxiliaryGeneratedTypes,
699 final TypeBuilderFactory builderFactory, final EffectiveStatement<?, ?> definingStatement,
700 final UnionDependencies dependencies, final JavaTypeName typeName, final ModuleGenerator module,
701 final TypeEffectiveStatement<?> type, final boolean isTypedef, final TypeDefinition<?> typedef) {
702 final GeneratedUnionBuilder builder = builderFactory.newGeneratedUnionBuilder(typeName);
703 YangSourceDefinition.of(module.statement(), definingStatement).ifPresent(builder::setYangSourceDefinition);
704 builder.addImplementsType(BindingTypes.UNION_TYPE_OBJECT);
705 builder.setIsUnion(true);
707 // builder.setSchemaPath(typedef.getPath());
708 builder.setModuleName(module.statement().argument().getLocalName());
709 builderFactory.addCodegenInformation(definingStatement, builder);
711 annotateDeprecatedIfNecessary(definingStatement, builder);
713 // Pattern string is the key, XSD regex is the value. The reason for this choice is that the pattern carries
714 // also negation information and hence guarantees uniqueness.
715 final Map<String, String> expressions = new HashMap<>();
717 // Linear list of properties generated from subtypes. We need this information for runtime types, as it allows
718 // direct mapping of type to corresponding property -- without having to resort to re-resolving the leafrefs
720 final List<String> typeProperties = new ArrayList<>();
722 for (EffectiveStatement<?, ?> stmt : type.effectiveSubstatements()) {
723 if (stmt instanceof TypeEffectiveStatement<?> subType) {
724 final QName subName = subType.argument();
725 final String localName = subName.getLocalName();
727 String propSource = localName;
728 final Type generatedType;
729 if (TypeDefinitions.UNION.equals(subName)) {
730 final JavaTypeName subUnionName = typeName.createEnclosed(
731 provideAvailableNameForGenTOBuilder(typeName.simpleName()));
732 final GeneratedTransferObject subUnion = createUnion(auxiliaryGeneratedTypes, builderFactory,
733 definingStatement, dependencies, subUnionName, module, subType, isTypedef,
734 subType.getTypeDefinition());
735 builder.addEnclosingTransferObject(subUnion);
736 propSource = subUnionName.simpleName();
737 generatedType = subUnion;
738 } else if (TypeDefinitions.ENUMERATION.equals(subName)) {
739 final Enumeration subEnumeration = createEnumeration(builderFactory, definingStatement,
740 typeName.createEnclosed(Naming.getClassName(localName), "$"), module,
741 (EnumTypeDefinition) subType.getTypeDefinition());
742 builder.addEnumeration(subEnumeration);
743 generatedType = subEnumeration;
744 } else if (TypeDefinitions.BITS.equals(subName)) {
745 final GeneratedTransferObject subBits = createBits(builderFactory, definingStatement,
746 typeName.createEnclosed(Naming.getClassName(localName), "$"), module,
747 (BitsTypeDefinition) subType.getTypeDefinition(), isTypedef);
748 builder.addEnclosingTransferObject(subBits);
749 generatedType = subBits;
750 } else if (TypeDefinitions.IDENTITYREF.equals(subName)) {
751 propSource = stmt.findFirstEffectiveSubstatement(BaseEffectiveStatement.class)
752 .orElseThrow(() -> new VerifyException(String.format("Invalid identityref "
753 + "definition %s in %s, missing BASE statement", stmt, definingStatement)))
754 .argument().getLocalName();
755 generatedType = verifyNotNull(dependencies.identityTypes.get(stmt),
756 "Cannot resolve identityref %s in %s", stmt, definingStatement)
757 .methodReturnType(builderFactory);
758 } else if (TypeDefinitions.LEAFREF.equals(subName)) {
759 generatedType = verifyNotNull(dependencies.leafTypes.get(stmt),
760 "Cannot resolve leafref %s in %s", stmt, definingStatement)
761 .methodReturnType(builderFactory);
763 Type baseType = SIMPLE_TYPES.get(subName);
764 if (baseType == null) {
765 // This has to be a reference to a typedef, let's lookup it up and pick up its type
766 final AbstractTypeObjectGenerator<?, ?> baseGen = verifyNotNull(
767 dependencies.baseTypes.get(subName), "Cannot resolve base type %s in %s", subName,
769 baseType = baseGen.methodReturnType(builderFactory);
771 // FIXME: This is legacy behaviour for leafrefs:
772 if (baseGen.refType instanceof TypeReference.Leafref) {
773 // if there already is a compatible property, do not generate a new one
774 final Type search = baseType;
776 final String matching = builder.getProperties().stream()
777 .filter(prop -> search == ((GeneratedPropertyBuilderImpl) prop).getReturnType())
779 .map(GeneratedPropertyBuilder::getName)
781 if (matching != null) {
782 typeProperties.add(matching);
786 // ... otherwise generate this weird property name
787 propSource = Naming.getUnionLeafrefMemberName(builder.getName(), baseType.getName());
791 expressions.putAll(resolveRegExpressions(subType.getTypeDefinition()));
793 generatedType = restrictType(baseType,
794 BindingGeneratorUtil.getRestrictions(type.getTypeDefinition()), builderFactory);
797 final String propName = Naming.getPropertyName(propSource);
798 typeProperties.add(propName);
800 if (builder.containsProperty(propName)) {
802 * FIXME: this is not okay, as we are ignoring multiple base types. For example in the case of:
813 * We are ending up losing the information about 8..10 being an alternative. This is also the case
814 * for leafrefs -- we are performing property compression as well (see above). While it is alluring
815 * to merge these into 'length 1..5|8..10', that may not be generally feasible.
817 * We should resort to a counter of conflicting names, i.e. the second string would be mapped to
818 * 'string1' or similar.
823 final GeneratedPropertyBuilder propBuilder = builder
824 .addProperty(propName)
825 .setReturnType(generatedType);
827 builder.addEqualsIdentity(propBuilder);
828 builder.addHashIdentity(propBuilder);
829 builder.addToStringProperty(propBuilder);
833 // Record property names if needed
834 builder.setTypePropertyNames(typeProperties);
836 addStringRegExAsConstant(builder, expressions);
837 addUnits(builder, typedef);
839 makeSerializable(builder);
840 return builder.build();
843 // FIXME: we should not rely on TypeDefinition
844 abstract @NonNull TypeDefinition<?> extractTypeDefinition();
846 abstract @NonNull GeneratedTransferObject createDerivedType(@NonNull TypeBuilderFactory builderFactory,
847 @NonNull GeneratedTransferObject baseType);
850 * Adds to the {@code genTOBuilder} the constant which contains regular expressions from the {@code expressions}.
852 * @param genTOBuilder generated TO builder to which are {@code regular expressions} added
853 * @param expressions list of string which represent regular expressions
855 static void addStringRegExAsConstant(final GeneratedTOBuilder genTOBuilder, final Map<String, String> expressions) {
856 if (!expressions.isEmpty()) {
857 genTOBuilder.addConstant(Types.listTypeFor(BaseYangTypes.STRING_TYPE), TypeConstants.PATTERN_CONSTANT_NAME,
858 ImmutableMap.copyOf(expressions));
863 * Converts the pattern constraints from {@code typedef} to the list of the strings which represents these
866 * @param typedef extended type in which are the pattern constraints sought
867 * @return list of strings which represents the constraint patterns
868 * @throws IllegalArgumentException if <code>typedef</code> equals null
870 static Map<String, String> resolveRegExpressions(final TypeDefinition<?> typedef) {
871 return typedef instanceof StringTypeDefinition stringTypedef
872 // TODO: run diff against base ?
873 ? resolveRegExpressions(stringTypedef.getPatternConstraints())
878 * Converts the pattern constraints to the list of the strings which represents these constraints.
880 * @param patternConstraints list of pattern constraints
881 * @return list of strings which represents the constraint patterns
883 private static Map<String, String> resolveRegExpressions(final List<PatternConstraint> patternConstraints) {
884 if (patternConstraints.isEmpty()) {
885 return ImmutableMap.of();
888 final Map<String, String> regExps = Maps.newHashMapWithExpectedSize(patternConstraints.size());
889 for (PatternConstraint patternConstraint : patternConstraints) {
890 String regEx = patternConstraint.getJavaPatternString();
892 // The pattern can be inverted
893 final Optional<ModifierKind> optModifier = patternConstraint.getModifier();
894 if (optModifier.isPresent()) {
895 regEx = applyModifier(optModifier.orElseThrow(), regEx);
898 regExps.put(regEx, patternConstraint.getRegularExpressionString());
905 * Returns string which contains the same value as <code>name</code> but integer suffix is incremented by one. If
906 * <code>name</code> contains no number suffix, a new suffix initialized at 1 is added. A suffix is actually
907 * composed of a '$' marker, which is safe, as no YANG identifier can contain '$', and a unsigned decimal integer.
909 * @param name string with name of augmented node
910 * @return string with the number suffix incremented by one (or 1 is added)
912 private static String provideAvailableNameForGenTOBuilder(final String name) {
913 final int dollar = name.indexOf('$');
918 final int newSuffix = Integer.parseUnsignedInt(name.substring(dollar + 1)) + 1;
919 verify(newSuffix > 0, "Suffix counter overflow");
920 return name.substring(0, dollar + 1) + newSuffix;
923 private static String applyModifier(final ModifierKind modifier, final String pattern) {
924 return switch (modifier) {
925 case INVERT_MATCH -> RegexPatterns.negatePatternString(pattern);