Bug 6859: Cleanup package names for mdsal-binding-generator-util module
[mdsal.git] / binding / mdsal-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.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;
48
49 /**
50  * Contains the methods for converting strings to valid JAVA language strings
51  * (package names, class names, attribute names) and to valid javadoc comments.
52  *
53  * @deprecated Use {@link org.opendaylight.mdsal.binding.generator.util.BindingGeneratorUtil} instead.
54  */
55 @Deprecated
56 public final class BindingGeneratorUtil {
57
58     /**
59      * Impossible to instantiate this class. All of the methods or attributes
60      * are static.
61      */
62     private BindingGeneratorUtil() {
63     }
64
65     /**
66      * Pre-compiled replacement pattern.
67      */
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('<');
72
73     private static final Restrictions EMPTY_RESTRICTIONS = new Restrictions() {
74         @Override
75         public List<LengthConstraint> getLengthConstraints() {
76             return Collections.emptyList();
77         }
78
79         @Override
80         public List<PatternConstraint> getPatternConstraints() {
81             return Collections.emptyList();
82         }
83
84         @Override
85         public List<RangeConstraint> getRangeConstraints() {
86             return Collections.emptyList();
87         }
88
89         @Override
90         public boolean isEmpty() {
91             return true;
92         }
93     };
94
95     private static final Comparator<TypeMemberBuilder<?>> SUID_MEMBER_COMPARATOR =
96         (o1, o2) -> o1.getName().compareTo(o2.getName());
97
98     private static final Comparator<Type> SUID_NAME_COMPARATOR =
99         (o1, o2) -> o1.getFullyQualifiedName().compareTo(o2.getFullyQualifiedName());
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     /**
144      * Creates package name from specified <code>basePackageName</code> (package
145      * name for module) and <code>schemaPath</code>.
146      *
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.
150      *
151      * @param basePackageName
152      *            string with package name of the module, MUST be normalized,
153      *            otherwise this method may return an invalid string.
154      * @param schemaPath
155      *            list of names of YANG nodes which are parents of some node +
156      *            name of this node
157      * @return string with valid JAVA package name
158      * @throws NullPointerException if any of the arguments are null
159      */
160     public static String packageNameForGeneratedType(final String basePackageName, final SchemaPath schemaPath) {
161         final int size = Iterables.size(schemaPath.getPathTowardsRoot()) - 1;
162         if (size <= 0) {
163             return basePackageName;
164         }
165
166         return generateNormalizedPackageName(basePackageName, schemaPath.getPathFromRoot(), size);
167     }
168
169     /**
170      * Creates package name from specified <code>basePackageName</code> (package
171      * name for module) and <code>schemaPath</code> which crosses an augmentation.
172      *
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.
176      *
177      * @param basePackageName
178      *            string with package name of the module, MUST be normalized,
179      *            otherwise this method may return an invalid string.
180      * @param schemaPath
181      *            list of names of YANG nodes which are parents of some node +
182      *            name of this node
183      * @return string with valid JAVA package name
184      * @throws NullPointerException if any of the arguments are null
185      */
186     public static String packageNameForAugmentedGeneratedType(final String basePackageName, final SchemaPath schemaPath) {
187         final int size = Iterables.size(schemaPath.getPathTowardsRoot());
188         if (size == 0) {
189             return basePackageName;
190         }
191
192         return generateNormalizedPackageName(basePackageName, schemaPath.getPathFromRoot(), size);
193     }
194
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) {
199             builder.append('.');
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, '.'));
203         }
204         return BindingMapping.normalizePackageName(builder.toString());
205     }
206
207     /**
208      * Creates package name from specified <code>basePackageName</code> (package
209      * name for module) and <code>schemaPath</code>.
210      *
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.
214      *
215      * @param basePackageName
216      *            string with package name of the module
217      * @param schemaPath
218      *            list of names of YANG nodes which are parents of some node +
219      *            name of this node
220      * @param isUsesAugment
221      *            boolean true if using augment
222      * @return string with valid JAVA package name
223      *
224      * @deprecated Use {@link #packageNameForGeneratedType(String, SchemaPath)} or
225      *             {@link #packageNameForAugmentedGeneratedType(String, SchemaPath)} instead.
226      */
227     @Deprecated
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!");
232         }
233         if (schemaPath == null) {
234             throw new IllegalArgumentException("Schema Path cannot be NULL!");
235         }
236
237         final Iterable<QName> iterable = schemaPath.getPathFromRoot();
238         final int size = Iterables.size(iterable);
239         final int traversalSteps;
240         if (isUsesAugment) {
241             traversalSteps = size;
242         } else {
243             traversalSteps = size - 1;
244         }
245
246         if (traversalSteps == 0) {
247             return BindingMapping.normalizePackageName(basePackageName);
248         }
249
250         return generateNormalizedPackageName(basePackageName, iterable, traversalSteps);
251     }
252
253     /**
254      * Generates the package name for type definition from
255      * <code>typeDefinition</code> and <code>basePackageName</code>.
256      *
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
263      *             <ul>
264      *             <li>if <code>basePackageName</code> equals <code>null</code></li>
265      *             <li>if <code>typeDefinition</code> equals <code>null</code></li>
266      *             </ul>
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
271      *             in Boron release.
272      */
273     @Deprecated
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!");
278         }
279         if (typeDefinition == null) {
280             throw new IllegalArgumentException("Type Definition reference cannot be NULL!");
281         }
282
283         return BindingMapping.normalizePackageName(basePackageName);
284     }
285
286     /**
287      * Converts <code>token</code> to string which is in accordance with best
288      * practices for JAVA class names.
289      *
290      * @param token
291      *            string which contains characters which should be converted to
292      *            JAVA class name
293      * @return string which is in accordance with best practices for JAVA class
294      *         name.
295      *
296      * @deprecated Use {@link BindingMapping#getClassName(QName)} instead.
297      */
298     @Deprecated
299     public static String parseToClassName(final String token) {
300         return parseToCamelCase(token, true);
301     }
302
303     /**
304      * Converts <code>token</code> to string which is in accordance with best
305      * practices for JAVA parameter names.
306      *
307      * @param token
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
311      *         parameter name.
312      *
313      * @deprecated Use {@link BindingMapping#getPropertyName(String)} instead.
314      */
315     @Deprecated public static String parseToValidParamName(final String token) {
316         return resolveJavaReservedWordEquivalency(parseToCamelCase(token, false));
317     }
318
319     /**
320      *
321      * Converts string <code>token</code> to the cammel case format.
322      *
323      * @param token
324      *            string which should be converted to the cammel case format
325      * @param uppercase
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
330      *             <ul>
331      *             <li>if <code>token</code> without white spaces is empty</li>
332      *             <li>if <code>token</code> equals null</li>
333      *             </ul>
334      */
335     private static String parseToCamelCase(final String token, final boolean uppercase) {
336         if (token == null) {
337             throw new IllegalArgumentException("Name can not be null");
338         }
339
340         String correctStr = DOT_MATCHER.removeFrom(token.trim());
341         if (correctStr.isEmpty()) {
342             throw new IllegalArgumentException("Name can not be empty");
343         }
344
345         correctStr = replaceWithCamelCase(correctStr, ' ');
346         correctStr = replaceWithCamelCase(correctStr, '-');
347         correctStr = replaceWithCamelCase(correctStr, '_');
348
349         char firstChar = correctStr.charAt(0);
350         firstChar = uppercase ? Character.toUpperCase(firstChar) : Character.toLowerCase(firstChar);
351
352         if (firstChar >= '0' && firstChar <= '9') {
353             return '_' + correctStr;
354         } else {
355             return firstChar + correctStr.substring(1);
356         }
357     }
358
359     /**
360      * Replaces all the occurrences of the <code>removalChar</code> in the
361      * <code>text</code> with empty string and converts following character to
362      * upper case.
363      *
364      * @param text
365      *            string with source text which should be converted
366      * @param removalChar
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
372      */
373     private static String replaceWithCamelCase(final String text, final char removalChar) {
374         int toBeRemovedPos = text.indexOf(removalChar);
375         if (toBeRemovedPos == -1) {
376             return text;
377         }
378
379         StringBuilder sb = new StringBuilder(text);
380         String toBeRemoved = String.valueOf(removalChar);
381         do {
382             sb.replace(toBeRemovedPos, toBeRemovedPos + 1, "");
383             // check if 'toBeRemoved' character is not the only character in
384             // 'text'
385             if (sb.length() == 0) {
386                 throw new IllegalArgumentException("The resulting string can not be empty");
387             }
388             char replacement = Character.toUpperCase(sb.charAt(toBeRemovedPos));
389             sb.setCharAt(toBeRemovedPos, replacement);
390             toBeRemovedPos = sb.indexOf(toBeRemoved);
391         } while (toBeRemovedPos != -1);
392
393         return sb.toString();
394     }
395
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);
400             return ret;
401         } else {
402             return input;
403         }
404     }
405
406     private static final ThreadLocal<MessageDigest> SHA1_MD = new ThreadLocal<MessageDigest>() {
407         @Override
408         protected MessageDigest initialValue() {
409             try {
410                 return MessageDigest.getInstance("SHA");
411             } catch (NoSuchAlgorithmException e) {
412                 throw new IllegalStateException("Failed to get a SHA digest provider", e);
413             }
414         }
415     };
416
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);
422
423             for (Type ifc : sortedCollection(SUID_NAME_COMPARATOR, to.getImplementsTypes())) {
424                 dout.writeUTF(ifc.getFullyQualifiedName());
425             }
426
427             for (GeneratedPropertyBuilder gp : sortedCollection(SUID_MEMBER_COMPARATOR, to.getProperties())) {
428                 dout.writeUTF(gp.getName());
429             }
430
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());
435                 }
436             }
437
438             dout.flush();
439         } catch (IOException e) {
440             throw new IllegalStateException("Failed to hash object " + to, e);
441         }
442
443         final byte[] hashBytes = SHA1_MD.get().digest(bout.toByteArray());
444         long hash = 0;
445         for (int i = Math.min(hashBytes.length, 8) - 1; i >= 0; i--) {
446             hash = (hash << 8) | (hashBytes[i] & 0xFF);
447         }
448         return hash;
449     }
450
451     private static <T> List<T> currentOrEmpty(final List<T> current, final List<T> base) {
452         return current.equals(base) ? ImmutableList.<T>of() : current;
453     }
454
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)) {
458                 return true;
459             }
460         }
461
462         return false;
463     }
464
465     private static List<PatternConstraint> uniquePatterns(final StringTypeDefinition type) {
466         final List<PatternConstraint> constraints = type.getPatternConstraints();
467         if (constraints.isEmpty()) {
468             return constraints;
469         }
470
471         final Builder<PatternConstraint> builder = ImmutableList.builder();
472         boolean filtered = false;
473         for (PatternConstraint c : constraints) {
474             if (containsConstraint(type.getBaseType(), c)) {
475                 filtered = true;
476             } else {
477                 builder.add(c);
478             }
479         }
480
481         return filtered ? builder.build() : constraints;
482     }
483
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.
492             //
493             // FIXME: looking at the generated code it looks as though we need to pass the restrictions without
494             //        comparison
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();
500
501                 if (!tmp.getRangeConstraints().equals(decimal.getRangeConstraints())) {
502                     return new Restrictions() {
503                         @Override
504                         public boolean isEmpty() {
505                             return false;
506                         }
507
508                         @Override
509                         public List<RangeConstraint> getRangeConstraints() {
510                             return decimal.getRangeConstraints();
511                         }
512
513                         @Override
514                         public List<PatternConstraint> getPatternConstraints() {
515                             return ImmutableList.of();
516                         }
517
518                         @Override
519                         public List<LengthConstraint> getLengthConstraints() {
520                             return ImmutableList.of();
521                         }
522                     };
523                 }
524             }
525
526             return EMPTY_RESTRICTIONS;
527         }
528
529         final List<LengthConstraint> length;
530         final List<PatternConstraint> pattern;
531         final List<RangeConstraint> range;
532
533         /*
534          * Take care of extended types.
535          *
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.
539          *
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
542          * restrictions.
543          *
544          * FIXME: this probably not the best solution and needs further analysis.
545          */
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());
551             } else {
552                 length = binary.getLengthConstraints();
553             }
554
555             pattern = ImmutableList.of();
556             range = ImmutableList.of();
557         } else if (type instanceof DecimalTypeDefinition) {
558             length = ImmutableList.of();
559             pattern = ImmutableList.of();
560
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());
565             } else {
566                 range = decimal.getRangeConstraints();
567             }
568         } else if (type instanceof IntegerTypeDefinition) {
569             length = ImmutableList.of();
570             pattern = ImmutableList.of();
571
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());
576             } else {
577                 range = integer.getRangeConstraints();
578             }
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());
584             } else {
585                 length = string.getLengthConstraints();
586             }
587
588             pattern = uniquePatterns(string);
589             range = ImmutableList.of();
590         } else if (type instanceof UnsignedIntegerTypeDefinition) {
591             length = ImmutableList.of();
592             pattern = ImmutableList.of();
593
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());
598             } else {
599                 range = unsigned.getRangeConstraints();
600             }
601         } else {
602             length = ImmutableList.of();
603             pattern = ImmutableList.of();
604             range = ImmutableList.of();
605         }
606
607         // Now, this may have ended up being empty, too...
608         if (length.isEmpty() && pattern.isEmpty() && range.isEmpty()) {
609             return EMPTY_RESTRICTIONS;
610         }
611
612         // Nope, not empty allocate a holder
613         return new Restrictions() {
614             @Override
615             public List<RangeConstraint> getRangeConstraints() {
616                 return range;
617             }
618             @Override
619             public List<PatternConstraint> getPatternConstraints() {
620                 return pattern;
621             }
622             @Override
623             public List<LengthConstraint> getLengthConstraints() {
624                 return length;
625             }
626             @Override
627             public boolean isEmpty() {
628                 return false;
629             }
630         };
631     }
632
633     /**
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
637      */
638     public static String encodeAngleBrackets(String description) {
639         if (description != null) {
640             description = LT_MATCHER.replaceFrom(description, "&lt;");
641             description = GT_MATCHER.replaceFrom(description, "&gt;");
642         }
643         return description;
644     }
645 }