Hide binding.model.api.DefaultType
[mdsal.git] / binding / mdsal-binding-generator-impl / src / main / java / org / opendaylight / mdsal / binding / generator / impl / reactor / Generator.java
1 /*
2  * Copyright (c) 2021 PANTHEON.tech, s.r.o. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.mdsal.binding.generator.impl.reactor;
9
10 import static com.google.common.base.Verify.verifyNotNull;
11 import static java.util.Objects.requireNonNull;
12 import static org.opendaylight.mdsal.binding.model.util.Types.STRING;
13 import static org.opendaylight.mdsal.binding.model.util.Types.classType;
14 import static org.opendaylight.mdsal.binding.model.util.Types.primitiveBooleanType;
15 import static org.opendaylight.mdsal.binding.model.util.Types.primitiveIntType;
16 import static org.opendaylight.mdsal.binding.model.util.Types.wildcardTypeFor;
17
18 import com.google.common.base.MoreObjects;
19 import com.google.common.base.MoreObjects.ToStringHelper;
20 import java.util.Collections;
21 import java.util.Iterator;
22 import java.util.List;
23 import java.util.Optional;
24 import org.eclipse.jdt.annotation.NonNull;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.opendaylight.mdsal.binding.generator.impl.reactor.CollisionDomain.Member;
27 import org.opendaylight.mdsal.binding.model.api.AccessModifier;
28 import org.opendaylight.mdsal.binding.model.api.GeneratedType;
29 import org.opendaylight.mdsal.binding.model.api.JavaTypeName;
30 import org.opendaylight.mdsal.binding.model.api.Type;
31 import org.opendaylight.mdsal.binding.model.api.YangSourceDefinition;
32 import org.opendaylight.mdsal.binding.model.api.type.builder.AnnotableTypeBuilder;
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.GeneratedTypeBuilder;
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.util.BindingGeneratorUtil;
39 import org.opendaylight.mdsal.binding.model.util.BindingTypes;
40 import org.opendaylight.mdsal.binding.model.util.TypeComments;
41 import org.opendaylight.mdsal.binding.model.util.Types;
42 import org.opendaylight.mdsal.binding.model.util.generated.type.builder.GeneratedPropertyBuilderImpl;
43 import org.opendaylight.mdsal.binding.spec.naming.BindingMapping;
44 import org.opendaylight.yangtools.yang.binding.DataContainer;
45 import org.opendaylight.yangtools.yang.model.api.DocumentedNode;
46 import org.opendaylight.yangtools.yang.model.api.DocumentedNode.WithStatus;
47 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
48 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
49 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
50 import org.opendaylight.yangtools.yang.model.ri.type.TypeBuilder;
51 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
52
53 /**
54  * A single node in generator tree. Each node will eventually resolve to a generated Java class. Each node also can have
55  * a number of children, which are generators corresponding to the YANG subtree of this node.
56  */
57 public abstract class Generator implements Iterable<Generator> {
58     private static final JavaTypeName DEPRECATED_ANNOTATION = JavaTypeName.create(Deprecated.class);
59     static final JavaTypeName OVERRIDE_ANNOTATION = JavaTypeName.create(Override.class);
60
61     private final AbstractCompositeGenerator<?> parent;
62
63     private Optional<Member> member;
64     private GeneratorResult result;
65     private JavaTypeName typeName;
66     private String javaPackage;
67
68     Generator() {
69         this.parent = null;
70     }
71
72     Generator(final AbstractCompositeGenerator<?> parent) {
73         this.parent = requireNonNull(parent);
74     }
75
76     public final @NonNull Optional<GeneratedType> generatedType() {
77         return Optional.ofNullable(result.generatedType());
78     }
79
80     public @NonNull List<GeneratedType> auxiliaryGeneratedTypes() {
81         return List.of();
82     }
83
84     @Override
85     public Iterator<Generator> iterator() {
86         return Collections.emptyIterator();
87     }
88
89     /**
90      * Return the {@link AbstractCompositeGenerator} inside which this generator is defined. It is illegal to call this
91      * method on a {@link ModuleGenerator}.
92      *
93      * @return Parent generator
94      */
95     final @NonNull AbstractCompositeGenerator<?> getParent() {
96         return verifyNotNull(parent, "No parent for %s", this);
97     }
98
99     boolean isEmpty() {
100         return true;
101     }
102
103     @Nullable Generator findGenerator(final EffectiveStatement<?, ?> stmt) {
104         return null;
105     }
106
107     final @NonNull Generator getGenerator(final EffectiveStatement<?, ?> stmt) {
108         return verifyNotNull(findGenerator(stmt), "Cannot match statement %s in %s", stmt, this);
109     }
110
111     /**
112      * Return the namespace of this statement.
113      *
114      * @return Corresponding namespace
115      * @throws UnsupportedOperationException if this node does not have a corresponding namespace
116      */
117     @NonNull StatementNamespace namespace() {
118         return StatementNamespace.DEFAULT;
119     }
120
121     @NonNull ModuleGenerator currentModule() {
122         return getParent().currentModule();
123     }
124
125     /**
126      * Push this statement into a {@link SchemaInferenceStack} so that the stack contains a resolvable {@code data tree}
127      * hierarchy.
128      *
129      * @param inferenceStack Target inference stack
130      */
131     abstract void pushToInference(@NonNull SchemaInferenceStack inferenceStack);
132
133     abstract @NonNull ClassPlacement classPlacement();
134
135     final @NonNull Member getMember() {
136         return verifyNotNull(ensureMember(), "No member for %s", this);
137     }
138
139     final Member ensureMember() {
140         if (member == null) {
141             final ClassPlacement placement = classPlacement();
142             switch (placement) {
143                 case NONE:
144                     member = Optional.empty();
145                     break;
146                 case MEMBER:
147                 case PHANTOM:
148                 case TOP_LEVEL:
149                     member = Optional.of(createMember(parentDomain()));
150                     break;
151                 default:
152                     throw new IllegalStateException("Unhandled placement " + placement);
153             }
154         }
155         return member.orElse(null);
156     }
157
158     @NonNull CollisionDomain parentDomain() {
159         return getParent().domain();
160     }
161
162     abstract @NonNull Member createMember(@NonNull CollisionDomain domain);
163
164     /**
165      * Create the type associated with this builder. This method idempotent.
166      *
167      * @param builderFactory Factory for {@link TypeBuilder}s
168      * @throws NullPointerException if {@code builderFactory} is {@code null}
169      */
170     final void ensureType(final TypeBuilderFactory builderFactory) {
171         if (result != null) {
172             return;
173         }
174
175         final ClassPlacement placement = classPlacement();
176         switch (placement) {
177             case NONE:
178             case PHANTOM:
179                 result = GeneratorResult.empty();
180                 break;
181             case MEMBER:
182                 result = GeneratorResult.member(createTypeImpl(requireNonNull(builderFactory)));
183                 break;
184             case TOP_LEVEL:
185                 result = GeneratorResult.toplevel(createTypeImpl(requireNonNull(builderFactory)));
186                 break;
187             default:
188                 throw new IllegalStateException("Unhandled placement " + placement);
189         }
190
191         for (Generator child : this) {
192             child.ensureType(builderFactory);
193         }
194     }
195
196     @NonNull GeneratedType getGeneratedType(final TypeBuilderFactory builderFactory) {
197         return verifyNotNull(tryGeneratedType(builderFactory), "No type generated for %s", this);
198     }
199
200     final @Nullable GeneratedType tryGeneratedType(final TypeBuilderFactory builderFactory) {
201         ensureType(builderFactory);
202         return result.generatedType();
203     }
204
205     final @Nullable GeneratedType enclosedType(final TypeBuilderFactory builderFactory) {
206         ensureType(builderFactory);
207         return result.enclosedType();
208     }
209
210     /**
211      * Create the type associated with this builder, as per {@link #ensureType(TypeBuilderFactory)} contract. This
212      * method is guaranteed to be called at most once.
213      *
214      * @param builderFactory Factory for {@link TypeBuilder}s
215      */
216     abstract @NonNull GeneratedType createTypeImpl(@NonNull TypeBuilderFactory builderFactory);
217
218     final @NonNull String assignedName() {
219         return getMember().currentClass();
220     }
221
222     final @NonNull String javaPackage() {
223         String local = javaPackage;
224         if (local == null) {
225             javaPackage = local = createJavaPackage();
226         }
227         return local;
228     }
229
230     @NonNull String createJavaPackage() {
231         final String parentPackage = getPackageParent().javaPackage();
232         final String myPackage = getMember().currentPackage();
233         return BindingMapping.normalizePackageName(parentPackage + '.' + myPackage);
234     }
235
236     final @NonNull JavaTypeName typeName() {
237         JavaTypeName local = typeName;
238         if (local == null) {
239             typeName = local = createTypeName();
240         }
241         return local;
242     }
243
244     @NonNull JavaTypeName createTypeName() {
245         return JavaTypeName.create(getPackageParent().javaPackage(), assignedName());
246     }
247
248     @NonNull AbstractCompositeGenerator<?> getPackageParent() {
249         return getParent();
250     }
251
252     @Override
253     public final String toString() {
254         return addToStringAttributes(MoreObjects.toStringHelper(this).omitNullValues()).toString();
255     }
256
257     ToStringHelper addToStringAttributes(final ToStringHelper helper) {
258         return helper;
259     }
260
261     final void addImplementsChildOf(final GeneratedTypeBuilder builder) {
262         AbstractCompositeGenerator<?> ancestor = getParent();
263         while (true) {
264             // choice/case hierarchy does not factor into 'ChildOf' hierarchy, hence we need to skip them
265             if (ancestor instanceof CaseGenerator || ancestor instanceof ChoiceGenerator) {
266                 ancestor = ancestor.getParent();
267                 continue;
268             }
269
270             // if we into a choice we need to follow the hierararchy of that choice
271             if (ancestor instanceof AbstractAugmentGenerator) {
272                 final AbstractCompositeGenerator<?> target = ((AbstractAugmentGenerator) ancestor).targetGenerator();
273                 if (target instanceof ChoiceGenerator) {
274                     ancestor = target;
275                     continue;
276                 }
277             }
278
279             break;
280         }
281
282         builder.addImplementsType(BindingTypes.childOf(Type.of(ancestor.typeName())));
283     }
284
285     /**
286      * Add common methods implemented in a generated type. This includes {@link DataContainer#implementedInterface()} as
287      * well has {@code bindingHashCode()}, {@code bindingEquals()} and {@code bindingToString()}.
288      *
289      * @param builder Target builder
290      */
291     static final void addConcreteInterfaceMethods(final GeneratedTypeBuilder builder) {
292         defaultImplementedInterace(builder);
293
294         builder.addMethod(BindingMapping.BINDING_HASHCODE_NAME)
295             .setAccessModifier(AccessModifier.PUBLIC)
296             .setStatic(true)
297             .setReturnType(primitiveIntType());
298         builder.addMethod(BindingMapping.BINDING_EQUALS_NAME)
299             .setAccessModifier(AccessModifier.PUBLIC)
300             .setStatic(true)
301             .setReturnType(primitiveBooleanType());
302         builder.addMethod(BindingMapping.BINDING_TO_STRING_NAME)
303             .setAccessModifier(AccessModifier.PUBLIC)
304             .setStatic(true)
305             .setReturnType(STRING);
306     }
307
308     static final void annotateDeprecatedIfNecessary(final EffectiveStatement<?, ?> stmt,
309             final AnnotableTypeBuilder builder) {
310         if (stmt instanceof WithStatus) {
311             annotateDeprecatedIfNecessary((WithStatus) stmt, builder);
312         }
313     }
314
315     static final void annotateDeprecatedIfNecessary(final WithStatus node, final AnnotableTypeBuilder builder) {
316         switch (node.getStatus()) {
317             case DEPRECATED:
318                 // FIXME: we really want to use a pre-made annotation
319                 builder.addAnnotation(DEPRECATED_ANNOTATION);
320                 break;
321             case OBSOLETE:
322                 builder.addAnnotation(DEPRECATED_ANNOTATION).addParameter("forRemoval", "true");
323                 break;
324             case CURRENT:
325                 // No-op
326                 break;
327             default:
328                 throw new IllegalStateException("Unhandled status in " + node);
329         }
330     }
331
332     static final void addCodegenInformation(final EffectiveStatement<?, ?> stmt,
333             final GeneratedTypeBuilderBase<?> builder) {
334         if (stmt instanceof DocumentedNode) {
335             addCodegenInformation((DocumentedNode) stmt, builder);
336         }
337     }
338
339     static final void addCodegenInformation(final DocumentedNode node, final GeneratedTypeBuilderBase<?> builder) {
340         node.getDescription().map(BindingGeneratorUtil::encodeAngleBrackets).ifPresent(builder::setDescription);
341         node.getReference().ifPresent(builder::setReference);
342     }
343
344     static final void addCodegenInformation(final ModuleGenerator module, final EffectiveStatement<?, ?> stmt,
345             final GeneratedTypeBuilderBase<?> builder) {
346         if (stmt instanceof DocumentedNode) {
347             final DocumentedNode node = (DocumentedNode) stmt;
348             TypeComments.description(node).ifPresent(builder::addComment);
349             node.getDescription().ifPresent(builder::setDescription);
350             node.getReference().ifPresent(builder::setReference);
351         }
352         if (stmt instanceof SchemaNode) {
353             YangSourceDefinition.of(module.statement(), (SchemaNode) stmt).ifPresent(builder::setYangSourceDefinition);
354         }
355     }
356
357     static final void addUnits(final GeneratedTOBuilder builder, final TypeDefinition<?> typedef) {
358         typedef.getUnits().ifPresent(units -> {
359             if (!units.isEmpty()) {
360                 builder.addConstant(Types.STRING, "_UNITS", "\"" + units + "\"");
361                 final GeneratedPropertyBuilder prop = new GeneratedPropertyBuilderImpl("UNITS");
362                 prop.setReturnType(Types.STRING);
363                 builder.addToStringProperty(prop);
364             }
365         });
366     }
367
368     /**
369      * Add {@link java.io.Serializable} to implemented interfaces of this TO. Also compute and add serialVersionUID
370      * property.
371      *
372      * @param builder transfer object which needs to be made serializable
373      */
374     static final void makeSerializable(final GeneratedTOBuilder builder) {
375         builder.addImplementsType(Types.serializableType());
376         addSerialVersionUID(builder);
377     }
378
379     static final void addSerialVersionUID(final GeneratedTOBuilder gto) {
380         final GeneratedPropertyBuilder prop = new GeneratedPropertyBuilderImpl("serialVersionUID");
381         prop.setValue(Long.toString(BindingGeneratorUtil.computeDefaultSUID(gto)));
382         gto.setSUID(prop);
383     }
384
385     /**
386      * Add a {@link DataContainer#implementedInterface()} declaration with a narrower return type to specified builder.
387      *
388      * @param builder Target builder
389      */
390     static final void narrowImplementedInterface(final GeneratedTypeBuilder builder) {
391         defineImplementedInterfaceMethod(builder, wildcardTypeFor(builder.getIdentifier()));
392     }
393
394     /**
395      * Add a default implementation of {@link DataContainer#implementedInterface()} to specified builder.
396      *
397      * @param builder Target builder
398      */
399     static final void defaultImplementedInterace(final GeneratedTypeBuilder builder) {
400         defineImplementedInterfaceMethod(builder, Type.of(builder)).setDefault(true);
401     }
402
403     static final <T extends EffectiveStatement<?, ?>> AbstractExplicitGenerator<T> getChild(final Generator parent,
404             final Class<T> type) {
405         for (Generator child : parent) {
406             if (child instanceof AbstractExplicitGenerator) {
407                 @SuppressWarnings("unchecked")
408                 final AbstractExplicitGenerator<T> explicit = (AbstractExplicitGenerator<T>)child;
409                 if (type.isInstance(explicit.statement())) {
410                     return explicit;
411                 }
412             }
413         }
414         throw new IllegalStateException("Cannot find " + type + " in " + parent);
415     }
416
417     private static MethodSignatureBuilder defineImplementedInterfaceMethod(final GeneratedTypeBuilder typeBuilder,
418             final Type classType) {
419         final MethodSignatureBuilder ret = typeBuilder
420                 .addMethod(BindingMapping.DATA_CONTAINER_IMPLEMENTED_INTERFACE_NAME)
421                 .setAccessModifier(AccessModifier.PUBLIC)
422                 .setReturnType(classType(classType));
423         ret.addAnnotation(OVERRIDE_ANNOTATION);
424         return ret;
425     }
426 }