Fixup BuilderTemplate decomposition
[mdsal.git] / binding / mdsal-binding-java-api-generator / src / main / java / org / opendaylight / mdsal / binding / java / api / generator / BuilderGenerator.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. 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.java.api.generator;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.AUGMENTABLE_AUGMENTATION_NAME;
12
13 import com.google.common.annotations.VisibleForTesting;
14 import com.google.common.collect.ImmutableSortedSet;
15 import java.lang.reflect.Method;
16 import java.util.Collection;
17 import java.util.Collections;
18 import java.util.Comparator;
19 import java.util.LinkedHashSet;
20 import java.util.List;
21 import java.util.Set;
22 import org.eclipse.xtext.xbase.lib.StringExtensions;
23 import org.opendaylight.mdsal.binding.model.api.CodeGenerator;
24 import org.opendaylight.mdsal.binding.model.api.GeneratedProperty;
25 import org.opendaylight.mdsal.binding.model.api.GeneratedTransferObject;
26 import org.opendaylight.mdsal.binding.model.api.GeneratedType;
27 import org.opendaylight.mdsal.binding.model.api.JavaTypeName;
28 import org.opendaylight.mdsal.binding.model.api.MethodSignature;
29 import org.opendaylight.mdsal.binding.model.api.ParameterizedType;
30 import org.opendaylight.mdsal.binding.model.api.Type;
31 import org.opendaylight.mdsal.binding.model.api.type.builder.GeneratedTOBuilder;
32 import org.opendaylight.mdsal.binding.model.api.type.builder.GeneratedTypeBuilder;
33 import org.opendaylight.mdsal.binding.model.util.ReferencedTypeImpl;
34 import org.opendaylight.mdsal.binding.model.util.Types;
35 import org.opendaylight.mdsal.binding.model.util.generated.type.builder.CodegenGeneratedTOBuilder;
36 import org.opendaylight.mdsal.binding.model.util.generated.type.builder.CodegenGeneratedTypeBuilder;
37 import org.opendaylight.mdsal.binding.spec.naming.BindingMapping;
38 import org.opendaylight.yangtools.yang.binding.Augmentable;
39 import org.opendaylight.yangtools.yang.binding.Augmentation;
40
41 /**
42  *
43  * Transformator of the data from the virtual form to JAVA programming language.
44  * The result source code represent java class. For generation of the source
45  * code is used the template written in XTEND language.
46  *
47  */
48 public final class BuilderGenerator implements CodeGenerator {
49     private static final Comparator<MethodSignature> METHOD_COMPARATOR = new AlphabeticallyTypeMemberComparator<>();
50     private static final Type AUGMENTATION_RET_TYPE;
51
52     static {
53         final Method m;
54         try {
55             m = Augmentable.class.getDeclaredMethod(AUGMENTABLE_AUGMENTATION_NAME, Class.class);
56         } catch (NoSuchMethodException e) {
57             throw new ExceptionInInitializerError(e);
58         }
59
60         AUGMENTATION_RET_TYPE = new ReferencedTypeImpl(JavaTypeName.create(m.getReturnType()));
61     }
62
63     /**
64      * Passes via list of implemented types in <code>type</code>.
65      *
66      * @param type
67      *            JAVA <code>Type</code>
68      * @return boolean value which is true if any of implemented types is of the
69      *         type <code>Augmentable</code>.
70      */
71     @Override
72     public boolean isAcceptable(Type type) {
73         if (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject)) {
74             for (Type t : ((GeneratedType) type).getImplements()) {
75                 // "rpc" and "grouping" elements do not implement Augmentable
76                 if (t.getFullyQualifiedName().equals(Augmentable.class.getName())) {
77                     return true;
78                 } else if (t.getFullyQualifiedName().equals(Augmentation.class.getName())) {
79                     return true;
80                 }
81
82             }
83         }
84         return false;
85     }
86
87     /**
88      * Generates JAVA source code for generated type <code>Type</code>. The code
89      * is generated according to the template source code template which is
90      * written in XTEND language.
91      */
92     @Override
93     public String generate(Type type) {
94         if (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject)) {
95             return templateForType((GeneratedType) type).generate();
96         }
97         return "";
98     }
99
100     @Override
101     public String getUnitName(Type type) {
102         return type.getName() + BuilderTemplate.BUILDER;
103     }
104
105     @VisibleForTesting
106     static BuilderTemplate templateForType(GeneratedType type) {
107         final GeneratedType genType = type;
108         final JavaTypeName origName = genType.getIdentifier();
109
110         final Set<MethodSignature> methods = new LinkedHashSet<>();
111         final Type augmentType = createMethods(genType, methods);
112         final Set<MethodSignature> sortedMethods = ImmutableSortedSet.orderedBy(METHOD_COMPARATOR)
113                 .addAll(methods).build();
114
115         final GeneratedTypeBuilder builderTypeBuilder = new CodegenGeneratedTypeBuilder(
116             origName.createSibling(origName.simpleName() + BuilderTemplate.BUILDER));
117
118         final GeneratedTOBuilder implTypeBuilder = builderTypeBuilder.addEnclosingTransferObject(
119             origName.simpleName() + "Impl");
120         implTypeBuilder.addImplementsType(genType);
121
122         return new BuilderTemplate(builderTypeBuilder.build(), genType, propertiesFromMethods(sortedMethods),
123             augmentType, getKey(genType));
124     }
125
126     private static Type getKey(GeneratedType type) {
127         for (MethodSignature m : type.getMethodDefinitions()) {
128             if (BindingMapping.IDENTIFIABLE_KEY_NAME.equals(m.getName())) {
129                 return m.getReturnType();
130             }
131         }
132         return null;
133     }
134
135     /**
136      * Returns set of method signature instances which contains all the methods of the <code>genType</code>
137      * and all the methods of the implemented interfaces.
138      *
139      * @returns set of method signature instances
140      */
141     private static ParameterizedType createMethods(GeneratedType type, Set<MethodSignature> methods) {
142         methods.addAll(type.getMethodDefinitions());
143         return collectImplementedMethods(type, methods, type.getImplements());
144     }
145
146     /**
147      * Adds to the <code>methods</code> set all the methods of the <code>implementedIfcs</code>
148      * and recursively their implemented interfaces.
149      *
150      * @param methods set of method signatures
151      * @param implementedIfcs list of implemented interfaces
152      */
153     private static ParameterizedType collectImplementedMethods(GeneratedType type, Set<MethodSignature> methods,
154             List<Type> implementedIfcs) {
155         if (implementedIfcs == null || implementedIfcs.isEmpty()) {
156             return null;
157         }
158
159         ParameterizedType augmentType = null;
160         for (Type implementedIfc : implementedIfcs) {
161             if (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject)) {
162                 final GeneratedType ifc = (GeneratedType) implementedIfc;
163                 methods.addAll(ifc.getMethodDefinitions());
164
165                 final ParameterizedType t = collectImplementedMethods(type, methods, ifc.getImplements());
166                 if (t != null && augmentType == null) {
167                     augmentType = t;
168                 }
169             } else if (Augmentable.class.getName().equals(implementedIfc.getFullyQualifiedName())) {
170                 augmentType = Types.parameterizedTypeFor(AUGMENTATION_RET_TYPE,
171                     new ReferencedTypeImpl(type.getIdentifier()));
172             }
173         }
174
175         return augmentType;
176     }
177
178     /**
179      * Creates set of generated property instances from getter <code>methods</code>.
180      *
181      * @param set of method signature instances which should be transformed to list of properties
182      * @return set of generated property instances which represents the getter <code>methods</code>
183      */
184     private static Set<GeneratedProperty> propertiesFromMethods(Collection<MethodSignature> methods) {
185         if (methods == null || methods.isEmpty()) {
186             return Collections.emptySet();
187         }
188         final Set<GeneratedProperty> result = new LinkedHashSet<>();
189         for (MethodSignature m : methods) {
190             final GeneratedProperty createdField = propertyFromGetter(m);
191             if (createdField != null) {
192                 result.add(createdField);
193             }
194         }
195         return result;
196     }
197
198     /**
199      * Creates generated property instance from the getter <code>method</code> name and return type.
200      *
201      * @param method method signature from which is the method name and return type obtained
202      * @return generated property instance for the getter <code>method</code>
203      * @throws IllegalArgumentException<ul>
204      *  <li>if the <code>method</code> equals <code>null</code></li>
205      *  <li>if the name of the <code>method</code> equals <code>null</code></li>
206      *  <li>if the name of the <code>method</code> is empty</li>
207      *  <li>if the return type of the <code>method</code> equals <code>null</code></li>
208      * </ul>
209      */
210     private static GeneratedProperty propertyFromGetter(MethodSignature method) {
211         checkArgument(method != null);
212         checkArgument(method.getReturnType() != null);
213         checkArgument(method.getName() != null);
214         checkArgument(!method.getName().isEmpty());
215         final String prefix = Types.BOOLEAN.equals(method.getReturnType()) ? "is" : "get";
216         if (!method.getName().startsWith(prefix)) {
217             return null;
218         }
219
220         final String fieldName = StringExtensions.toFirstLower(method.getName().substring(prefix.length()));
221         final GeneratedTOBuilder tmpGenTO = new CodegenGeneratedTOBuilder(JavaTypeName.create("foo", "foo"));
222         tmpGenTO.addProperty(fieldName).setReturnType(method.getReturnType());
223         return tmpGenTO.build().getProperties().get(0);
224     }
225 }