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