2 * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.yangtools.binding.generator.util;
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;
45 * Contains the methods for converting strings to valid JAVA language strings
46 * (package names, class names, attribute names).
50 public final class BindingGeneratorUtil {
53 * Impossible to instantiate this class. All of the methods or attributes
56 private BindingGeneratorUtil() {
60 * Pre-compiled replacement pattern.
62 private static final CharMatcher DOT_MATCHER = CharMatcher.is('.');
63 private static final CharMatcher DASH_COLON_MATCHER = CharMatcher.anyOf("-:");
65 private static final Restrictions EMPTY_RESTRICTIONS = new Restrictions() {
67 public List<LengthConstraint> getLengthConstraints() {
68 return Collections.emptyList();
72 public List<PatternConstraint> getPatternConstraints() {
73 return Collections.emptyList();
77 public List<RangeConstraint> getRangeConstraints() {
78 return Collections.emptyList();
82 public boolean isEmpty() {
87 private static final Comparator<TypeMemberBuilder<?>> SUID_MEMBER_COMPARATOR = new Comparator<TypeMemberBuilder<?>>() {
89 public int compare(final TypeMemberBuilder<?> o1, final TypeMemberBuilder<?> o2) {
90 return o1.getName().compareTo(o2.getName());
94 private static final Comparator<Type> SUID_NAME_COMPARATOR = new Comparator<Type>() {
96 public int compare(final Type o1, final Type o2) {
97 return o1.getFullyQualifiedName().compareTo(o2.getFullyQualifiedName());
102 * Converts <code>parameterName</code> to valid JAVA parameter name.
104 * If the <code>parameterName</code> is one of the JAVA reserved words then
105 * it is prefixed with underscore character.
107 * @param parameterName
108 * string with the parameter name
109 * @return string with the admissible parameter name
111 public static String resolveJavaReservedWordEquivalency(final String parameterName) {
112 if (parameterName != null && BindingMapping.JAVA_RESERVED_WORDS.contains(parameterName)) {
113 return "_" + parameterName;
115 return parameterName;
119 * Converts module name to valid JAVA package name.
121 * The package name consists of:
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>
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
136 * @deprecated USe {@link BindingMapping#getRootPackageName(QNameModule)} with {@link Module#getQNameModule()}.
139 public static String moduleNamespaceToPackageName(final Module module) {
140 return BindingMapping.getRootPackageName(module.getQNameModule());
143 public static String packageNameForGeneratedType(final String basePackageName, final SchemaPath schemaPath) {
144 return packageNameForGeneratedType(basePackageName, schemaPath, false);
148 * Creates package name from specified <code>basePackageName</code> (package
149 * name for module) and <code>schemaPath</code>.
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.
155 * @param basePackageName
156 * string with package name of the module
158 * list of names of YANG nodes which are parents of some node +
160 * @return string with valid JAVA package name
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!");
167 if (schemaPath == null) {
168 throw new IllegalArgumentException("Schema Path cannot be NULL!");
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;
178 traversalSteps = size;
180 traversalSteps = size - 1;
182 for (int i = 0; i < traversalSteps; ++i) {
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, '.'));
188 return BindingMapping.normalizePackageName(builder.toString());
192 * Generates the package name for type definition from
193 * <code>typeDefinition</code> and <code>basePackageName</code>.
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
202 * <li>if <code>basePackageName</code> equals <code>null</code></li>
203 * <li>if <code>typeDefinition</code> equals <code>null</code></li>
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!");
211 if (typeDefinition == null) {
212 throw new IllegalArgumentException("Type Definition reference cannot be NULL!");
215 final StringBuilder builder = new StringBuilder();
216 builder.append(basePackageName);
217 return BindingMapping.normalizePackageName(builder.toString());
221 * Converts <code>token</code> to string which is in accordance with best
222 * practices for JAVA class names.
225 * string which contains characters which should be converted to
227 * @return string which is in accordance with best practices for JAVA class
230 * @deprecated Use {@link BindingMapping#getClassName(QName)} instead.
233 public static String parseToClassName(final String token) {
234 return parseToCamelCase(token, true);
238 * Converts <code>token</code> to string which is in accordance with best
239 * practices for JAVA parameter names.
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
247 * @deprecated Use {@link BindingMapping#getPropertyName(String)} instead.
249 @Deprecated public static String parseToValidParamName(final String token) {
250 return resolveJavaReservedWordEquivalency(parseToCamelCase(token, false));
255 * Converts string <code>token</code> to the cammel case format.
258 * string which should be converted to the cammel case format
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
265 * <li>if <code>token</code> without white spaces is empty</li>
266 * <li>if <code>token</code> equals null</li>
270 private static String parseToCamelCase(final String token, final boolean uppercase) {
272 throw new IllegalArgumentException("Name can not be null");
275 String correctStr = DOT_MATCHER.removeFrom(token.trim());
276 if (correctStr.isEmpty()) {
277 throw new IllegalArgumentException("Name can not be empty");
280 correctStr = replaceWithCamelCase(correctStr, ' ');
281 correctStr = replaceWithCamelCase(correctStr, '-');
282 correctStr = replaceWithCamelCase(correctStr, '_');
284 char firstChar = correctStr.charAt(0);
285 firstChar = uppercase ? Character.toUpperCase(firstChar) : Character.toLowerCase(firstChar);
287 if (firstChar >= '0' && firstChar <= '9') {
288 return '_' + correctStr;
290 return firstChar + correctStr.substring(1);
295 * Replaces all the occurrences of the <code>removalChar</code> in the
296 * <code>text</code> with empty string and converts following character to
300 * string with source text which should be converted
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
308 private static String replaceWithCamelCase(final String text, final char removalChar) {
309 int toBeRemovedPos = text.indexOf(removalChar);
310 if (toBeRemovedPos == -1) {
314 StringBuilder sb = new StringBuilder(text);
315 String toBeRemoved = String.valueOf(removalChar);
317 sb.replace(toBeRemovedPos, toBeRemovedPos + 1, "");
318 // check if 'toBeRemoved' character is not the only character in
320 if (sb.length() == 0) {
321 throw new IllegalArgumentException("The resulting string can not be empty");
323 char replacement = Character.toUpperCase(sb.charAt(toBeRemovedPos));
324 sb.setCharAt(toBeRemovedPos, replacement);
325 toBeRemovedPos = sb.indexOf(toBeRemoved);
326 } while (toBeRemovedPos != -1);
328 return sb.toString();
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);
341 public static long computeDefaultSUID(final GeneratedTypeBuilderBase<?> to) {
343 ByteArrayOutputStream bout = new ByteArrayOutputStream();
344 DataOutputStream dout = new DataOutputStream(bout);
346 dout.writeUTF(to.getName());
347 dout.writeInt(to.isAbstract() ? 3 : 7);
349 for (Type ifc : sortedCollection(SUID_NAME_COMPARATOR, to.getImplementsTypes())) {
350 dout.writeUTF(ifc.getFullyQualifiedName());
353 for (GeneratedPropertyBuilder gp : sortedCollection(SUID_MEMBER_COMPARATOR, to.getProperties())) {
354 dout.writeUTF(gp.getName());
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());
367 MessageDigest md = MessageDigest.getInstance("SHA");
368 byte[] hashBytes = md.digest(bout.toByteArray());
370 for (int i = Math.min(hashBytes.length, 8) - 1; i >= 0; i--) {
371 hash = (hash << 8) | (hashBytes[i] & 0xFF);
374 } catch (NoSuchAlgorithmException ex) {
375 throw new SecurityException(ex.getMessage());
377 } catch (IOException ex) {
378 throw new InternalError();
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;
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();
393 List<RangeConstraint> tmp = ext.getRangeConstraints();
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();
405 // Now, this may have ended up being empty, too...
406 if (length.isEmpty() && pattern.isEmpty() && tmp.isEmpty()) {
407 return EMPTY_RESTRICTIONS;
410 // Nope, not empty allocate a holder
411 final List<RangeConstraint> range = tmp;
412 return new Restrictions() {
414 public List<RangeConstraint> getRangeConstraints() {
418 public List<PatternConstraint> getPatternConstraints() {
422 public List<LengthConstraint> getLengthConstraints() {
426 public boolean isEmpty() {