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