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.Verify.verify;
11 import static com.google.common.base.Verify.verifyNotNull;
13 import com.google.common.collect.ImmutableMap;
14 import com.google.common.collect.Maps;
15 import java.util.ArrayList;
16 import java.util.HashMap;
17 import java.util.List;
19 import java.util.Optional;
20 import java.util.stream.Collectors;
21 import org.eclipse.jdt.annotation.NonNull;
22 import org.eclipse.jdt.annotation.Nullable;
23 import org.opendaylight.mdsal.binding.generator.impl.reactor.TypeReference.ResolvedLeafref;
24 import org.opendaylight.mdsal.binding.model.api.AccessModifier;
25 import org.opendaylight.mdsal.binding.model.api.ConcreteType;
26 import org.opendaylight.mdsal.binding.model.api.Enumeration;
27 import org.opendaylight.mdsal.binding.model.api.GeneratedProperty;
28 import org.opendaylight.mdsal.binding.model.api.GeneratedTransferObject;
29 import org.opendaylight.mdsal.binding.model.api.GeneratedType;
30 import org.opendaylight.mdsal.binding.model.api.JavaTypeName;
31 import org.opendaylight.mdsal.binding.model.api.Restrictions;
32 import org.opendaylight.mdsal.binding.model.api.Type;
33 import org.opendaylight.mdsal.binding.model.api.type.builder.GeneratedPropertyBuilder;
34 import org.opendaylight.mdsal.binding.model.api.type.builder.GeneratedTOBuilder;
35 import org.opendaylight.mdsal.binding.model.api.type.builder.GeneratedTypeBuilderBase;
36 import org.opendaylight.mdsal.binding.model.api.type.builder.MethodSignatureBuilder;
37 import org.opendaylight.mdsal.binding.model.util.BaseYangTypes;
38 import org.opendaylight.mdsal.binding.model.util.BindingGeneratorUtil;
39 import org.opendaylight.mdsal.binding.model.util.BindingTypes;
40 import org.opendaylight.mdsal.binding.model.util.TypeConstants;
41 import org.opendaylight.mdsal.binding.model.util.Types;
42 import org.opendaylight.mdsal.binding.model.util.generated.type.builder.AbstractEnumerationBuilder;
43 import org.opendaylight.mdsal.binding.model.util.generated.type.builder.GeneratedPropertyBuilderImpl;
44 import org.opendaylight.mdsal.binding.spec.naming.BindingMapping;
45 import org.opendaylight.yangtools.concepts.Immutable;
46 import org.opendaylight.yangtools.yang.binding.RegexPatterns;
47 import org.opendaylight.yangtools.yang.binding.TypeObject;
48 import org.opendaylight.yangtools.yang.common.QName;
49 import org.opendaylight.yangtools.yang.common.YangConstants;
50 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
51 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
52 import org.opendaylight.yangtools.yang.model.api.stmt.BaseEffectiveStatement;
53 import org.opendaylight.yangtools.yang.model.api.stmt.LengthEffectiveStatement;
54 import org.opendaylight.yangtools.yang.model.api.stmt.PathEffectiveStatement;
55 import org.opendaylight.yangtools.yang.model.api.stmt.PatternEffectiveStatement;
56 import org.opendaylight.yangtools.yang.model.api.stmt.PatternExpression;
57 import org.opendaylight.yangtools.yang.model.api.stmt.RangeEffectiveStatement;
58 import org.opendaylight.yangtools.yang.model.api.stmt.TypeEffectiveStatement;
59 import org.opendaylight.yangtools.yang.model.api.stmt.ValueRange;
60 import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition;
61 import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition.Bit;
62 import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition;
63 import org.opendaylight.yangtools.yang.model.api.type.ModifierKind;
64 import org.opendaylight.yangtools.yang.model.api.type.PatternConstraint;
65 import org.opendaylight.yangtools.yang.model.api.type.StringTypeDefinition;
66 import org.opendaylight.yangtools.yang.model.api.type.TypeDefinitions;
67 import org.slf4j.Logger;
68 import org.slf4j.LoggerFactory;
71 * Common base class for {@link TypedefGenerator} and {@link AbstractTypeAwareGenerator}. This encompasses three
72 * different statements with two different semantics:
74 * <li>{@link TypedefGenerator}s always result in a generated {@link TypeObject}, even if the semantics is exactly
75 * the same as its base type. This aligns with {@code typedef} defining a new type.<li>
76 * <li>{@link LeafGenerator}s and {@link LeafListGenerator}s, on the other hand, do not generate a {@link TypeObject}
77 * unless absolutely necassary. This aligns with {@code leaf} and {@code leaf-list} being mapped onto a property
78 * of its parent type.<li>
82 * To throw a bit of confusion into the mix, there are three exceptions to those rules:
85 * {@code identityref} definitions never result in a type definition being emitted. The reason for this has to do
86 * with identity type mapping as well as history of our codebase.
89 * The problem at hand is inconsistency between the fact that identity is mapped to a {@link Class}, which is also
90 * returned from leaves which specify it like this:
104 * which results in fine-looking
108 * Class<? extends Iden> getFoo();
114 * This gets more dicey if we decide to extend the previous snippet to also include:
132 * Now we have competing requirements: {@code typedef} would like us to use encapsulation to capture the defined
133 * type, while {@code getBar()} wants us to retain shape with getFoo(), as it should not matter how the
134 * {@code identityref} was formed. We need to pick between:
139 * public class BarRef extends ScalarTypeObject<Class<? extends Iden>> {
140 * Class<? extends Iden> getValue() {
155 * Class<? extends Iden> getBar();
163 * Here the second option is more user-friendly, as the type system works along the lines of <b>reference</b>
164 * semantics, treating and {@code Bar.getBar()} and {@code Foo.getFoo()} as equivalent. The first option would
165 * force users to go through explicit encapsulation, for no added benefit as the {@code typedef} cannot possibly add
166 * anything useful to the actual type semantics.
169 * {@code leafref} definitions never result in a type definition being emitted. The reasons for this are similar to
170 * {@code identityref}, but have an additional twist: a {@leafref} can target a relative path, which may only be
171 * resolved at a particular instantiation.
173 * Take the example of the following model:
206 * The {@code typedef ref} points to outside of the grouping, and hence the type of {@code leaf foo} is polymorphic:
207 * the definition in {@code grouping grp} needs to use {@code Object}, whereas the instantiations in
208 * {@code container bar} and {@code container baz} need to use {@code String} and {@link Integer} respectively.
209 * Expressing the resulting interface contracts requires return type specialization and run-time checks. An
210 * intermediate class generated for the typedef would end up being a hindrance without any benefit.
213 * {@code enumeration} definitions never result in a derived type. This is because these are mapped to Java
214 * {@code enum}, which does not allow subclassing.
219 * At the end of the day, the mechanic translation rules are giving way to correctly mapping the semantics -- which in
220 * both of the exception cases boil down to tracking type indirection. Intermediate constructs involved in tracking
221 * type indirection in YANG constructs is therefore explicitly excluded from the generated Java code, but the Binding
222 * Specification still takes them into account when determining types as outlined above.
224 abstract class AbstractTypeObjectGenerator<T extends EffectiveStatement<?, ?>> extends AbstractDependentGenerator<T> {
225 private static final class UnionDependencies implements Immutable {
226 private final Map<EffectiveStatement<?, ?>, TypeReference> identityTypes = new HashMap<>();
227 private final Map<EffectiveStatement<?, ?>, TypeReference> leafTypes = new HashMap<>();
228 private final Map<QName, TypedefGenerator> baseTypes = new HashMap<>();
230 UnionDependencies(final TypeEffectiveStatement<?> type, final GeneratorContext context) {
231 resolveUnionDependencies(context, type);
234 private void resolveUnionDependencies(final GeneratorContext context, final TypeEffectiveStatement<?> union) {
235 for (EffectiveStatement<?, ?> stmt : union.effectiveSubstatements()) {
236 if (stmt instanceof TypeEffectiveStatement) {
237 final TypeEffectiveStatement<?> type = (TypeEffectiveStatement<?>) stmt;
238 final QName typeName = type.argument();
239 if (TypeDefinitions.IDENTITYREF.equals(typeName)) {
240 if (!identityTypes.containsKey(stmt)) {
241 identityTypes.put(stmt, TypeReference.identityRef(
242 type.streamEffectiveSubstatements(BaseEffectiveStatement.class)
243 .map(BaseEffectiveStatement::argument)
244 .map(context::resolveIdentity)
245 .collect(Collectors.toUnmodifiableList())));
247 } else if (TypeDefinitions.LEAFREF.equals(typeName)) {
248 if (!leafTypes.containsKey(stmt)) {
249 leafTypes.put(stmt, TypeReference.leafRef(context.resolveLeafref(
250 type.findFirstEffectiveSubstatementArgument(PathEffectiveStatement.class)
253 } else if (TypeDefinitions.UNION.equals(typeName)) {
254 resolveUnionDependencies(context, type);
255 } else if (!isBuiltinName(typeName) && !baseTypes.containsKey(typeName)) {
256 baseTypes.put(typeName, context.resolveTypedef(typeName));
263 private static final Logger LOG = LoggerFactory.getLogger(AbstractTypeObjectGenerator.class);
264 static final ImmutableMap<QName, Type> SIMPLE_TYPES = ImmutableMap.<QName, Type>builder()
265 .put(TypeDefinitions.BINARY, BaseYangTypes.BINARY_TYPE)
266 .put(TypeDefinitions.BOOLEAN, BaseYangTypes.BOOLEAN_TYPE)
267 .put(TypeDefinitions.DECIMAL64, BaseYangTypes.DECIMAL64_TYPE)
268 .put(TypeDefinitions.EMPTY, BaseYangTypes.EMPTY_TYPE)
269 .put(TypeDefinitions.INSTANCE_IDENTIFIER, BaseYangTypes.INSTANCE_IDENTIFIER)
270 .put(TypeDefinitions.INT8, BaseYangTypes.INT8_TYPE)
271 .put(TypeDefinitions.INT16, BaseYangTypes.INT16_TYPE)
272 .put(TypeDefinitions.INT32, BaseYangTypes.INT32_TYPE)
273 .put(TypeDefinitions.INT64, BaseYangTypes.INT64_TYPE)
274 .put(TypeDefinitions.STRING, BaseYangTypes.STRING_TYPE)
275 .put(TypeDefinitions.UINT8, BaseYangTypes.UINT8_TYPE)
276 .put(TypeDefinitions.UINT16, BaseYangTypes.UINT16_TYPE)
277 .put(TypeDefinitions.UINT32, BaseYangTypes.UINT32_TYPE)
278 .put(TypeDefinitions.UINT64, BaseYangTypes.UINT64_TYPE)
281 private final TypeEffectiveStatement<?> type;
284 * The generator corresponding to our YANG base type. It produces the superclass of our encapsulated type. If it is
285 * {@code null}, this generator is the root of the hierarchy.
287 private TypedefGenerator baseGen;
288 private TypeReference refType;
289 private List<GeneratedType> auxiliaryGeneratedTypes = List.of();
290 private UnionDependencies unionDependencies;
291 private List<AbstractTypeObjectGenerator<?>> inferred = List.of();
293 AbstractTypeObjectGenerator(final T statement, final AbstractCompositeGenerator<?> parent) {
294 super(statement, parent);
295 type = statement().findFirstEffectiveSubstatement(TypeEffectiveStatement.class).orElseThrow();
299 public final List<GeneratedType> auxiliaryGeneratedTypes() {
300 return auxiliaryGeneratedTypes;
304 final void linkDependencies(final GeneratorContext context) {
305 verify(inferred != null, "Duplicate linking of %s", this);
307 final QName typeName = type.argument();
308 if (isBuiltinName(typeName)) {
309 verify(inferred.isEmpty(), "Unexpected non-empty downstreams in %s", this);
314 final AbstractExplicitGenerator<?> prev = previous();
316 verify(prev instanceof AbstractTypeObjectGenerator, "Unexpected previous %s", prev);
317 ((AbstractTypeObjectGenerator<?>) prev).linkInferred(this);
319 linkBaseGen(context.resolveTypedef(typeName));
323 private void linkInferred(final AbstractTypeObjectGenerator<?> downstream) {
324 if (inferred == null) {
325 downstream.linkBaseGen(verifyNotNull(baseGen, "Mismatch on linking between %s and %s", this, downstream));
329 if (inferred.isEmpty()) {
330 inferred = new ArrayList<>(2);
332 inferred.add(downstream);
335 private void linkBaseGen(final TypedefGenerator upstreamBaseGen) {
336 verify(baseGen == null, "Attempted to replace base %s with %s in %s", baseGen, upstreamBaseGen, this);
337 final List<AbstractTypeObjectGenerator<?>> downstreams = verifyNotNull(inferred,
338 "Duplicated linking of %s", this);
339 baseGen = verifyNotNull(upstreamBaseGen);
340 baseGen.addDerivedGenerator(this);
343 for (AbstractTypeObjectGenerator<?> downstream : downstreams) {
344 downstream.linkBaseGen(upstreamBaseGen);
348 void bindTypeDefinition(final GeneratorContext context) {
349 if (baseGen != null) {
350 // We have registered with baseGen, it will push the type to us
354 final QName arg = type.argument();
355 if (TypeDefinitions.IDENTITYREF.equals(arg)) {
356 refType = TypeReference.identityRef(type.streamEffectiveSubstatements(BaseEffectiveStatement.class)
357 .map(BaseEffectiveStatement::argument)
358 .map(context::resolveIdentity)
359 .collect(Collectors.toUnmodifiableList()));
360 } else if (TypeDefinitions.LEAFREF.equals(arg)) {
361 refType = TypeReference.leafRef(context.resolveLeafref(
362 type.findFirstEffectiveSubstatementArgument(PathEffectiveStatement.class).orElseThrow()));
363 } else if (TypeDefinitions.UNION.equals(arg)) {
364 unionDependencies = new UnionDependencies(type, context);
365 LOG.trace("Resolved union {} to dependencies {}", type, unionDependencies);
368 LOG.trace("Resolved base {} to generator {}", type, refType);
369 bindDerivedGenerators(refType);
372 final void bindTypeDefinition(final @Nullable TypeReference reference) {
374 LOG.trace("Resolved derived {} to generator {}", type, refType);
377 private static boolean isBuiltinName(final QName typeName) {
378 return YangConstants.RFC6020_YANG_MODULE.equals(typeName.getModule());
381 abstract void bindDerivedGenerators(@Nullable TypeReference reference);
384 final ClassPlacement classPlacement() {
385 if (refType != null) {
386 // Reference types never create a new type
387 return ClassPlacement.NONE;
389 if (isDerivedEnumeration()) {
390 // Types derived from an enumeration never create a new type, as that would have to be a subclass of an enum
391 // and since enums are final, that can never happen.
392 return ClassPlacement.NONE;
394 return classPlacementImpl();
397 @NonNull ClassPlacement classPlacementImpl() {
398 // TODO: make this a lot more accurate by comparing the effective delta between the base type and the effective
399 // restricted type. We should not be generating a type for constructs like:
402 // type uint8 { range 0..255; }
408 // type uint8 { range 0..100; }
412 // type foo { range 0..100; }
415 // Which is relatively easy to do for integral types, but is way more problematic for 'pattern'
416 // restrictions. Nevertheless we can define the mapping in a way which can be implemented with relative
418 return baseGen != null || SIMPLE_TYPES.containsKey(type.argument()) || isAddedByUses() || isAugmenting()
419 ? ClassPlacement.NONE : ClassPlacement.MEMBER;
423 final GeneratedType getGeneratedType(final TypeBuilderFactory builderFactory) {
424 // For derived enumerations defer to base type
425 return isDerivedEnumeration() ? baseGen.getGeneratedType(builderFactory)
426 : super.getGeneratedType(builderFactory);
429 final boolean isEnumeration() {
430 return baseGen != null ? baseGen.isEnumeration() : TypeDefinitions.ENUMERATION.equals(type.argument());
433 final boolean isDerivedEnumeration() {
434 return baseGen != null && baseGen.isEnumeration();
438 Type methodReturnType(final TypeBuilderFactory builderFactory) {
439 return methodReturnElementType(builderFactory);
442 final @NonNull Type methodReturnElementType(final @NonNull TypeBuilderFactory builderFactory) {
443 final GeneratedType generatedType = tryGeneratedType(builderFactory);
444 if (generatedType != null) {
445 // We have generated a type here, so return it. This covers 'bits', 'enumeration' and 'union'.
446 return generatedType;
449 if (refType != null) {
450 // This is a reference type of some kind. Defer to its judgement as to what the return type is.
451 return refType.methodReturnType(builderFactory);
454 final AbstractExplicitGenerator<?> prev = previous();
456 // We have been added through augment/uses, defer to the original definition
457 return prev.methodReturnType(builderFactory);
461 if (baseGen == null) {
462 final QName qname = type.argument();
463 baseType = verifyNotNull(SIMPLE_TYPES.get(qname), "Cannot resolve type %s in %s", qname, this);
465 // We are derived from a base generator. Defer to its type for return.
466 baseType = baseGen.getGeneratedType(builderFactory);
469 return restrictType(baseType, computeRestrictions(), builderFactory);
472 private static @NonNull Type restrictType(final @NonNull Type baseType, final Restrictions restrictions,
473 final TypeBuilderFactory builderFactory) {
474 if (restrictions == null || restrictions.isEmpty()) {
475 // No additional restrictions, return base type
479 if (!(baseType instanceof GeneratedTransferObject)) {
480 // This is a simple Java type, just wrap it with new restrictions
481 return Types.restrictedType(baseType, restrictions);
484 // Base type is a GTO, we need to re-adjust it with new restrictions
485 final GeneratedTransferObject gto = (GeneratedTransferObject) baseType;
486 final GeneratedTOBuilder builder = builderFactory.newGeneratedTOBuilder(gto.getIdentifier());
487 final GeneratedTransferObject parent = gto.getSuperType();
488 if (parent != null) {
489 builder.setExtendsType(parent);
491 builder.setRestrictions(restrictions);
492 for (GeneratedProperty gp : gto.getProperties()) {
493 builder.addProperty(gp.getName())
494 .setValue(gp.getValue())
495 .setReadOnly(gp.isReadOnly())
496 .setAccessModifier(gp.getAccessModifier())
497 .setReturnType(gp.getReturnType())
498 .setFinal(gp.isFinal())
499 .setStatic(gp.isStatic());
501 return builder.build();
505 final void addAsGetterMethodOverride(final GeneratedTypeBuilderBase<?> builder,
506 final TypeBuilderFactory builderFactory) {
507 if (!(refType instanceof ResolvedLeafref)) {
508 // We are not dealing with a leafref or have nothing to add
512 final AbstractTypeObjectGenerator<?> prev =
513 (AbstractTypeObjectGenerator<?>) verifyNotNull(previous(), "Missing previous link in %s", this);
514 if (prev.refType instanceof ResolvedLeafref) {
515 // We should be already inheriting the correct type
519 // Note: this may we wrapped for leaf-list, hence we need to deal with that
520 final Type myType = methodReturnType(builderFactory);
521 LOG.trace("Override of {} to {}", this, myType);
522 final MethodSignatureBuilder getter = constructGetter(builder, myType);
523 getter.addAnnotation(OVERRIDE_ANNOTATION);
524 annotateDeprecatedIfNecessary(getter);
527 final @Nullable Restrictions computeRestrictions() {
528 final List<ValueRange> length = type.findFirstEffectiveSubstatementArgument(LengthEffectiveStatement.class)
530 final List<ValueRange> range = type.findFirstEffectiveSubstatementArgument(RangeEffectiveStatement.class)
532 final List<PatternExpression> patterns = type.streamEffectiveSubstatements(PatternEffectiveStatement.class)
533 .map(PatternEffectiveStatement::argument)
534 .collect(Collectors.toUnmodifiableList());
536 if (length.isEmpty() && range.isEmpty() && patterns.isEmpty()) {
540 return BindingGeneratorUtil.getRestrictions(extractTypeDefinition());
544 final GeneratedType createTypeImpl(final TypeBuilderFactory builderFactory) {
545 if (baseGen != null) {
546 final GeneratedType baseType = baseGen.getGeneratedType(builderFactory);
547 verify(baseType instanceof GeneratedTransferObject, "Unexpected base type %s", baseType);
548 return createDerivedType(builderFactory, (GeneratedTransferObject) baseType);
551 // FIXME: why do we need this boolean?
552 final boolean isTypedef = this instanceof TypedefGenerator;
553 final QName arg = type.argument();
554 if (TypeDefinitions.BITS.equals(arg)) {
555 return createBits(builderFactory, typeName(), currentModule(), extractTypeDefinition(), isTypedef);
556 } else if (TypeDefinitions.ENUMERATION.equals(arg)) {
557 return createEnumeration(builderFactory, typeName(), currentModule(),
558 (EnumTypeDefinition) extractTypeDefinition());
559 } else if (TypeDefinitions.UNION.equals(arg)) {
560 final List<GeneratedType> tmp = new ArrayList<>(1);
561 final GeneratedTransferObject ret = createUnion(tmp, builderFactory, statement(), unionDependencies,
562 typeName(), currentModule(), type, isTypedef, extractTypeDefinition());
563 auxiliaryGeneratedTypes = List.copyOf(tmp);
566 return createSimple(builderFactory, typeName(), currentModule(),
567 verifyNotNull(SIMPLE_TYPES.get(arg), "Unhandled type %s", arg), extractTypeDefinition());
571 private static @NonNull GeneratedTransferObject createBits(final TypeBuilderFactory builderFactory,
572 final JavaTypeName typeName, final ModuleGenerator module, final TypeDefinition<?> typedef,
573 final boolean isTypedef) {
574 final GeneratedTOBuilder builder = builderFactory.newGeneratedTOBuilder(typeName);
575 builder.setTypedef(isTypedef);
576 builder.addImplementsType(BindingTypes.TYPE_OBJECT);
577 builder.setBaseType(typedef);
579 for (Bit bit : ((BitsTypeDefinition) typedef).getBits()) {
580 final String name = bit.getName();
581 GeneratedPropertyBuilder genPropertyBuilder = builder.addProperty(BindingMapping.getPropertyName(name));
582 genPropertyBuilder.setReadOnly(true);
583 genPropertyBuilder.setReturnType(BaseYangTypes.BOOLEAN_TYPE);
585 builder.addEqualsIdentity(genPropertyBuilder);
586 builder.addHashIdentity(genPropertyBuilder);
587 builder.addToStringProperty(genPropertyBuilder);
590 // builder.setSchemaPath(typedef.getPath());
591 builder.setModuleName(module.statement().argument().getLocalName());
592 addCodegenInformation(typedef, builder);
593 annotateDeprecatedIfNecessary(typedef, builder);
594 makeSerializable(builder);
595 return builder.build();
598 private static @NonNull Enumeration createEnumeration(final TypeBuilderFactory builderFactory,
599 final JavaTypeName typeName, final ModuleGenerator module, final EnumTypeDefinition typedef) {
600 // TODO units for typedef enum
601 final AbstractEnumerationBuilder builder = builderFactory.newEnumerationBuilder(typeName);
603 typedef.getDescription().map(BindingGeneratorUtil::encodeAngleBrackets)
604 .ifPresent(builder::setDescription);
605 typedef.getReference().ifPresent(builder::setReference);
607 builder.setModuleName(module.statement().argument().getLocalName());
608 builder.updateEnumPairsFromEnumTypeDef(typedef);
609 return builder.toInstance();
612 private static @NonNull GeneratedType createSimple(final TypeBuilderFactory builderFactory,
613 final JavaTypeName typeName, final ModuleGenerator module, final Type javaType,
614 final TypeDefinition<?> typedef) {
615 final String moduleName = module.statement().argument().getLocalName();
616 final GeneratedTOBuilder builder = builderFactory.newGeneratedTOBuilder(typeName);
617 builder.setTypedef(true);
618 builder.addImplementsType(BindingTypes.scalarTypeObject(javaType));
620 final GeneratedPropertyBuilder genPropBuilder = builder.addProperty(TypeConstants.VALUE_PROP);
621 genPropBuilder.setReturnType(javaType);
622 builder.addEqualsIdentity(genPropBuilder);
623 builder.addHashIdentity(genPropBuilder);
624 builder.addToStringProperty(genPropBuilder);
626 builder.setRestrictions(BindingGeneratorUtil.getRestrictions(typedef));
628 // builder.setSchemaPath(typedef.getPath());
629 builder.setModuleName(moduleName);
630 addCodegenInformation(typedef, builder);
632 annotateDeprecatedIfNecessary(typedef, builder);
634 if (javaType instanceof ConcreteType
635 // FIXME: This looks very suspicious: we should by checking for Types.STRING
636 && "String".equals(javaType.getName()) && typedef.getBaseType() != null) {
637 addStringRegExAsConstant(builder, resolveRegExpressions(typedef));
639 addUnits(builder, typedef);
641 makeSerializable(builder);
642 return builder.build();
645 private static @NonNull GeneratedTransferObject createUnion(final List<GeneratedType> auxiliaryGeneratedTypes,
646 final TypeBuilderFactory builderFactory, final EffectiveStatement<?, ?> definingStatement,
647 final UnionDependencies dependencies, final JavaTypeName typeName, final ModuleGenerator module,
648 final TypeEffectiveStatement<?> type, final boolean isTypedef, final TypeDefinition<?> typedef) {
649 final GeneratedUnionBuilder builder = builderFactory.newGeneratedUnionBuilder(typeName);
650 builder.addImplementsType(BindingTypes.TYPE_OBJECT);
651 builder.setIsUnion(true);
653 // builder.setSchemaPath(typedef.getPath());
654 builder.setModuleName(module.statement().argument().getLocalName());
655 addCodegenInformation(definingStatement, builder);
657 annotateDeprecatedIfNecessary(definingStatement, builder);
659 // Pattern string is the key, XSD regex is the value. The reason for this choice is that the pattern carries
660 // also negation information and hence guarantees uniqueness.
661 final Map<String, String> expressions = new HashMap<>();
663 // Linear list of properties generated from subtypes. We need this information for runtime types, as it allows
664 // direct mapping of type to corresponding property -- without having to resort to re-resolving the leafrefs
666 final List<String> typeProperties = new ArrayList<>();
668 for (EffectiveStatement<?, ?> stmt : type.effectiveSubstatements()) {
669 if (stmt instanceof TypeEffectiveStatement) {
670 final TypeEffectiveStatement<?> subType = (TypeEffectiveStatement<?>) stmt;
671 final QName subName = subType.argument();
672 final String localName = subName.getLocalName();
674 String propSource = localName;
675 final Type generatedType;
676 if (TypeDefinitions.UNION.equals(subName)) {
677 final JavaTypeName subUnionName = typeName.createEnclosed(
678 provideAvailableNameForGenTOBuilder(typeName.simpleName()));
679 final GeneratedTransferObject subUnion = createUnion(auxiliaryGeneratedTypes, builderFactory,
680 definingStatement, dependencies, subUnionName, module, subType, isTypedef,
681 subType.getTypeDefinition());
682 builder.addEnclosingTransferObject(subUnion);
683 propSource = subUnionName.simpleName();
684 generatedType = subUnion;
685 } else if (TypeDefinitions.ENUMERATION.equals(subName)) {
686 final Enumeration subEnumeration = createEnumeration(builderFactory,
687 typeName.createEnclosed(BindingMapping.getClassName(localName), "$"), module,
688 (EnumTypeDefinition) subType.getTypeDefinition());
689 builder.addEnumeration(subEnumeration);
690 generatedType = subEnumeration;
691 } else if (TypeDefinitions.BITS.equals(subName)) {
692 final GeneratedTransferObject subBits = createBits(builderFactory,
693 typeName.createEnclosed(BindingMapping.getClassName(localName), "$"), module,
694 subType.getTypeDefinition(), isTypedef);
695 builder.addEnclosingTransferObject(subBits);
696 generatedType = subBits;
697 } else if (TypeDefinitions.IDENTITYREF.equals(subName)) {
698 generatedType = verifyNotNull(dependencies.identityTypes.get(stmt),
699 "Cannot resolve identityref %s in %s", stmt, definingStatement)
700 .methodReturnType(builderFactory);
701 } else if (TypeDefinitions.LEAFREF.equals(subName)) {
702 generatedType = verifyNotNull(dependencies.leafTypes.get(stmt),
703 "Cannot resolve leafref %s in %s", stmt, definingStatement)
704 .methodReturnType(builderFactory);
706 Type baseType = SIMPLE_TYPES.get(subName);
707 if (baseType == null) {
708 // This has to be a reference to a typedef, let's lookup it up and pick up its type
709 final AbstractTypeObjectGenerator<?> baseGen = verifyNotNull(
710 dependencies.baseTypes.get(subName), "Cannot resolve base type %s in %s", subName,
712 baseType = baseGen.methodReturnType(builderFactory);
714 // FIXME: This is legacy behaviour for leafrefs:
715 if (baseGen.refType instanceof TypeReference.Leafref) {
716 // if there already is a compatible property, do not generate a new one
717 final Type search = baseType;
719 final String matching = builder.getProperties().stream()
720 .filter(prop -> search == ((GeneratedPropertyBuilderImpl) prop).getReturnType())
722 .map(GeneratedPropertyBuilder::getName)
724 if (matching != null) {
725 typeProperties.add(matching);
729 // ... otherwise generate this weird property name
730 propSource = BindingMapping.getUnionLeafrefMemberName(builder.getName(),
735 expressions.putAll(resolveRegExpressions(subType.getTypeDefinition()));
737 generatedType = restrictType(baseType,
738 BindingGeneratorUtil.getRestrictions(type.getTypeDefinition()), builderFactory);
741 final String propName = BindingMapping.getPropertyName(propSource);
742 typeProperties.add(propName);
744 if (builder.containsProperty(propName)) {
746 * FIXME: this is not okay, as we are ignoring multiple base types. For example in the case of:
757 * We are ending up losing the information about 8..10 being an alternative. This is also the case
758 * for leafrefs -- we are performing property compression as well (see above). While it is alluring
759 * to merge these into 'length 1..5|8..10', that may not be generally feasible.
761 * We should resort to a counter of conflicting names, i.e. the second string would be mapped to
762 * 'string1' or similar.
767 final GeneratedPropertyBuilder propBuilder = builder
768 .addProperty(propName)
769 .setReturnType(generatedType);
771 builder.addEqualsIdentity(propBuilder);
772 builder.addHashIdentity(propBuilder);
773 builder.addToStringProperty(propBuilder);
777 // Record property names if needed
778 builder.setTypePropertyNames(typeProperties);
780 addStringRegExAsConstant(builder, expressions);
781 addUnits(builder, typedef);
783 makeSerializable(builder);
784 final GeneratedTransferObject ret = builder.build();
786 // Define a corresponding union builder. Typedefs are always anchored at a Java package root,
787 // so we are placing the builder alongside the union.
788 final GeneratedTOBuilder unionBuilder = builderFactory.newGeneratedTOBuilder(unionBuilderName(typeName));
789 unionBuilder.setIsUnionBuilder(true);
790 unionBuilder.addMethod("getDefaultInstance")
791 .setAccessModifier(AccessModifier.PUBLIC)
794 .addParameter(Types.STRING, "defaultValue");
795 auxiliaryGeneratedTypes.add(unionBuilder.build());
800 // FIXME: this can be a source of conflicts as we are not guarding against nesting
801 private static @NonNull JavaTypeName unionBuilderName(final JavaTypeName unionName) {
802 final StringBuilder sb = new StringBuilder();
803 for (String part : unionName.localNameComponents()) {
806 return JavaTypeName.create(unionName.packageName(), sb.append("Builder").toString());
809 // FIXME: we should not rely on TypeDefinition
810 abstract @NonNull TypeDefinition<?> extractTypeDefinition();
812 abstract @NonNull GeneratedTransferObject createDerivedType(@NonNull TypeBuilderFactory builderFactory,
813 @NonNull GeneratedTransferObject baseType);
816 * Adds to the {@code genTOBuilder} the constant which contains regular expressions from the {@code expressions}.
818 * @param genTOBuilder generated TO builder to which are {@code regular expressions} added
819 * @param expressions list of string which represent regular expressions
821 static void addStringRegExAsConstant(final GeneratedTOBuilder genTOBuilder, final Map<String, String> expressions) {
822 if (!expressions.isEmpty()) {
823 genTOBuilder.addConstant(Types.listTypeFor(BaseYangTypes.STRING_TYPE), TypeConstants.PATTERN_CONSTANT_NAME,
824 ImmutableMap.copyOf(expressions));
829 * Converts the pattern constraints from {@code typedef} to the list of the strings which represents these
832 * @param typedef extended type in which are the pattern constraints sought
833 * @return list of strings which represents the constraint patterns
834 * @throws IllegalArgumentException if <code>typedef</code> equals null
836 static Map<String, String> resolveRegExpressions(final TypeDefinition<?> typedef) {
837 return typedef instanceof StringTypeDefinition
838 // TODO: run diff against base ?
839 ? resolveRegExpressions(((StringTypeDefinition) typedef).getPatternConstraints())
844 * Converts the pattern constraints to the list of the strings which represents these constraints.
846 * @param patternConstraints list of pattern constraints
847 * @return list of strings which represents the constraint patterns
849 private static Map<String, String> resolveRegExpressions(final List<PatternConstraint> patternConstraints) {
850 if (patternConstraints.isEmpty()) {
851 return ImmutableMap.of();
854 final Map<String, String> regExps = Maps.newHashMapWithExpectedSize(patternConstraints.size());
855 for (PatternConstraint patternConstraint : patternConstraints) {
856 String regEx = patternConstraint.getJavaPatternString();
858 // The pattern can be inverted
859 final Optional<ModifierKind> optModifier = patternConstraint.getModifier();
860 if (optModifier.isPresent()) {
861 regEx = applyModifier(optModifier.get(), regEx);
864 regExps.put(regEx, patternConstraint.getRegularExpressionString());
871 * Returns string which contains the same value as <code>name</code> but integer suffix is incremented by one. If
872 * <code>name</code> contains no number suffix, a new suffix initialized at 1 is added. A suffix is actually
873 * composed of a '$' marker, which is safe, as no YANG identifier can contain '$', and a unsigned decimal integer.
875 * @param name string with name of augmented node
876 * @return string with the number suffix incremented by one (or 1 is added)
878 private static String provideAvailableNameForGenTOBuilder(final String name) {
879 final int dollar = name.indexOf('$');
884 final int newSuffix = Integer.parseUnsignedInt(name.substring(dollar + 1)) + 1;
885 verify(newSuffix > 0, "Suffix counter overflow");
886 return name.substring(0, dollar + 1) + newSuffix;
889 private static String applyModifier(final ModifierKind modifier, final String pattern) {
892 return RegexPatterns.negatePatternString(pattern);
894 LOG.warn("Ignoring unhandled modifier {}", modifier);