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