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