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.ImmutableList;
12 import com.google.common.collect.ImmutableList.Builder;
13 import com.google.common.collect.Iterables;
14 import java.io.ByteArrayOutputStream;
15 import java.io.DataOutputStream;
16 import java.io.IOException;
17 import java.security.MessageDigest;
18 import java.security.NoSuchAlgorithmException;
19 import java.util.ArrayList;
20 import java.util.Collection;
21 import java.util.Collections;
22 import java.util.Comparator;
23 import java.util.Iterator;
24 import java.util.List;
25 import org.opendaylight.yangtools.sal.binding.model.api.AccessModifier;
26 import org.opendaylight.yangtools.sal.binding.model.api.Restrictions;
27 import org.opendaylight.yangtools.sal.binding.model.api.Type;
28 import org.opendaylight.yangtools.sal.binding.model.api.type.builder.GeneratedPropertyBuilder;
29 import org.opendaylight.yangtools.sal.binding.model.api.type.builder.GeneratedTypeBuilderBase;
30 import org.opendaylight.yangtools.sal.binding.model.api.type.builder.MethodSignatureBuilder;
31 import org.opendaylight.yangtools.sal.binding.model.api.type.builder.TypeMemberBuilder;
32 import org.opendaylight.yangtools.yang.binding.BindingMapping;
33 import org.opendaylight.yangtools.yang.common.QName;
34 import org.opendaylight.yangtools.yang.common.QNameModule;
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.BinaryTypeDefinition;
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.StringTypeDefinition;
45 import org.opendaylight.yangtools.yang.model.api.type.UnsignedIntegerTypeDefinition;
46 import org.opendaylight.yangtools.yang.model.util.type.BaseTypes;
47 import org.opendaylight.yangtools.yang.model.util.type.DecimalTypeBuilder;
50 * Contains the methods for converting strings to valid JAVA language strings
51 * (package names, class names, attribute names) and to valid javadoc comments.
53 * @deprecated Use {@link org.opendaylight.mdsal.binding.generator.util.BindingGeneratorUtil} instead.
56 public final class BindingGeneratorUtil {
59 * Impossible to instantiate this class. All of the methods or attributes
62 private BindingGeneratorUtil() {
66 * Pre-compiled replacement pattern.
68 private static final CharMatcher DOT_MATCHER = CharMatcher.is('.');
69 private static final CharMatcher DASH_COLON_MATCHER = CharMatcher.anyOf("-:");
70 private static final CharMatcher GT_MATCHER = CharMatcher.is('>');
71 private static final CharMatcher LT_MATCHER = CharMatcher.is('<');
73 private static final Restrictions EMPTY_RESTRICTIONS = new Restrictions() {
75 public List<LengthConstraint> getLengthConstraints() {
76 return Collections.emptyList();
80 public List<PatternConstraint> getPatternConstraints() {
81 return Collections.emptyList();
85 public List<RangeConstraint> getRangeConstraints() {
86 return Collections.emptyList();
90 public boolean isEmpty() {
95 private static final Comparator<TypeMemberBuilder<?>> SUID_MEMBER_COMPARATOR =
96 (o1, o2) -> o1.getName().compareTo(o2.getName());
98 private static final Comparator<Type> SUID_NAME_COMPARATOR =
99 (o1, o2) -> 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());
144 * Creates package name from specified <code>basePackageName</code> (package
145 * name for module) and <code>schemaPath</code>.
147 * Resulting package name is concatenation of <code>basePackageName</code>
148 * and all local names of YANG nodes which are parents of some node for
149 * which <code>schemaPath</code> is specified.
151 * @param basePackageName
152 * string with package name of the module, MUST be normalized,
153 * otherwise this method may return an invalid string.
155 * list of names of YANG nodes which are parents of some node +
157 * @return string with valid JAVA package name
158 * @throws NullPointerException if any of the arguments are null
160 public static String packageNameForGeneratedType(final String basePackageName, final SchemaPath schemaPath) {
161 final int size = Iterables.size(schemaPath.getPathTowardsRoot()) - 1;
163 return basePackageName;
166 return generateNormalizedPackageName(basePackageName, schemaPath.getPathFromRoot(), size);
170 * Creates package name from specified <code>basePackageName</code> (package
171 * name for module) and <code>schemaPath</code> which crosses an augmentation.
173 * Resulting package name is concatenation of <code>basePackageName</code>
174 * and all local names of YANG nodes which are parents of some node for
175 * which <code>schemaPath</code> is specified.
177 * @param basePackageName
178 * string with package name of the module, MUST be normalized,
179 * otherwise this method may return an invalid string.
181 * list of names of YANG nodes which are parents of some node +
183 * @return string with valid JAVA package name
184 * @throws NullPointerException if any of the arguments are null
186 public static String packageNameForAugmentedGeneratedType(final String basePackageName, final SchemaPath schemaPath) {
187 final int size = Iterables.size(schemaPath.getPathTowardsRoot());
189 return basePackageName;
192 return generateNormalizedPackageName(basePackageName, schemaPath.getPathFromRoot(), size);
195 private static String generateNormalizedPackageName(final String base, final Iterable<QName> path, final int size) {
196 final StringBuilder builder = new StringBuilder(base);
197 final Iterator<QName> iterator = path.iterator();
198 for (int i = 0; i < size; ++i) {
200 String nodeLocalName = iterator.next().getLocalName();
201 // FIXME: Collon ":" is invalid in node local name as per RFC6020, identifier statement.
202 builder.append(DASH_COLON_MATCHER.replaceFrom(nodeLocalName, '.'));
204 return BindingMapping.normalizePackageName(builder.toString());
208 * Creates package name from specified <code>basePackageName</code> (package
209 * name for module) and <code>schemaPath</code>.
211 * Resulting package name is concatenation of <code>basePackageName</code>
212 * and all local names of YANG nodes which are parents of some node for
213 * which <code>schemaPath</code> is specified.
215 * @param basePackageName
216 * string with package name of the module
218 * list of names of YANG nodes which are parents of some node +
220 * @param isUsesAugment
221 * boolean true if using augment
222 * @return string with valid JAVA package name
224 * @deprecated Use {@link #packageNameForGeneratedType(String, SchemaPath)} or
225 * {@link #packageNameForAugmentedGeneratedType(String, SchemaPath)} instead.
228 public static String packageNameForGeneratedType(final String basePackageName, final SchemaPath schemaPath,
229 final boolean isUsesAugment) {
230 if (basePackageName == null) {
231 throw new IllegalArgumentException("Base Package Name cannot be NULL!");
233 if (schemaPath == null) {
234 throw new IllegalArgumentException("Schema Path cannot be NULL!");
237 final Iterable<QName> iterable = schemaPath.getPathFromRoot();
238 final int size = Iterables.size(iterable);
239 final int traversalSteps;
241 traversalSteps = size;
243 traversalSteps = size - 1;
246 if (traversalSteps == 0) {
247 return BindingMapping.normalizePackageName(basePackageName);
250 return generateNormalizedPackageName(basePackageName, iterable, traversalSteps);
254 * Generates the package name for type definition from
255 * <code>typeDefinition</code> and <code>basePackageName</code>.
257 * @param basePackageName
258 * string with the package name of the module
259 * @param typeDefinition
260 * type definition for which the package name will be generated *
261 * @return string with valid JAVA package name
262 * @throws IllegalArgumentException
264 * <li>if <code>basePackageName</code> equals <code>null</code></li>
265 * <li>if <code>typeDefinition</code> equals <code>null</code></li>
267 * @deprecated This method ignores typeDefinition argument and its result is only
268 * <code>BindingMapping.normalizePackageName(basePackageName)</code>.
269 * Aside from tests, there is not a single user in OpenDaylight codebase,
270 * hence it can be considered buggy and defunct. It is scheduled for removal
274 public static String packageNameForTypeDefinition(final String basePackageName,
275 final TypeDefinition<?> typeDefinition) {
276 if (basePackageName == null) {
277 throw new IllegalArgumentException("Base Package Name cannot be NULL!");
279 if (typeDefinition == null) {
280 throw new IllegalArgumentException("Type Definition reference cannot be NULL!");
283 return BindingMapping.normalizePackageName(basePackageName);
287 * Converts <code>token</code> to string which is in accordance with best
288 * practices for JAVA class names.
291 * string which contains characters which should be converted to
293 * @return string which is in accordance with best practices for JAVA class
296 * @deprecated Use {@link BindingMapping#getClassName(QName)} instead.
299 public static String parseToClassName(final String token) {
300 return parseToCamelCase(token, true);
304 * Converts <code>token</code> to string which is in accordance with best
305 * practices for JAVA parameter names.
308 * string which contains characters which should be converted to
309 * JAVA parameter name
310 * @return string which is in accordance with best practices for JAVA
313 * @deprecated Use {@link BindingMapping#getPropertyName(String)} instead.
315 @Deprecated public static String parseToValidParamName(final String token) {
316 return resolveJavaReservedWordEquivalency(parseToCamelCase(token, false));
321 * Converts string <code>token</code> to the cammel case format.
324 * string which should be converted to the cammel case format
326 * boolean value which says whether the first character of the
327 * <code>token</code> should|shuldn't be uppercased
328 * @return string in the cammel case format
329 * @throws IllegalArgumentException
331 * <li>if <code>token</code> without white spaces is empty</li>
332 * <li>if <code>token</code> equals null</li>
335 private static String parseToCamelCase(final String token, final boolean uppercase) {
337 throw new IllegalArgumentException("Name can not be null");
340 String correctStr = DOT_MATCHER.removeFrom(token.trim());
341 if (correctStr.isEmpty()) {
342 throw new IllegalArgumentException("Name can not be empty");
345 correctStr = replaceWithCamelCase(correctStr, ' ');
346 correctStr = replaceWithCamelCase(correctStr, '-');
347 correctStr = replaceWithCamelCase(correctStr, '_');
349 char firstChar = correctStr.charAt(0);
350 firstChar = uppercase ? Character.toUpperCase(firstChar) : Character.toLowerCase(firstChar);
352 if (firstChar >= '0' && firstChar <= '9') {
353 return '_' + correctStr;
355 return firstChar + correctStr.substring(1);
360 * Replaces all the occurrences of the <code>removalChar</code> in the
361 * <code>text</code> with empty string and converts following character to
365 * string with source text which should be converted
367 * character which is sought in the <code>text</code>
368 * @return string which doesn't contain <code>removalChar</code> and has
369 * following characters converted to upper case
370 * @throws IllegalArgumentException
371 * if the length of the returning string has length 0
373 private static String replaceWithCamelCase(final String text, final char removalChar) {
374 int toBeRemovedPos = text.indexOf(removalChar);
375 if (toBeRemovedPos == -1) {
379 StringBuilder sb = new StringBuilder(text);
380 String toBeRemoved = String.valueOf(removalChar);
382 sb.replace(toBeRemovedPos, toBeRemovedPos + 1, "");
383 // check if 'toBeRemoved' character is not the only character in
385 if (sb.length() == 0) {
386 throw new IllegalArgumentException("The resulting string can not be empty");
388 char replacement = Character.toUpperCase(sb.charAt(toBeRemovedPos));
389 sb.setCharAt(toBeRemovedPos, replacement);
390 toBeRemovedPos = sb.indexOf(toBeRemoved);
391 } while (toBeRemovedPos != -1);
393 return sb.toString();
396 private static <T> Iterable<T> sortedCollection(final Comparator<? super T> comparator, final Collection<T> input) {
397 if (input.size() > 1) {
398 final List<T> ret = new ArrayList<>(input);
399 Collections.sort(ret, comparator);
406 private static final ThreadLocal<MessageDigest> SHA1_MD = new ThreadLocal<MessageDigest>() {
408 protected MessageDigest initialValue() {
410 return MessageDigest.getInstance("SHA");
411 } catch (NoSuchAlgorithmException e) {
412 throw new IllegalStateException("Failed to get a SHA digest provider", e);
417 public static long computeDefaultSUID(final GeneratedTypeBuilderBase<?> to) {
418 final ByteArrayOutputStream bout = new ByteArrayOutputStream();
419 try (final DataOutputStream dout = new DataOutputStream(bout)) {
420 dout.writeUTF(to.getName());
421 dout.writeInt(to.isAbstract() ? 3 : 7);
423 for (Type ifc : sortedCollection(SUID_NAME_COMPARATOR, to.getImplementsTypes())) {
424 dout.writeUTF(ifc.getFullyQualifiedName());
427 for (GeneratedPropertyBuilder gp : sortedCollection(SUID_MEMBER_COMPARATOR, to.getProperties())) {
428 dout.writeUTF(gp.getName());
431 for (MethodSignatureBuilder m : sortedCollection(SUID_MEMBER_COMPARATOR, to.getMethodDefinitions())) {
432 if (!(m.getAccessModifier().equals(AccessModifier.PRIVATE))) {
433 dout.writeUTF(m.getName());
434 dout.write(m.getAccessModifier().ordinal());
439 } catch (IOException e) {
440 throw new IllegalStateException("Failed to hash object " + to, e);
443 final byte[] hashBytes = SHA1_MD.get().digest(bout.toByteArray());
445 for (int i = Math.min(hashBytes.length, 8) - 1; i >= 0; i--) {
446 hash = (hash << 8) | (hashBytes[i] & 0xFF);
451 private static <T> List<T> currentOrEmpty(final List<T> current, final List<T> base) {
452 return current.equals(base) ? ImmutableList.<T>of() : current;
455 private static boolean containsConstraint(final StringTypeDefinition type, final PatternConstraint constraint) {
456 for (StringTypeDefinition wlk = type; wlk != null; wlk = wlk.getBaseType()) {
457 if (wlk.getPatternConstraints().contains(constraint)) {
465 private static List<PatternConstraint> uniquePatterns(final StringTypeDefinition type) {
466 final List<PatternConstraint> constraints = type.getPatternConstraints();
467 if (constraints.isEmpty()) {
471 final Builder<PatternConstraint> builder = ImmutableList.builder();
472 boolean filtered = false;
473 for (PatternConstraint c : constraints) {
474 if (containsConstraint(type.getBaseType(), c)) {
481 return filtered ? builder.build() : constraints;
484 public static Restrictions getRestrictions(final TypeDefinition<?> type) {
485 // Old parser generated types which actually contained based restrictions, but our code deals with that when
486 // binding to core Java types. Hence we'll emit empty restrictions for base types.
487 if (type == null || type.getBaseType() == null) {
488 // Handling of decimal64 has changed in the new parser. It contains range restrictions applied to the type
489 // directly, without an extended type. We need to capture such constraints. In order to retain behavior we
490 // need to analyze the new semantics and see if the constraints have been overridden. To do that we
491 // instantiate a temporary unconstrained type and compare them.
493 // FIXME: looking at the generated code it looks as though we need to pass the restrictions without
495 if (type instanceof DecimalTypeDefinition) {
496 final DecimalTypeDefinition decimal = (DecimalTypeDefinition) type;
497 final DecimalTypeBuilder tmpBuilder = BaseTypes.decimalTypeBuilder(decimal.getPath());
498 tmpBuilder.setFractionDigits(decimal.getFractionDigits());
499 final DecimalTypeDefinition tmp = tmpBuilder.build();
501 if (!tmp.getRangeConstraints().equals(decimal.getRangeConstraints())) {
502 return new Restrictions() {
504 public boolean isEmpty() {
509 public List<RangeConstraint> getRangeConstraints() {
510 return decimal.getRangeConstraints();
514 public List<PatternConstraint> getPatternConstraints() {
515 return ImmutableList.of();
519 public List<LengthConstraint> getLengthConstraints() {
520 return ImmutableList.of();
526 return EMPTY_RESTRICTIONS;
529 final List<LengthConstraint> length;
530 final List<PatternConstraint> pattern;
531 final List<RangeConstraint> range;
534 * Take care of extended types.
536 * Other types which support constraints are check afterwards. There is a slight twist with them, as returned
537 * constraints are the effective view, e.g. they are inherited from base type. Since the constraint is already
538 * enforced by the base type, we want to skip them and not perform duplicate checks.
540 * We end up emitting ConcreteType instances for YANG base types, which leads to their constraints not being
541 * enforced (most notably decimal64). Therefore we need to make sure we do not strip the next-to-last
544 * FIXME: this probably not the best solution and needs further analysis.
546 if (type instanceof BinaryTypeDefinition) {
547 final BinaryTypeDefinition binary = (BinaryTypeDefinition)type;
548 final BinaryTypeDefinition base = binary.getBaseType();
549 if (base != null && base.getBaseType() != null) {
550 length = currentOrEmpty(binary.getLengthConstraints(), base.getLengthConstraints());
552 length = binary.getLengthConstraints();
555 pattern = ImmutableList.of();
556 range = ImmutableList.of();
557 } else if (type instanceof DecimalTypeDefinition) {
558 length = ImmutableList.of();
559 pattern = ImmutableList.of();
561 final DecimalTypeDefinition decimal = (DecimalTypeDefinition)type;
562 final DecimalTypeDefinition base = decimal.getBaseType();
563 if (base != null && base.getBaseType() != null) {
564 range = currentOrEmpty(decimal.getRangeConstraints(), base.getRangeConstraints());
566 range = decimal.getRangeConstraints();
568 } else if (type instanceof IntegerTypeDefinition) {
569 length = ImmutableList.of();
570 pattern = ImmutableList.of();
572 final IntegerTypeDefinition integer = (IntegerTypeDefinition)type;
573 final IntegerTypeDefinition base = integer.getBaseType();
574 if (base != null && base.getBaseType() != null) {
575 range = currentOrEmpty(integer.getRangeConstraints(), base.getRangeConstraints());
577 range = integer.getRangeConstraints();
579 } else if (type instanceof StringTypeDefinition) {
580 final StringTypeDefinition string = (StringTypeDefinition)type;
581 final StringTypeDefinition base = string.getBaseType();
582 if (base != null && base.getBaseType() != null) {
583 length = currentOrEmpty(string.getLengthConstraints(), base.getLengthConstraints());
585 length = string.getLengthConstraints();
588 pattern = uniquePatterns(string);
589 range = ImmutableList.of();
590 } else if (type instanceof UnsignedIntegerTypeDefinition) {
591 length = ImmutableList.of();
592 pattern = ImmutableList.of();
594 final UnsignedIntegerTypeDefinition unsigned = (UnsignedIntegerTypeDefinition)type;
595 final UnsignedIntegerTypeDefinition base = unsigned.getBaseType();
596 if (base != null && base.getBaseType() != null) {
597 range = currentOrEmpty(unsigned.getRangeConstraints(), base.getRangeConstraints());
599 range = unsigned.getRangeConstraints();
602 length = ImmutableList.of();
603 pattern = ImmutableList.of();
604 range = ImmutableList.of();
607 // Now, this may have ended up being empty, too...
608 if (length.isEmpty() && pattern.isEmpty() && range.isEmpty()) {
609 return EMPTY_RESTRICTIONS;
612 // Nope, not empty allocate a holder
613 return new Restrictions() {
615 public List<RangeConstraint> getRangeConstraints() {
619 public List<PatternConstraint> getPatternConstraints() {
623 public List<LengthConstraint> getLengthConstraints() {
627 public boolean isEmpty() {
634 * Encodes angle brackets in yang statement description
635 * @param description description of a yang statement which is used to generate javadoc comments
636 * @return string with encoded angle brackets
638 public static String encodeAngleBrackets(String description) {
639 if (description != null) {
640 description = LT_MATCHER.replaceFrom(description, "<");
641 description = GT_MATCHER.replaceFrom(description, ">");