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