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.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;
48 * Contains the methods for converting strings to valid JAVA language strings
49 * (package names, class names, attribute names).
53 public final class BindingGeneratorUtil {
55 private static final ThreadLocal<SimpleDateFormat> DATE_FORMAT = new ThreadLocal<SimpleDateFormat>() {
58 protected SimpleDateFormat initialValue() {
59 return new SimpleDateFormat("yyMMdd");
63 public void set(final SimpleDateFormat value) {
64 throw new UnsupportedOperationException();
69 * Impossible to instantiate this class. All of the methods or attributes
72 private BindingGeneratorUtil() {
76 * Pre-compiled replacement pattern.
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('.');
83 * Converts string <code>packageName</code> to valid JAVA package name.
85 * If some words of package name are digits of JAVA reserved words they are
86 * prefixed with underscore character.
89 * string which contains words separated by point.
90 * @return package name which contains words separated by point.
92 private static String validateJavaPackage(final String packageName) {
93 if (packageName == null) {
97 final StringBuilder builder = new StringBuilder();
100 for (String p : DOT.split(packageName.toLowerCase())) {
107 if (Character.isDigit(p.charAt(0)) || BindingMapping.JAVA_RESERVED_WORDS.contains(p)) {
113 return builder.toString();
117 * Converts <code>parameterName</code> to valid JAVA parameter name.
119 * If the <code>parameterName</code> is one of the JAVA reserved words then
120 * it is prefixed with underscore character.
122 * @param parameterName
123 * string with the parameter name
124 * @return string with the admissible parameter name
126 public static String resolveJavaReservedWordEquivalency(final String parameterName) {
127 if (parameterName != null && BindingMapping.JAVA_RESERVED_WORDS.contains(parameterName)) {
128 return "_" + parameterName;
130 return parameterName;
134 * Converts module name to valid JAVA package name.
136 * The package name consists of:
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>
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
152 public static String moduleNamespaceToPackageName(final Module module) {
153 final StringBuilder packageNameBuilder = new StringBuilder();
155 if (module.getRevision() == null) {
156 throw new IllegalArgumentException("Module " + module.getName() + " does not specify revision date!");
158 packageNameBuilder.append(BindingMapping.PACKAGE_PREFIX);
159 packageNameBuilder.append('.');
161 String namespace = module.getNamespace().toString();
162 namespace = COLON_SLASH_SLASH.matcher(namespace).replaceAll(QUOTED_DOT);
164 final char[] chars = namespace.toCharArray();
165 for (int i = 0; i < chars.length; ++i) {
183 packageNameBuilder.append(chars);
184 packageNameBuilder.append(".rev");
185 packageNameBuilder.append(DATE_FORMAT.get().format(module.getRevision()));
187 return validateJavaPackage(packageNameBuilder.toString());
190 public static String packageNameForGeneratedType(final String basePackageName, final SchemaPath schemaPath) {
191 return packageNameForGeneratedType(basePackageName, schemaPath, false);
195 * Creates package name from specified <code>basePackageName</code> (package
196 * name for module) and <code>schemaPath</code>.
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.
202 * @param basePackageName
203 * string with package name of the module
205 * list of names of YANG nodes which are parents of some node +
207 * @return string with valid JAVA package name
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!");
214 if (schemaPath == null) {
215 throw new IllegalArgumentException("Schema Path cannot be NULL!");
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;
225 traversalSteps = size;
227 traversalSteps = size - 1;
229 for (int i = 0; i < traversalSteps; ++i) {
231 String nodeLocalName = iterator.next().getLocalName();
233 nodeLocalName = nodeLocalName.replace(':', '.');
234 nodeLocalName = nodeLocalName.replace('-', '.');
235 builder.append(nodeLocalName);
237 return validateJavaPackage(builder.toString());
241 * Generates the package name for type definition from
242 * <code>typeDefinition</code> and <code>basePackageName</code>.
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
251 * <li>if <code>basePackageName</code> equals <code>null</code></li>
252 * <li>if <code>typeDefinition</code> equals <code>null</code></li>
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!");
260 if (typeDefinition == null) {
261 throw new IllegalArgumentException("Type Definition reference cannot be NULL!");
264 final StringBuilder builder = new StringBuilder();
265 builder.append(basePackageName);
266 return validateJavaPackage(builder.toString());
270 * Converts <code>token</code> to string which is in accordance with best
271 * practices for JAVA class names.
274 * string which contains characters which should be converted to
276 * @return string which is in accordance with best practices for JAVA class
279 * @deprecated Use {@link BindingMapping#getClassName(QName)} instead.
282 public static String parseToClassName(final String token) {
283 return parseToCamelCase(token, true);
287 * Converts <code>token</code> to string which is in accordance with best
288 * practices for JAVA parameter names.
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
296 public static String parseToValidParamName(final String token) {
297 return resolveJavaReservedWordEquivalency(parseToCamelCase(token, false));
302 * Converts string <code>token</code> to the cammel case format.
305 * string which should be converted to the cammel case format
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
312 * <li>if <code>token</code> without white spaces is empty</li>
313 * <li>if <code>token</code> equals null</li>
317 private static String parseToCamelCase(final String token, final boolean uppercase) {
319 throw new IllegalArgumentException("Name can not be null");
322 String correctStr = token.trim();
323 correctStr = correctStr.replace(".", "");
325 if (correctStr.isEmpty()) {
326 throw new IllegalArgumentException("Name can not be emty");
329 correctStr = replaceWithCamelCase(correctStr, ' ');
330 correctStr = replaceWithCamelCase(correctStr, '-');
331 correctStr = replaceWithCamelCase(correctStr, '_');
333 char firstChar = correctStr.charAt(0);
334 firstChar = uppercase ? Character.toUpperCase(firstChar) : Character.toLowerCase(firstChar);
336 if (firstChar >= '0' && firstChar <= '9') {
337 return correctStr = '_' + correctStr;
339 return correctStr = firstChar + correctStr.substring(1);
344 * Replaces all the occurances of the <code>removalChar</code> in the
345 * <code>text</code> with empty string and converts following character to
349 * string with source text which should be converted
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
357 private static String replaceWithCamelCase(final String text, final char removalChar) {
358 int toBeRemovedPos = text.indexOf(removalChar);
359 if (toBeRemovedPos == -1) {
363 StringBuilder sb = new StringBuilder(text);
364 String toBeRemoved = String.valueOf(removalChar);
366 sb.replace(toBeRemovedPos, toBeRemovedPos + 1, "");
367 // check if 'toBeRemoved' character is not the only character in
369 if (sb.length() == 0) {
370 throw new IllegalArgumentException("The resulting string can not be empty");
372 char replacement = Character.toUpperCase(sb.charAt(toBeRemovedPos));
373 sb.setCharAt(toBeRemovedPos, replacement);
374 toBeRemovedPos = sb.indexOf(toBeRemoved);
375 } while (toBeRemovedPos != -1);
377 return sb.toString();
381 * Add {@link Serializable} to implemented interfaces of this TO. Also
382 * compute and add serialVersionUID property.
385 * transfer object which needs to be serializable
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)));
394 public static long computeDefaultSUID(final GeneratedTOBuilderImpl to) {
396 ByteArrayOutputStream bout = new ByteArrayOutputStream();
397 DataOutputStream dout = new DataOutputStream(bout);
399 dout.writeUTF(to.getName());
400 dout.writeInt(to.isAbstract() ? 3 : 7);
402 List<Type> impl = to.getImplementsTypes();
403 Collections.sort(impl, new Comparator<Type>() {
405 public int compare(final Type o1, final Type o2) {
406 return o1.getFullyQualifiedName().compareTo(o2.getFullyQualifiedName());
409 for (Type ifc : impl) {
410 dout.writeUTF(ifc.getFullyQualifiedName());
413 Comparator<TypeMemberBuilder<?>> comparator = new Comparator<TypeMemberBuilder<?>>() {
415 public int compare(final TypeMemberBuilder<?> o1, final TypeMemberBuilder<?> o2) {
416 return o1.getName().compareTo(o2.getName());
420 List<GeneratedPropertyBuilder> props = to.getProperties();
421 Collections.sort(props, comparator);
422 for (GeneratedPropertyBuilder gp : props) {
423 dout.writeUTF(gp.getName());
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());
437 MessageDigest md = MessageDigest.getInstance("SHA");
438 byte[] hashBytes = md.digest(bout.toByteArray());
440 for (int i = Math.min(hashBytes.length, 8) - 1; i >= 0; i--) {
441 hash = (hash << 8) | (hashBytes[i] & 0xFF);
444 } catch (IOException ex) {
445 throw new InternalError();
446 } catch (NoSuchAlgorithmException ex) {
447 throw new SecurityException(ex.getMessage());
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<>();
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());
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());
473 return new Restrictions() {
475 public List<RangeConstraint> getRangeConstraints() {
479 public List<PatternConstraint> getPatternConstraints() {
483 public List<LengthConstraint> getLengthConstraints() {
487 public boolean isEmpty() {
488 return range.isEmpty() && pattern.isEmpty() && length.isEmpty();