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