f5d72d762d038e561596fe4242ee18db3ebbc2bb
[mdsal.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.CharMatcher;
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.security.MessageDigest;
16 import java.security.NoSuchAlgorithmException;
17 import java.util.ArrayList;
18 import java.util.Collection;
19 import java.util.Collections;
20 import java.util.Comparator;
21 import java.util.Iterator;
22 import java.util.List;
23 import org.opendaylight.yangtools.sal.binding.model.api.AccessModifier;
24 import org.opendaylight.yangtools.sal.binding.model.api.Restrictions;
25 import org.opendaylight.yangtools.sal.binding.model.api.Type;
26 import org.opendaylight.yangtools.sal.binding.model.api.type.builder.GeneratedPropertyBuilder;
27 import org.opendaylight.yangtools.sal.binding.model.api.type.builder.GeneratedTypeBuilderBase;
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.common.QNameModule;
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     /**
53      * Impossible to instantiate this class. All of the methods or attributes
54      * are static.
55      */
56     private BindingGeneratorUtil() {
57     }
58
59     /**
60      * Pre-compiled replacement pattern.
61      */
62     private static final CharMatcher DOT_MATCHER = CharMatcher.is('.');
63     private static final CharMatcher DASH_COLON_MATCHER = CharMatcher.anyOf("-:");
64
65     private static final Restrictions EMPTY_RESTRICTIONS = new Restrictions() {
66         @Override
67         public List<LengthConstraint> getLengthConstraints() {
68             return Collections.emptyList();
69         }
70
71         @Override
72         public List<PatternConstraint> getPatternConstraints() {
73             return Collections.emptyList();
74         }
75
76         @Override
77         public List<RangeConstraint> getRangeConstraints() {
78             return Collections.emptyList();
79         }
80
81         @Override
82         public boolean isEmpty() {
83             return true;
84         }
85     };
86
87     private static final Comparator<TypeMemberBuilder<?>> SUID_MEMBER_COMPARATOR = new Comparator<TypeMemberBuilder<?>>() {
88         @Override
89         public int compare(final TypeMemberBuilder<?> o1, final TypeMemberBuilder<?> o2) {
90             return o1.getName().compareTo(o2.getName());
91         }
92     };
93
94     private static final Comparator<Type> SUID_NAME_COMPARATOR = new Comparator<Type>() {
95         @Override
96         public int compare(final Type o1, final Type o2) {
97             return o1.getFullyQualifiedName().compareTo(o2.getFullyQualifiedName());
98         }
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 && BindingMapping.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      * @deprecated USe {@link BindingMapping#getRootPackageName(QNameModule)} with {@link Module#getQNameModule()}.
137      */
138     @Deprecated
139     public static String moduleNamespaceToPackageName(final Module module) {
140         return BindingMapping.getRootPackageName(module.getQNameModule());
141     }
142
143     public static String packageNameForGeneratedType(final String basePackageName, final SchemaPath schemaPath) {
144         return packageNameForGeneratedType(basePackageName, schemaPath, false);
145     }
146
147     /**
148      * Creates package name from specified <code>basePackageName</code> (package
149      * name for module) and <code>schemaPath</code>.
150      *
151      * Resulting package name is concatenation of <code>basePackageName</code>
152      * and all local names of YANG nodes which are parents of some node for
153      * which <code>schemaPath</code> is specified.
154      *
155      * @param basePackageName
156      *            string with package name of the module
157      * @param schemaPath
158      *            list of names of YANG nodes which are parents of some node +
159      *            name of this node
160      * @return string with valid JAVA package name
161      */
162     public static String packageNameForGeneratedType(final String basePackageName, final SchemaPath schemaPath,
163             final boolean isUsesAugment) {
164         if (basePackageName == null) {
165             throw new IllegalArgumentException("Base Package Name cannot be NULL!");
166         }
167         if (schemaPath == null) {
168             throw new IllegalArgumentException("Schema Path cannot be NULL!");
169         }
170
171         final StringBuilder builder = new StringBuilder();
172         builder.append(basePackageName);
173         final Iterable<QName> iterable = schemaPath.getPathFromRoot();
174         final Iterator<QName> iterator = iterable.iterator();
175         int size = Iterables.size(iterable);
176         final int traversalSteps;
177         if (isUsesAugment) {
178             traversalSteps = size;
179         } else {
180             traversalSteps = size - 1;
181         }
182         for (int i = 0; i < traversalSteps; ++i) {
183             builder.append('.');
184             String nodeLocalName = iterator.next().getLocalName();
185             // FIXME: Collon ":" is invalid in node local name as per RFC6020, identifier statement.
186             builder.append(DASH_COLON_MATCHER.replaceFrom(nodeLocalName, '.'));
187         }
188         return BindingMapping.normalizePackageName(builder.toString());
189     }
190
191     /**
192      * Generates the package name for type definition from
193      * <code>typeDefinition</code> and <code>basePackageName</code>.
194      *
195      * @param basePackageName
196      *            string with the package name of the module
197      * @param typeDefinition
198      *            type definition for which the package name will be generated *
199      * @return string with valid JAVA package name
200      * @throws IllegalArgumentException
201      *             <ul>
202      *             <li>if <code>basePackageName</code> equals <code>null</code></li>
203      *             <li>if <code>typeDefinition</code> equals <code>null</code></li>
204      *             </ul>
205      */
206     public static String packageNameForTypeDefinition(final String basePackageName,
207             final TypeDefinition<?> typeDefinition) {
208         if (basePackageName == null) {
209             throw new IllegalArgumentException("Base Package Name cannot be NULL!");
210         }
211         if (typeDefinition == null) {
212             throw new IllegalArgumentException("Type Definition reference cannot be NULL!");
213         }
214
215         final StringBuilder builder = new StringBuilder();
216         builder.append(basePackageName);
217         return BindingMapping.normalizePackageName(builder.toString());
218     }
219
220     /**
221      * Converts <code>token</code> to string which is in accordance with best
222      * practices for JAVA class names.
223      *
224      * @param token
225      *            string which contains characters which should be converted to
226      *            JAVA class name
227      * @return string which is in accordance with best practices for JAVA class
228      *         name.
229      *
230      * @deprecated Use {@link BindingMapping#getClassName(QName)} instead.
231      */
232     @Deprecated
233     public static String parseToClassName(final String token) {
234         return parseToCamelCase(token, true);
235     }
236
237     /**
238      * Converts <code>token</code> to string which is in accordance with best
239      * practices for JAVA parameter names.
240      *
241      * @param token
242      *            string which contains characters which should be converted to
243      *            JAVA parameter name
244      * @return string which is in accordance with best practices for JAVA
245      *         parameter name.
246      *
247      * @deprecated Use {@link BindingMapping#getPropertyName(String)} instead.
248      */
249     @Deprecated public static String parseToValidParamName(final String token) {
250         return resolveJavaReservedWordEquivalency(parseToCamelCase(token, false));
251     }
252
253     /**
254      *
255      * Converts string <code>token</code> to the cammel case format.
256      *
257      * @param token
258      *            string which should be converted to the cammel case format
259      * @param uppercase
260      *            boolean value which says whether the first character of the
261      *            <code>token</code> should|shuldn't be uppercased
262      * @return string in the cammel case format
263      * @throws IllegalArgumentException
264      *             <ul>
265      *             <li>if <code>token</code> without white spaces is empty</li>
266      *             <li>if <code>token</code> equals null</li>
267      *             </ul>
268      */
269
270     private static String parseToCamelCase(final String token, final boolean uppercase) {
271         if (token == null) {
272             throw new IllegalArgumentException("Name can not be null");
273         }
274
275         String correctStr = DOT_MATCHER.removeFrom(token.trim());
276         if (correctStr.isEmpty()) {
277             throw new IllegalArgumentException("Name can not be empty");
278         }
279
280         correctStr = replaceWithCamelCase(correctStr, ' ');
281         correctStr = replaceWithCamelCase(correctStr, '-');
282         correctStr = replaceWithCamelCase(correctStr, '_');
283
284         char firstChar = correctStr.charAt(0);
285         firstChar = uppercase ? Character.toUpperCase(firstChar) : Character.toLowerCase(firstChar);
286
287         if (firstChar >= '0' && firstChar <= '9') {
288             return '_' + correctStr;
289         } else {
290             return firstChar + correctStr.substring(1);
291         }
292     }
293
294     /**
295      * Replaces all the occurrences of the <code>removalChar</code> in the
296      * <code>text</code> with empty string and converts following character to
297      * upper case.
298      *
299      * @param text
300      *            string with source text which should be converted
301      * @param removalChar
302      *            character which is sought in the <code>text</code>
303      * @return string which doesn't contain <code>removalChar</code> and has
304      *         following characters converted to upper case
305      * @throws IllegalArgumentException
306      *             if the length of the returning string has length 0
307      */
308     private static String replaceWithCamelCase(final String text, final char removalChar) {
309         int toBeRemovedPos = text.indexOf(removalChar);
310         if (toBeRemovedPos == -1) {
311             return text;
312         }
313
314         StringBuilder sb = new StringBuilder(text);
315         String toBeRemoved = String.valueOf(removalChar);
316         do {
317             sb.replace(toBeRemovedPos, toBeRemovedPos + 1, "");
318             // check if 'toBeRemoved' character is not the only character in
319             // 'text'
320             if (sb.length() == 0) {
321                 throw new IllegalArgumentException("The resulting string can not be empty");
322             }
323             char replacement = Character.toUpperCase(sb.charAt(toBeRemovedPos));
324             sb.setCharAt(toBeRemovedPos, replacement);
325             toBeRemovedPos = sb.indexOf(toBeRemoved);
326         } while (toBeRemovedPos != -1);
327
328         return sb.toString();
329     }
330
331     private static <T> Iterable<T> sortedCollection(final Comparator<? super T> comparator, final Collection<T> input) {
332         if (input.size() > 1) {
333             final List<T> ret = new ArrayList<>(input);
334             Collections.sort(ret, comparator);
335             return ret;
336         } else {
337             return input;
338         }
339     }
340
341     public static long computeDefaultSUID(final GeneratedTypeBuilderBase<?> to) {
342         try {
343             ByteArrayOutputStream bout = new ByteArrayOutputStream();
344             DataOutputStream dout = new DataOutputStream(bout);
345
346             dout.writeUTF(to.getName());
347             dout.writeInt(to.isAbstract() ? 3 : 7);
348
349             for (Type ifc : sortedCollection(SUID_NAME_COMPARATOR, to.getImplementsTypes())) {
350                 dout.writeUTF(ifc.getFullyQualifiedName());
351             }
352
353             for (GeneratedPropertyBuilder gp : sortedCollection(SUID_MEMBER_COMPARATOR, to.getProperties())) {
354                 dout.writeUTF(gp.getName());
355             }
356
357             for (MethodSignatureBuilder m : sortedCollection(SUID_MEMBER_COMPARATOR, to.getMethodDefinitions())) {
358                 if (!(m.getAccessModifier().equals(AccessModifier.PRIVATE))) {
359                     dout.writeUTF(m.getName());
360                     dout.write(m.getAccessModifier().ordinal());
361                 }
362             }
363
364             dout.flush();
365
366             try {
367                 MessageDigest md = MessageDigest.getInstance("SHA");
368                 byte[] hashBytes = md.digest(bout.toByteArray());
369                 long hash = 0;
370                 for (int i = Math.min(hashBytes.length, 8) - 1; i >= 0; i--) {
371                     hash = (hash << 8) | (hashBytes[i] & 0xFF);
372                 }
373                 return hash;
374             } catch (NoSuchAlgorithmException ex) {
375                 throw new SecurityException(ex.getMessage());
376             }
377         } catch (IOException ex) {
378             throw new InternalError();
379         }
380     }
381
382     public static Restrictions getRestrictions(final TypeDefinition<?> type) {
383         // Base types have no constraints, so get over it quickly
384         if (!(type instanceof ExtendedType)) {
385             return EMPTY_RESTRICTIONS;
386         }
387
388         // Take care of the extended types ...
389         final ExtendedType ext = (ExtendedType)type;
390         final List<LengthConstraint> length = ext.getLengthConstraints();
391         final List<PatternConstraint> pattern = ext.getPatternConstraints();
392
393         List<RangeConstraint> tmp = ext.getRangeConstraints();
394         if (tmp.isEmpty()) {
395             final TypeDefinition<?> base = ext.getBaseType();
396             if (base instanceof IntegerTypeDefinition) {
397                 tmp = ((IntegerTypeDefinition)base).getRangeConstraints();
398             } else if (base instanceof UnsignedIntegerTypeDefinition) {
399                 tmp = ((UnsignedIntegerTypeDefinition)base).getRangeConstraints();
400             } else if (base instanceof DecimalTypeDefinition) {
401                 tmp = ((DecimalTypeDefinition)base).getRangeConstraints();
402             }
403         }
404
405         // Now, this may have ended up being empty, too...
406         if (length.isEmpty() && pattern.isEmpty() && tmp.isEmpty()) {
407             return EMPTY_RESTRICTIONS;
408         }
409
410         // Nope, not empty allocate a holder
411         final List<RangeConstraint> range = tmp;
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 false;
428             }
429         };
430     }
431
432 }