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