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