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