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 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.Arrays;
18 import java.util.Collections;
19 import java.util.Comparator;
20 import java.util.HashSet;
21 import java.util.List;
23 import java.util.regex.Matcher;
24 import java.util.regex.Pattern;
26 import org.opendaylight.yangtools.binding.generator.util.generated.type.builder.GeneratedTOBuilderImpl;
27 import org.opendaylight.yangtools.sal.binding.model.api.AccessModifier;
28 import org.opendaylight.yangtools.sal.binding.model.api.Restrictions;
29 import org.opendaylight.yangtools.sal.binding.model.api.Type;
30 import org.opendaylight.yangtools.sal.binding.model.api.type.builder.GeneratedPropertyBuilder;
31 import org.opendaylight.yangtools.sal.binding.model.api.type.builder.MethodSignatureBuilder;
32 import org.opendaylight.yangtools.sal.binding.model.api.type.builder.TypeMemberBuilder;
33 import org.opendaylight.yangtools.yang.binding.BindingMapping;
34 import org.opendaylight.yangtools.yang.common.QName;
35 import org.opendaylight.yangtools.yang.model.api.Module;
36 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
37 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
38 import org.opendaylight.yangtools.yang.model.api.type.DecimalTypeDefinition;
39 import org.opendaylight.yangtools.yang.model.api.type.IntegerTypeDefinition;
40 import org.opendaylight.yangtools.yang.model.api.type.LengthConstraint;
41 import org.opendaylight.yangtools.yang.model.api.type.PatternConstraint;
42 import org.opendaylight.yangtools.yang.model.api.type.RangeConstraint;
43 import org.opendaylight.yangtools.yang.model.api.type.UnsignedIntegerTypeDefinition;
44 import org.opendaylight.yangtools.yang.model.util.ExtendedType;
47 * Contains the methods for converting strings to valid JAVA language strings
48 * (package names, class names, attribute names).
52 public final class BindingGeneratorUtil {
54 private static final ThreadLocal<SimpleDateFormat> DATE_FORMAT = new ThreadLocal<SimpleDateFormat>() {
57 protected SimpleDateFormat initialValue() {
58 return new SimpleDateFormat("yyMMdd");
62 public void set(final SimpleDateFormat value) {
63 throw new UnsupportedOperationException();
68 * Array of strings values which represents JAVA reserved words.
71 private static final String[] SET_VALUES = new String[] { "abstract", "assert", "boolean", "break", "byte", "case",
72 "catch", "char", "class", "const", "continue", "default", "double", "do", "else", "enum", "extends",
73 "false", "final", "finally", "float", "for", "goto", "if", "implements", "import", "instanceof", "int",
74 "interface", "long", "native", "new", "null", "package", "private", "protected", "public", "return",
75 "short", "static", "strictfp", "super", "switch", "synchronized", "this", "throw", "throws", "transient",
76 "true", "try", "void", "volatile", "while" };
79 * Impossible to instantiate this class. All of the methods or attributes
82 private BindingGeneratorUtil() {
86 * Hash set of words which are reserved in JAVA language.
89 private static final Set<String> JAVA_RESERVED_WORDS = new HashSet<String>(Arrays.asList(SET_VALUES));
92 * Pre-compiled replacement pattern.
94 private static final Pattern COLON_SLASH_SLASH = Pattern.compile("://", Pattern.LITERAL);
95 private static final String QUOTED_DOT = Matcher.quoteReplacement(".");
98 * Converts string <code>packageName</code> to valid JAVA package name.
100 * If some words of package name are digits of JAVA reserved words they are
101 * prefixed with underscore character.
104 * string which contains words separated by point.
105 * @return package name which contains words separated by point.
107 private static String validateJavaPackage(final String packageName) {
108 if (packageName != null) {
109 final String[] packNameParts = packageName.toLowerCase().split("\\.");
110 if (packNameParts != null) {
111 final StringBuilder builder = new StringBuilder();
112 for (int i = 0; i < packNameParts.length; ++i) {
113 final String packNamePart = packNameParts[i];
114 if (Character.isDigit(packNamePart.charAt(0))) {
115 packNameParts[i] = "_" + packNamePart;
116 } else if (JAVA_RESERVED_WORDS.contains(packNamePart)) {
117 packNameParts[i] = "_" + packNamePart;
122 builder.append(packNameParts[i]);
124 return builder.toString();
131 * Converts <code>parameterName</code> to valid JAVA parameter name.
133 * If the <code>parameterName</code> is one of the JAVA reserved words then
134 * it is prefixed with underscore character.
136 * @param parameterName
137 * string with the parameter name
138 * @return string with the admissible parameter name
140 public static String resolveJavaReservedWordEquivalency(final String parameterName) {
141 if (parameterName != null && JAVA_RESERVED_WORDS.contains(parameterName)) {
142 return "_" + parameterName;
144 return parameterName;
148 * Converts module name to valid JAVA package name.
150 * The package name consists of:
152 * <li>prefix - <i>org.opendaylight.yang.gen.v</i></li>
153 * <li>module YANG version - <i>org.opendaylight.yang.gen.v</i></li>
154 * <li>module namespace - invalid characters are replaced with dots</li>
155 * <li>revision prefix - <i>.rev</i></li>
156 * <li>revision - YYYYMMDD (MM and DD aren't spread to the whole length)</li>
160 * module which contains data about namespace and revision date
161 * @return string with the valid JAVA package name
162 * @throws IllegalArgumentException
163 * if the revision date of the <code>module</code> equals
166 public static String moduleNamespaceToPackageName(final Module module) {
167 final StringBuilder packageNameBuilder = new StringBuilder();
169 if (module.getRevision() == null) {
170 throw new IllegalArgumentException("Module " + module.getName() + " does not specify revision date!");
172 packageNameBuilder.append("org.opendaylight.yang.gen.v");
173 packageNameBuilder.append(module.getYangVersion());
174 packageNameBuilder.append('.');
176 String namespace = module.getNamespace().toString();
177 namespace = COLON_SLASH_SLASH.matcher(namespace).replaceAll(QUOTED_DOT);
179 // FIXME: improve these
180 namespace = namespace.replace("/", ".");
181 namespace = namespace.replace(":", ".");
182 namespace = namespace.replace("-", ".");
183 namespace = namespace.replace("@", ".");
184 namespace = namespace.replace("$", ".");
185 namespace = namespace.replace("#", ".");
186 namespace = namespace.replace("'", ".");
187 namespace = namespace.replace("*", ".");
188 namespace = namespace.replace("+", ".");
189 namespace = namespace.replace(",", ".");
190 namespace = namespace.replace(";", ".");
191 namespace = namespace.replace("=", ".");
193 packageNameBuilder.append(namespace);
194 packageNameBuilder.append(".rev");
195 packageNameBuilder.append(DATE_FORMAT.get().format(module.getRevision()));
197 return validateJavaPackage(packageNameBuilder.toString());
200 public static String packageNameForGeneratedType(final String basePackageName, final SchemaPath schemaPath) {
201 return packageNameForGeneratedType(basePackageName, schemaPath, false);
205 * Creates package name from specified <code>basePackageName</code> (package
206 * name for module) and <code>schemaPath</code>.
208 * Resulting package name is concatenation of <code>basePackageName</code>
209 * and all local names of YANG nodes which are parents of some node for
210 * which <code>schemaPath</code> is specified.
212 * @param basePackageName
213 * string with package name of the module
215 * list of names of YANG nodes which are parents of some node +
217 * @return string with valid JAVA package name
219 public static String packageNameForGeneratedType(final String basePackageName, final SchemaPath schemaPath,
220 final boolean isUsesAugment) {
221 if (basePackageName == null) {
222 throw new IllegalArgumentException("Base Package Name cannot be NULL!");
224 if (schemaPath == null) {
225 throw new IllegalArgumentException("Schema Path cannot be NULL!");
228 final StringBuilder builder = new StringBuilder();
229 builder.append(basePackageName);
230 final List<QName> pathToNode = schemaPath.getPath();
231 final int traversalSteps;
233 traversalSteps = (pathToNode.size());
235 traversalSteps = (pathToNode.size() - 1);
237 for (int i = 0; i < traversalSteps; ++i) {
239 String nodeLocalName = pathToNode.get(i).getLocalName();
241 nodeLocalName = nodeLocalName.replace(":", ".");
242 nodeLocalName = nodeLocalName.replace("-", ".");
243 builder.append(nodeLocalName);
245 return validateJavaPackage(builder.toString());
249 * Generates the package name for type definition from
250 * <code>typeDefinition</code> and <code>basePackageName</code>.
252 * @param basePackageName
253 * string with the package name of the module
254 * @param typeDefinition
255 * type definition for which the package name will be generated *
256 * @return string with valid JAVA package name
257 * @throws IllegalArgumentException
259 * <li>if <code>basePackageName</code> equals <code>null</code></li>
260 * <li>if <code>typeDefinition</code> equals <code>null</code></li>
263 public static String packageNameForTypeDefinition(final String basePackageName,
264 final TypeDefinition<?> typeDefinition) {
265 if (basePackageName == null) {
266 throw new IllegalArgumentException("Base Package Name cannot be NULL!");
268 if (typeDefinition == null) {
269 throw new IllegalArgumentException("Type Definition reference cannot be NULL!");
272 final StringBuilder builder = new StringBuilder();
273 builder.append(basePackageName);
274 return validateJavaPackage(builder.toString());
278 * Converts <code>token</code> to string which is in accordance with best
279 * practices for JAVA class names.
282 * string which contains characters which should be converted to
284 * @return string which is in accordance with best practices for JAVA class
287 * @deprecated Use {@link BindingMapping#getClassName(QName)} instead.
290 public static String parseToClassName(final String token) {
291 return parseToCamelCase(token, true);
295 * Converts <code>token</code> to string which is in accordance with best
296 * practices for JAVA parameter names.
299 * string which contains characters which should be converted to
300 * JAVA parameter name
301 * @return string which is in accordance with best practices for JAVA
304 public static String parseToValidParamName(final String token) {
305 return resolveJavaReservedWordEquivalency(parseToCamelCase(token, false));
310 * Converts string <code>token</code> to the cammel case format.
313 * string which should be converted to the cammel case format
315 * boolean value which says whether the first character of the
316 * <code>token</code> should|shuldn't be uppercased
317 * @return string in the cammel case format
318 * @throws IllegalArgumentException
320 * <li>if <code>token</code> without white spaces is empty</li>
321 * <li>if <code>token</code> equals null</li>
325 private static String parseToCamelCase(final String token, final boolean uppercase) {
327 throw new IllegalArgumentException("Name can not be null");
330 String correctStr = token.trim();
331 correctStr = correctStr.replace(".", "");
333 if (correctStr.isEmpty()) {
334 throw new IllegalArgumentException("Name can not be emty");
337 correctStr = replaceWithCamelCase(correctStr, ' ');
338 correctStr = replaceWithCamelCase(correctStr, '-');
339 correctStr = replaceWithCamelCase(correctStr, '_');
341 char firstChar = correctStr.charAt(0);
342 firstChar = uppercase ? Character.toUpperCase(firstChar) : Character.toLowerCase(firstChar);
344 if (firstChar >= '0' && firstChar <= '9') {
345 return correctStr = '_' + correctStr;
347 return correctStr = firstChar + correctStr.substring(1);
352 * Replaces all the occurances of the <code>removalChar</code> in the
353 * <code>text</code> with empty string and converts following character to
357 * string with source text which should be converted
359 * character which is sought in the <code>text</code>
360 * @return string which doesn't contain <code>removalChar</code> and has
361 * following characters converted to upper case
362 * @throws IllegalArgumentException
363 * if the length of the returning string has length 0
365 private static String replaceWithCamelCase(final String text, final char removalChar) {
366 StringBuilder sb = new StringBuilder(text);
367 String toBeRemoved = String.valueOf(removalChar);
369 int toBeRemovedPos = sb.indexOf(toBeRemoved);
370 while (toBeRemovedPos != -1) {
371 sb.replace(toBeRemovedPos, toBeRemovedPos + 1, "");
372 // check if 'toBeRemoved' character is not the only character in
374 if (sb.length() == 0) {
375 throw new IllegalArgumentException("The resulting string can not be empty");
377 String replacement = String.valueOf(sb.charAt(toBeRemovedPos)).toUpperCase();
378 sb.setCharAt(toBeRemovedPos, replacement.charAt(0));
379 toBeRemovedPos = sb.indexOf(toBeRemoved);
381 return sb.toString();
384 public static long computeDefaultSUID(final GeneratedTOBuilderImpl to) {
386 ByteArrayOutputStream bout = new ByteArrayOutputStream();
387 DataOutputStream dout = new DataOutputStream(bout);
389 dout.writeUTF(to.getName());
390 dout.writeInt(to.isAbstract() ? 3 : 7);
392 List<Type> impl = to.getImplementsTypes();
393 Collections.sort(impl, new Comparator<Type>() {
395 public int compare(final Type o1, final Type o2) {
396 return o1.getFullyQualifiedName().compareTo(o2.getFullyQualifiedName());
399 for (Type ifc : impl) {
400 dout.writeUTF(ifc.getFullyQualifiedName());
403 Comparator<TypeMemberBuilder<?>> comparator = new Comparator<TypeMemberBuilder<?>>() {
405 public int compare(final TypeMemberBuilder<?> o1, final TypeMemberBuilder<?> o2) {
406 return o1.getName().compareTo(o2.getName());
410 List<GeneratedPropertyBuilder> props = to.getProperties();
411 Collections.sort(props, comparator);
412 for (GeneratedPropertyBuilder gp : props) {
413 dout.writeUTF(gp.getName());
416 List<MethodSignatureBuilder> methods = to.getMethodDefinitions();
417 Collections.sort(methods, comparator);
418 for (MethodSignatureBuilder m : methods) {
419 if (!(m.getAccessModifier().equals(AccessModifier.PRIVATE))) {
420 dout.writeUTF(m.getName());
421 dout.write(m.getAccessModifier().ordinal());
427 MessageDigest md = MessageDigest.getInstance("SHA");
428 byte[] hashBytes = md.digest(bout.toByteArray());
430 for (int i = Math.min(hashBytes.length, 8) - 1; i >= 0; i--) {
431 hash = (hash << 8) | (hashBytes[i] & 0xFF);
434 } catch (IOException ex) {
435 throw new InternalError();
436 } catch (NoSuchAlgorithmException ex) {
437 throw new SecurityException(ex.getMessage());
441 public static Restrictions getRestrictions(final TypeDefinition<?> type) {
442 final List<LengthConstraint> length = new ArrayList<>();
443 final List<PatternConstraint> pattern = new ArrayList<>();
444 final List<RangeConstraint> range = new ArrayList<>();
446 if (type instanceof ExtendedType) {
447 ExtendedType ext = (ExtendedType)type;
448 TypeDefinition<?> base = ext.getBaseType();
449 length.addAll(ext.getLengthConstraints());
450 pattern.addAll(ext.getPatternConstraints());
451 range.addAll(ext.getRangeConstraints());
453 if (base instanceof IntegerTypeDefinition && range.isEmpty()) {
454 range.addAll(((IntegerTypeDefinition)base).getRangeConstraints());
455 } else if (base instanceof UnsignedIntegerTypeDefinition && range.isEmpty()) {
456 range.addAll(((UnsignedIntegerTypeDefinition)base).getRangeConstraints());
457 } else if (base instanceof DecimalTypeDefinition && range.isEmpty()) {
458 range.addAll(((DecimalTypeDefinition)base).getRangeConstraints());
463 return new Restrictions() {
465 public List<RangeConstraint> getRangeConstraints() {
469 public List<PatternConstraint> getPatternConstraints() {
473 public List<LengthConstraint> getLengthConstraints() {
477 public boolean isEmpty() {
478 return range.isEmpty() && pattern.isEmpty() && length.isEmpty();