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