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