Merge "BUG-1276: fixed generated union constructor"
[yangtools.git] / code-generator / binding-generator-util / src / main / java / org / opendaylight / yangtools / binding / generator / util / BindingGeneratorUtil.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.yangtools.binding.generator.util;
9
10 import com.google.common.base.CharMatcher;
11 import com.google.common.collect.Iterables;
12
13 import java.io.ByteArrayOutputStream;
14 import java.io.DataOutputStream;
15 import java.io.IOException;
16 import java.security.MessageDigest;
17 import java.security.NoSuchAlgorithmException;
18 import java.util.ArrayList;
19 import java.util.Collection;
20 import java.util.Collections;
21 import java.util.Comparator;
22 import java.util.Iterator;
23 import java.util.List;
24
25 import org.opendaylight.yangtools.sal.binding.model.api.AccessModifier;
26 import org.opendaylight.yangtools.sal.binding.model.api.Restrictions;
27 import org.opendaylight.yangtools.sal.binding.model.api.Type;
28 import org.opendaylight.yangtools.sal.binding.model.api.type.builder.GeneratedPropertyBuilder;
29 import org.opendaylight.yangtools.sal.binding.model.api.type.builder.GeneratedTypeBuilderBase;
30 import org.opendaylight.yangtools.sal.binding.model.api.type.builder.MethodSignatureBuilder;
31 import org.opendaylight.yangtools.sal.binding.model.api.type.builder.TypeMemberBuilder;
32 import org.opendaylight.yangtools.yang.binding.BindingMapping;
33 import org.opendaylight.yangtools.yang.common.QName;
34 import org.opendaylight.yangtools.yang.common.QNameModule;
35 import org.opendaylight.yangtools.yang.model.api.Module;
36 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
37 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
38 import org.opendaylight.yangtools.yang.model.api.type.DecimalTypeDefinition;
39 import org.opendaylight.yangtools.yang.model.api.type.IntegerTypeDefinition;
40 import org.opendaylight.yangtools.yang.model.api.type.LengthConstraint;
41 import org.opendaylight.yangtools.yang.model.api.type.PatternConstraint;
42 import org.opendaylight.yangtools.yang.model.api.type.RangeConstraint;
43 import org.opendaylight.yangtools.yang.model.api.type.UnsignedIntegerTypeDefinition;
44 import org.opendaylight.yangtools.yang.model.util.ExtendedType;
45
46 /**
47  * Contains the methods for converting strings to valid JAVA language strings
48  * (package names, class names, attribute names).
49  *
50  *
51  */
52 public final class BindingGeneratorUtil {
53
54     /**
55      * Impossible to instantiate this class. All of the methods or attributes
56      * are static.
57      */
58     private BindingGeneratorUtil() {
59     }
60
61     /**
62      * Pre-compiled replacement pattern.
63      */
64     private static final CharMatcher DOT_MATCHER = CharMatcher.is('.');
65     private static final CharMatcher DASH_COLON_MATCHER = CharMatcher.anyOf("-:");
66
67     private static final Restrictions EMPTY_RESTRICTIONS = new Restrictions() {
68         @Override
69         public List<LengthConstraint> getLengthConstraints() {
70             return Collections.emptyList();
71         }
72
73         @Override
74         public List<PatternConstraint> getPatternConstraints() {
75             return Collections.emptyList();
76         }
77
78         @Override
79         public List<RangeConstraint> getRangeConstraints() {
80             return Collections.emptyList();
81         }
82
83         @Override
84         public boolean isEmpty() {
85             return true;
86         }
87     };
88
89     private static final Comparator<TypeMemberBuilder<?>> SUID_MEMBER_COMPARATOR = new Comparator<TypeMemberBuilder<?>>() {
90         @Override
91         public int compare(final TypeMemberBuilder<?> o1, final TypeMemberBuilder<?> o2) {
92             return o1.getName().compareTo(o2.getName());
93         }
94     };
95
96     private static final Comparator<Type> SUID_NAME_COMPARATOR = new Comparator<Type>() {
97         @Override
98         public int compare(final Type o1, final Type o2) {
99             return o1.getFullyQualifiedName().compareTo(o2.getFullyQualifiedName());
100         }
101     };
102
103     /**
104      * Converts <code>parameterName</code> to valid JAVA parameter name.
105      *
106      * If the <code>parameterName</code> is one of the JAVA reserved words then
107      * it is prefixed with underscore character.
108      *
109      * @param parameterName
110      *            string with the parameter name
111      * @return string with the admissible parameter name
112      */
113     public static String resolveJavaReservedWordEquivalency(final String parameterName) {
114         if (parameterName != null && BindingMapping.JAVA_RESERVED_WORDS.contains(parameterName)) {
115             return "_" + parameterName;
116         }
117         return parameterName;
118     }
119
120     /**
121      * Converts module name to valid JAVA package name.
122      *
123      * The package name consists of:
124      * <ul>
125      * <li>prefix - <i>org.opendaylight.yang.gen.v</i></li>
126      * <li>module YANG version - <i>org.opendaylight.yang.gen.v</i></li>
127      * <li>module namespace - invalid characters are replaced with dots</li>
128      * <li>revision prefix - <i>.rev</i></li>
129      * <li>revision - YYYYMMDD (MM and DD aren't spread to the whole length)</li>
130      * </ul>
131      *
132      * @param module
133      *            module which contains data about namespace and revision date
134      * @return string with the valid JAVA package name
135      * @throws IllegalArgumentException
136      *             if the revision date of the <code>module</code> equals
137      *             <code>null</code>
138      * @deprecated USe {@link BindingMapping#getRootPackageName(QNameModule)} with {@link Module#getQNameModule()}.
139      */
140     @Deprecated
141     public static String moduleNamespaceToPackageName(final Module module) {
142         return BindingMapping.getRootPackageName(module.getQNameModule());
143     }
144
145     public static String packageNameForGeneratedType(final String basePackageName, final SchemaPath schemaPath) {
146         return packageNameForGeneratedType(basePackageName, schemaPath, false);
147     }
148
149     /**
150      * Creates package name from specified <code>basePackageName</code> (package
151      * name for module) and <code>schemaPath</code>.
152      *
153      * Resulting package name is concatenation of <code>basePackageName</code>
154      * and all local names of YANG nodes which are parents of some node for
155      * which <code>schemaPath</code> is specified.
156      *
157      * @param basePackageName
158      *            string with package name of the module
159      * @param schemaPath
160      *            list of names of YANG nodes which are parents of some node +
161      *            name of this node
162      * @return string with valid JAVA package name
163      */
164     public static String packageNameForGeneratedType(final String basePackageName, final SchemaPath schemaPath,
165             final boolean isUsesAugment) {
166         if (basePackageName == null) {
167             throw new IllegalArgumentException("Base Package Name cannot be NULL!");
168         }
169         if (schemaPath == null) {
170             throw new IllegalArgumentException("Schema Path cannot be NULL!");
171         }
172
173         final StringBuilder builder = new StringBuilder();
174         builder.append(basePackageName);
175         final Iterable<QName> iterable = schemaPath.getPathFromRoot();
176         final Iterator<QName> iterator = iterable.iterator();
177         int size = Iterables.size(iterable);
178         final int traversalSteps;
179         if (isUsesAugment) {
180             traversalSteps = size;
181         } else {
182             traversalSteps = size - 1;
183         }
184         for (int i = 0; i < traversalSteps; ++i) {
185             builder.append('.');
186             String nodeLocalName = iterator.next().getLocalName();
187             // FIXME: Collon ":" is invalid in node local name as per RFC6020, identifier statement.
188             builder.append(DASH_COLON_MATCHER.replaceFrom(nodeLocalName, '.'));
189         }
190         return BindingMapping.normalizePackageName(builder.toString());
191     }
192
193     /**
194      * Generates the package name for type definition from
195      * <code>typeDefinition</code> and <code>basePackageName</code>.
196      *
197      * @param basePackageName
198      *            string with the package name of the module
199      * @param typeDefinition
200      *            type definition for which the package name will be generated *
201      * @return string with valid JAVA package name
202      * @throws IllegalArgumentException
203      *             <ul>
204      *             <li>if <code>basePackageName</code> equals <code>null</code></li>
205      *             <li>if <code>typeDefinition</code> equals <code>null</code></li>
206      *             </ul>
207      */
208     public static String packageNameForTypeDefinition(final String basePackageName,
209             final TypeDefinition<?> typeDefinition) {
210         if (basePackageName == null) {
211             throw new IllegalArgumentException("Base Package Name cannot be NULL!");
212         }
213         if (typeDefinition == null) {
214             throw new IllegalArgumentException("Type Definition reference cannot be NULL!");
215         }
216
217         final StringBuilder builder = new StringBuilder();
218         builder.append(basePackageName);
219         return BindingMapping.normalizePackageName(builder.toString());
220     }
221
222     /**
223      * Converts <code>token</code> to string which is in accordance with best
224      * practices for JAVA class names.
225      *
226      * @param token
227      *            string which contains characters which should be converted to
228      *            JAVA class name
229      * @return string which is in accordance with best practices for JAVA class
230      *         name.
231      *
232      * @deprecated Use {@link BindingMapping#getClassName(QName)} instead.
233      */
234     @Deprecated
235     public static String parseToClassName(final String token) {
236         return parseToCamelCase(token, true);
237     }
238
239     /**
240      * Converts <code>token</code> to string which is in accordance with best
241      * practices for JAVA parameter names.
242      *
243      * @param token
244      *            string which contains characters which should be converted to
245      *            JAVA parameter name
246      * @return string which is in accordance with best practices for JAVA
247      *         parameter name.
248      */
249     public static String parseToValidParamName(final String token) {
250         return resolveJavaReservedWordEquivalency(parseToCamelCase(token, false));
251     }
252
253     /**
254      *
255      * Converts string <code>token</code> to the cammel case format.
256      *
257      * @param token
258      *            string which should be converted to the cammel case format
259      * @param uppercase
260      *            boolean value which says whether the first character of the
261      *            <code>token</code> should|shuldn't be uppercased
262      * @return string in the cammel case format
263      * @throws IllegalArgumentException
264      *             <ul>
265      *             <li>if <code>token</code> without white spaces is empty</li>
266      *             <li>if <code>token</code> equals null</li>
267      *             </ul>
268      */
269
270     private static String parseToCamelCase(final String token, final boolean uppercase) {
271         if (token == null) {
272             throw new IllegalArgumentException("Name can not be null");
273         }
274
275         String correctStr = DOT_MATCHER.removeFrom(token.trim());
276         if (correctStr.isEmpty()) {
277             throw new IllegalArgumentException("Name can not be empty");
278         }
279
280         correctStr = replaceWithCamelCase(correctStr, ' ');
281         correctStr = replaceWithCamelCase(correctStr, '-');
282         correctStr = replaceWithCamelCase(correctStr, '_');
283
284         char firstChar = correctStr.charAt(0);
285         firstChar = uppercase ? Character.toUpperCase(firstChar) : Character.toLowerCase(firstChar);
286
287         if (firstChar >= '0' && firstChar <= '9') {
288             return correctStr = '_' + correctStr;
289         } else {
290             return correctStr = firstChar + correctStr.substring(1);
291         }
292     }
293
294     /**
295      * Replaces all the occurrences of the <code>removalChar</code> in the
296      * <code>text</code> with empty string and converts following character to
297      * upper case.
298      *
299      * @param text
300      *            string with source text which should be converted
301      * @param removalChar
302      *            character which is sought in the <code>text</code>
303      * @return string which doesn't contain <code>removalChar</code> and has
304      *         following characters converted to upper case
305      * @throws IllegalArgumentException
306      *             if the length of the returning string has length 0
307      */
308     private static String replaceWithCamelCase(final String text, final char removalChar) {
309         int toBeRemovedPos = text.indexOf(removalChar);
310         if (toBeRemovedPos == -1) {
311             return text;
312         }
313
314         StringBuilder sb = new StringBuilder(text);
315         String toBeRemoved = String.valueOf(removalChar);
316         do {
317             sb.replace(toBeRemovedPos, toBeRemovedPos + 1, "");
318             // check if 'toBeRemoved' character is not the only character in
319             // 'text'
320             if (sb.length() == 0) {
321                 throw new IllegalArgumentException("The resulting string can not be empty");
322             }
323             char replacement = Character.toUpperCase(sb.charAt(toBeRemovedPos));
324             sb.setCharAt(toBeRemovedPos, replacement);
325             toBeRemovedPos = sb.indexOf(toBeRemoved);
326         } while (toBeRemovedPos != -1);
327
328         return sb.toString();
329     }
330
331     private static <T> Iterable<T> sortedCollection(final Comparator<? super T> comparator, final Collection<T> input) {
332         if (input.size() > 1) {
333             final List<T> ret = new ArrayList<>(input);
334             Collections.sort(ret, comparator);
335             return ret;
336         } else {
337             return input;
338         }
339     }
340
341     public static long computeDefaultSUID(final GeneratedTypeBuilderBase<?> to) {
342         try {
343             ByteArrayOutputStream bout = new ByteArrayOutputStream();
344             DataOutputStream dout = new DataOutputStream(bout);
345
346             dout.writeUTF(to.getName());
347             dout.writeInt(to.isAbstract() ? 3 : 7);
348
349             for (Type ifc : sortedCollection(SUID_NAME_COMPARATOR, to.getImplementsTypes())) {
350                 dout.writeUTF(ifc.getFullyQualifiedName());
351             }
352
353             for (GeneratedPropertyBuilder gp : sortedCollection(SUID_MEMBER_COMPARATOR, to.getProperties())) {
354                 dout.writeUTF(gp.getName());
355             }
356
357             for (MethodSignatureBuilder m : sortedCollection(SUID_MEMBER_COMPARATOR, to.getMethodDefinitions())) {
358                 if (!(m.getAccessModifier().equals(AccessModifier.PRIVATE))) {
359                     dout.writeUTF(m.getName());
360                     dout.write(m.getAccessModifier().ordinal());
361                 }
362             }
363
364             dout.flush();
365
366             try {
367                 MessageDigest md = MessageDigest.getInstance("SHA");
368                 byte[] hashBytes = md.digest(bout.toByteArray());
369                 long hash = 0;
370                 for (int i = Math.min(hashBytes.length, 8) - 1; i >= 0; i--) {
371                     hash = (hash << 8) | (hashBytes[i] & 0xFF);
372                 }
373                 return hash;
374             } catch (NoSuchAlgorithmException ex) {
375                 throw new SecurityException(ex.getMessage());
376             }
377         } catch (IOException ex) {
378             throw new InternalError();
379         }
380     }
381
382     public static Restrictions getRestrictions(final TypeDefinition<?> type) {
383         // Base types have no constraints, so get over it quickly
384         if (!(type instanceof ExtendedType)) {
385             return EMPTY_RESTRICTIONS;
386         }
387
388         // Take care of the extended types ...
389         final ExtendedType ext = (ExtendedType)type;
390         final List<LengthConstraint> length = ext.getLengthConstraints();
391         final List<PatternConstraint> pattern = ext.getPatternConstraints();
392
393         List<RangeConstraint> tmp = ext.getRangeConstraints();
394         if (tmp.isEmpty()) {
395             final TypeDefinition<?> base = ext.getBaseType();
396             if (base instanceof IntegerTypeDefinition) {
397                 tmp = ((IntegerTypeDefinition)base).getRangeConstraints();
398             } else if (base instanceof UnsignedIntegerTypeDefinition) {
399                 tmp = ((UnsignedIntegerTypeDefinition)base).getRangeConstraints();
400             } else if (base instanceof DecimalTypeDefinition) {
401                 tmp = ((DecimalTypeDefinition)base).getRangeConstraints();
402             }
403         }
404
405         // Now, this may have ended up being empty, too...
406         if (length.isEmpty() && pattern.isEmpty() && tmp.isEmpty()) {
407             return EMPTY_RESTRICTIONS;
408         }
409
410         // Nope, not empty allocate a holder
411         final List<RangeConstraint> range = tmp;
412         return new Restrictions() {
413             @Override
414             public List<RangeConstraint> getRangeConstraints() {
415                 return range;
416             }
417             @Override
418             public List<PatternConstraint> getPatternConstraints() {
419                 return pattern;
420             }
421             @Override
422             public List<LengthConstraint> getLengthConstraints() {
423                 return length;
424             }
425             @Override
426             public boolean isEmpty() {
427                 return false;
428             }
429         };
430     }
431
432 }