Bug 7759 - TEST - Getter of BA object fails to construct class instance
[mdsal.git] / binding / mdsal-binding-generator-util / src / main / java / org / opendaylight / mdsal / 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.mdsal.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.mdsal.binding.model.api.AccessModifier;
26 import org.opendaylight.mdsal.binding.model.api.Restrictions;
27 import org.opendaylight.mdsal.binding.model.api.Type;
28 import org.opendaylight.mdsal.binding.model.api.type.builder.GeneratedPropertyBuilder;
29 import org.opendaylight.mdsal.binding.model.api.type.builder.GeneratedTypeBuilderBase;
30 import org.opendaylight.mdsal.binding.model.api.type.builder.MethodSignatureBuilder;
31 import org.opendaylight.mdsal.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 public final class BindingGeneratorUtil {
54
55     /**
56      * Impossible to instantiate this class. All of the methods or attributes
57      * are static.
58      */
59     private BindingGeneratorUtil() {
60     }
61
62     /**
63      * Pre-compiled replacement pattern.
64      */
65     private static final CharMatcher DOT_MATCHER = CharMatcher.is('.');
66     private static final CharMatcher DASH_COLON_MATCHER = CharMatcher.anyOf("-:");
67     private static final CharMatcher GT_MATCHER = CharMatcher.is('>');
68     private static final CharMatcher LT_MATCHER = CharMatcher.is('<');
69
70     private static final Restrictions EMPTY_RESTRICTIONS = new Restrictions() {
71         @Override
72         public List<LengthConstraint> getLengthConstraints() {
73             return Collections.emptyList();
74         }
75
76         @Override
77         public List<PatternConstraint> getPatternConstraints() {
78             return Collections.emptyList();
79         }
80
81         @Override
82         public List<RangeConstraint> getRangeConstraints() {
83             return Collections.emptyList();
84         }
85
86         @Override
87         public boolean isEmpty() {
88             return true;
89         }
90     };
91
92     private static final Comparator<TypeMemberBuilder<?>> SUID_MEMBER_COMPARATOR =
93         (o1, o2) -> o1.getName().compareTo(o2.getName());
94
95     private static final Comparator<Type> SUID_NAME_COMPARATOR =
96         (o1, o2) -> o1.getFullyQualifiedName().compareTo(o2.getFullyQualifiedName());
97
98     /**
99      * Converts <code>parameterName</code> to valid JAVA parameter name.
100      *
101      * If the <code>parameterName</code> is one of the JAVA reserved words then
102      * it is prefixed with underscore character.
103      *
104      * @param parameterName
105      *            string with the parameter name
106      * @return string with the admissible parameter name
107      */
108     public static String resolveJavaReservedWordEquivalency(final String parameterName) {
109         if ((parameterName != null) && BindingMapping.JAVA_RESERVED_WORDS.contains(parameterName)) {
110             return "_" + parameterName;
111         }
112         return parameterName;
113     }
114
115     /**
116      * Converts module name to valid JAVA package name.
117      *
118      * The package name consists of:
119      * <ul>
120      * <li>prefix - <i>org.opendaylight.yang.gen.v</i></li>
121      * <li>module YANG version - <i>org.opendaylight.yang.gen.v</i></li>
122      * <li>module namespace - invalid characters are replaced with dots</li>
123      * <li>revision prefix - <i>.rev</i></li>
124      * <li>revision - YYYYMMDD (MM and DD aren't spread to the whole length)</li>
125      * </ul>
126      *
127      * @param module
128      *            module which contains data about namespace and revision date
129      * @return string with the valid JAVA package name
130      * @throws IllegalArgumentException
131      *             if the revision date of the <code>module</code> equals
132      *             <code>null</code>
133      * @deprecated USe {@link BindingMapping#getRootPackageName(QNameModule)} with {@link Module#getQNameModule()}.
134      */
135     @Deprecated
136     public static String moduleNamespaceToPackageName(final Module module) {
137         return BindingMapping.getRootPackageName(module.getQNameModule());
138     }
139
140     /**
141      * Creates package name from specified <code>basePackageName</code> (package
142      * name for module) and <code>schemaPath</code>.
143      *
144      * Resulting package name is concatenation of <code>basePackageName</code>
145      * and all local names of YANG nodes which are parents of some node for
146      * which <code>schemaPath</code> is specified.
147      *
148      * @param basePackageName
149      *            string with package name of the module, MUST be normalized,
150      *            otherwise this method may return an invalid string.
151      * @param schemaPath
152      *            list of names of YANG nodes which are parents of some node +
153      *            name of this node
154      * @return string with valid JAVA package name
155      * @throws NullPointerException if any of the arguments are null
156      */
157     public static String packageNameForGeneratedType(final String basePackageName, final SchemaPath schemaPath) {
158         final int size = Iterables.size(schemaPath.getPathTowardsRoot()) - 1;
159         if (size <= 0) {
160             return basePackageName;
161         }
162
163         return generateNormalizedPackageName(basePackageName, schemaPath.getPathFromRoot(), size);
164     }
165
166     /**
167      * Creates package name from specified <code>basePackageName</code> (package
168      * name for module) and <code>schemaPath</code> which crosses an augmentation.
169      *
170      * Resulting package name is concatenation of <code>basePackageName</code>
171      * and all local names of YANG nodes which are parents of some node for
172      * which <code>schemaPath</code> is specified.
173      *
174      * @param basePackageName
175      *            string with package name of the module, MUST be normalized,
176      *            otherwise this method may return an invalid string.
177      * @param schemaPath
178      *            list of names of YANG nodes which are parents of some node +
179      *            name of this node
180      * @return string with valid JAVA package name
181      * @throws NullPointerException if any of the arguments are null
182      */
183     public static String packageNameForAugmentedGeneratedType(final String basePackageName, final SchemaPath schemaPath) {
184         final int size = Iterables.size(schemaPath.getPathTowardsRoot());
185         if (size == 0) {
186             return basePackageName;
187         }
188
189         return generateNormalizedPackageName(basePackageName, schemaPath.getPathFromRoot(), size);
190     }
191
192     private static String generateNormalizedPackageName(final String base, final Iterable<QName> path, final int size) {
193         final StringBuilder builder = new StringBuilder(base);
194         final Iterator<QName> iterator = path.iterator();
195         for (int i = 0; i < size; ++i) {
196             builder.append('.');
197             final String nodeLocalName = iterator.next().getLocalName();
198             // FIXME: Collon ":" is invalid in node local name as per RFC6020, identifier statement.
199             builder.append(DASH_COLON_MATCHER.replaceFrom(nodeLocalName, '.'));
200         }
201         return BindingMapping.normalizePackageName(builder.toString());
202     }
203
204     /**
205      * Creates package name from specified <code>basePackageName</code> (package
206      * name for module) and <code>schemaPath</code>.
207      *
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.
211      *
212      * @param basePackageName
213      *            string with package name of the module
214      * @param schemaPath
215      *            list of names of YANG nodes which are parents of some node +
216      *            name of this node
217      * @param isUsesAugment
218      *            boolean true if using augment
219      * @return string with valid JAVA package name
220      *
221      * @deprecated Use {@link #packageNameForGeneratedType(String, SchemaPath)} or
222      *             {@link #packageNameForAugmentedGeneratedType(String, SchemaPath)} instead.
223      */
224     @Deprecated
225     public static String packageNameForGeneratedType(final String basePackageName, final SchemaPath schemaPath,
226             final boolean isUsesAugment) {
227         if (basePackageName == null) {
228             throw new IllegalArgumentException("Base Package Name cannot be NULL!");
229         }
230         if (schemaPath == null) {
231             throw new IllegalArgumentException("Schema Path cannot be NULL!");
232         }
233
234         final Iterable<QName> iterable = schemaPath.getPathFromRoot();
235         final int size = Iterables.size(iterable);
236         final int traversalSteps;
237         if (isUsesAugment) {
238             traversalSteps = size;
239         } else {
240             traversalSteps = size - 1;
241         }
242
243         if (traversalSteps == 0) {
244             return BindingMapping.normalizePackageName(basePackageName);
245         }
246
247         return generateNormalizedPackageName(basePackageName, iterable, traversalSteps);
248     }
249
250     /**
251      * Generates the package name for type definition from
252      * <code>typeDefinition</code> and <code>basePackageName</code>.
253      *
254      * @param basePackageName
255      *            string with the package name of the module
256      * @param typeDefinition
257      *            type definition for which the package name will be generated *
258      * @return string with valid JAVA package name
259      * @throws IllegalArgumentException
260      *             <ul>
261      *             <li>if <code>basePackageName</code> equals <code>null</code></li>
262      *             <li>if <code>typeDefinition</code> equals <code>null</code></li>
263      *             </ul>
264      * @deprecated This method ignores typeDefinition argument and its result is only
265      *             <code>BindingMapping.normalizePackageName(basePackageName)</code>.
266      *             Aside from tests, there is not a single user in OpenDaylight codebase,
267      *             hence it can be considered buggy and defunct. It is scheduled for removal
268      *             in Boron release.
269      */
270     @Deprecated
271     public static String packageNameForTypeDefinition(final String basePackageName,
272             final TypeDefinition<?> typeDefinition) {
273         if (basePackageName == null) {
274             throw new IllegalArgumentException("Base Package Name cannot be NULL!");
275         }
276         if (typeDefinition == null) {
277             throw new IllegalArgumentException("Type Definition reference cannot be NULL!");
278         }
279
280         return BindingMapping.normalizePackageName(basePackageName);
281     }
282
283     /**
284      * Converts <code>token</code> to string which is in accordance with best
285      * practices for JAVA class names.
286      *
287      * @param token
288      *            string which contains characters which should be converted to
289      *            JAVA class name
290      * @return string which is in accordance with best practices for JAVA class
291      *         name.
292      *
293      * @deprecated Use {@link BindingMapping#getClassName(QName)} instead.
294      */
295     @Deprecated
296     public static String parseToClassName(final String token) {
297         return parseToCamelCase(token, true);
298     }
299
300     /**
301      * Converts <code>token</code> to string which is in accordance with best
302      * practices for JAVA parameter names.
303      *
304      * @param token
305      *            string which contains characters which should be converted to
306      *            JAVA parameter name
307      * @return string which is in accordance with best practices for JAVA
308      *         parameter name.
309      *
310      * @deprecated Use {@link BindingMapping#getPropertyName(String)} instead.
311      */
312     @Deprecated public static String parseToValidParamName(final String token) {
313         return resolveJavaReservedWordEquivalency(parseToCamelCase(token, false));
314     }
315
316     /**
317      *
318      * Converts string <code>token</code> to the cammel case format.
319      *
320      * @param token
321      *            string which should be converted to the cammel case format
322      * @param uppercase
323      *            boolean value which says whether the first character of the
324      *            <code>token</code> should|shuldn't be uppercased
325      * @return string in the cammel case format
326      * @throws IllegalArgumentException
327      *             <ul>
328      *             <li>if <code>token</code> without white spaces is empty</li>
329      *             <li>if <code>token</code> equals null</li>
330      *             </ul>
331      */
332     private static String parseToCamelCase(final String token, final boolean uppercase) {
333         if (token == null) {
334             throw new IllegalArgumentException("Name can not be null");
335         }
336
337         String correctStr = DOT_MATCHER.removeFrom(token.trim());
338         if (correctStr.isEmpty()) {
339             throw new IllegalArgumentException("Name can not be empty");
340         }
341
342         correctStr = replaceWithCamelCase(correctStr, ' ');
343         correctStr = replaceWithCamelCase(correctStr, '-');
344         correctStr = replaceWithCamelCase(correctStr, '_');
345
346         char firstChar = correctStr.charAt(0);
347         firstChar = uppercase ? Character.toUpperCase(firstChar) : Character.toLowerCase(firstChar);
348
349         if ((firstChar >= '0') && (firstChar <= '9')) {
350             return '_' + correctStr;
351         } else {
352             return firstChar + correctStr.substring(1);
353         }
354     }
355
356     /**
357      * Replaces all the occurrences of the <code>removalChar</code> in the
358      * <code>text</code> with empty string and converts following character to
359      * upper case.
360      *
361      * @param text
362      *            string with source text which should be converted
363      * @param removalChar
364      *            character which is sought in the <code>text</code>
365      * @return string which doesn't contain <code>removalChar</code> and has
366      *         following characters converted to upper case
367      * @throws IllegalArgumentException
368      *             if the length of the returning string has length 0
369      */
370     private static String replaceWithCamelCase(final String text, final char removalChar) {
371         int toBeRemovedPos = text.indexOf(removalChar);
372         if (toBeRemovedPos == -1) {
373             return text;
374         }
375
376         final StringBuilder sb = new StringBuilder(text);
377         final String toBeRemoved = String.valueOf(removalChar);
378         do {
379             sb.replace(toBeRemovedPos, toBeRemovedPos + 1, "");
380             // check if 'toBeRemoved' character is not the only character in
381             // 'text'
382             if (sb.length() == 0) {
383                 throw new IllegalArgumentException("The resulting string can not be empty");
384             }
385             final char replacement = Character.toUpperCase(sb.charAt(toBeRemovedPos));
386             sb.setCharAt(toBeRemovedPos, replacement);
387             toBeRemovedPos = sb.indexOf(toBeRemoved);
388         } while (toBeRemovedPos != -1);
389
390         return sb.toString();
391     }
392
393     private static <T> Iterable<T> sortedCollection(final Comparator<? super T> comparator, final Collection<T> input) {
394         if (input.size() > 1) {
395             final List<T> ret = new ArrayList<>(input);
396             Collections.sort(ret, comparator);
397             return ret;
398         } else {
399             return input;
400         }
401     }
402
403     private static final ThreadLocal<MessageDigest> SHA1_MD = new ThreadLocal<MessageDigest>() {
404         @Override
405         protected MessageDigest initialValue() {
406             try {
407                 return MessageDigest.getInstance("SHA");
408             } catch (final NoSuchAlgorithmException e) {
409                 throw new IllegalStateException("Failed to get a SHA digest provider", e);
410             }
411         }
412     };
413
414     public static long computeDefaultSUID(final GeneratedTypeBuilderBase<?> to) {
415         final ByteArrayOutputStream bout = new ByteArrayOutputStream();
416         try (final DataOutputStream dout = new DataOutputStream(bout)) {
417             dout.writeUTF(to.getName());
418             dout.writeInt(to.isAbstract() ? 3 : 7);
419
420             for (final Type ifc : sortedCollection(SUID_NAME_COMPARATOR, to.getImplementsTypes())) {
421                 dout.writeUTF(ifc.getFullyQualifiedName());
422             }
423
424             for (final GeneratedPropertyBuilder gp : sortedCollection(SUID_MEMBER_COMPARATOR, to.getProperties())) {
425                 dout.writeUTF(gp.getName());
426             }
427
428             for (final MethodSignatureBuilder m : sortedCollection(SUID_MEMBER_COMPARATOR, to.getMethodDefinitions())) {
429                 if (!(m.getAccessModifier().equals(AccessModifier.PRIVATE))) {
430                     dout.writeUTF(m.getName());
431                     dout.write(m.getAccessModifier().ordinal());
432                 }
433             }
434
435             dout.flush();
436         } catch (final IOException e) {
437             throw new IllegalStateException("Failed to hash object " + to, e);
438         }
439
440         final byte[] hashBytes = SHA1_MD.get().digest(bout.toByteArray());
441         long hash = 0;
442         for (int i = Math.min(hashBytes.length, 8) - 1; i >= 0; i--) {
443             hash = (hash << 8) | (hashBytes[i] & 0xFF);
444         }
445         return hash;
446     }
447
448     private static <T> List<T> currentOrEmpty(final List<T> current, final List<T> base) {
449         return current.equals(base) ? ImmutableList.<T>of() : current;
450     }
451
452     private static boolean containsConstraint(final StringTypeDefinition type, final PatternConstraint constraint) {
453         for (StringTypeDefinition wlk = type; wlk != null; wlk = wlk.getBaseType()) {
454             if (wlk.getPatternConstraints().contains(constraint)) {
455                 return true;
456             }
457         }
458
459         return false;
460     }
461
462     private static List<PatternConstraint> uniquePatterns(final StringTypeDefinition type) {
463         final List<PatternConstraint> constraints = type.getPatternConstraints();
464         if (constraints.isEmpty()) {
465             return constraints;
466         }
467
468         final Builder<PatternConstraint> builder = ImmutableList.builder();
469         boolean filtered = false;
470         for (final PatternConstraint c : constraints) {
471             if (containsConstraint(type.getBaseType(), c)) {
472                 filtered = true;
473             } else {
474                 builder.add(c);
475             }
476         }
477
478         return filtered ? builder.build() : constraints;
479     }
480
481     public static Restrictions getRestrictions(final TypeDefinition<?> type) {
482         // Old parser generated types which actually contained based restrictions, but our code deals with that when
483         // binding to core Java types. Hence we'll emit empty restrictions for base types.
484         if ((type == null) || (type.getBaseType() == null)) {
485             // Handling of decimal64 has changed in the new parser. It contains range restrictions applied to the type
486             // directly, without an extended type. We need to capture such constraints. In order to retain behavior we
487             // need to analyze the new semantics and see if the constraints have been overridden. To do that we
488             // instantiate a temporary unconstrained type and compare them.
489             //
490             // FIXME: looking at the generated code it looks as though we need to pass the restrictions without
491             //        comparison
492             if (type instanceof DecimalTypeDefinition) {
493                 final DecimalTypeDefinition decimal = (DecimalTypeDefinition) type;
494                 final DecimalTypeBuilder tmpBuilder = BaseTypes.decimalTypeBuilder(decimal.getPath());
495                 tmpBuilder.setFractionDigits(decimal.getFractionDigits());
496                 final DecimalTypeDefinition tmp = tmpBuilder.build();
497
498                 if (!tmp.getRangeConstraints().equals(decimal.getRangeConstraints())) {
499                     return new Restrictions() {
500                         @Override
501                         public boolean isEmpty() {
502                             return false;
503                         }
504
505                         @Override
506                         public List<RangeConstraint> getRangeConstraints() {
507                             return decimal.getRangeConstraints();
508                         }
509
510                         @Override
511                         public List<PatternConstraint> getPatternConstraints() {
512                             return ImmutableList.of();
513                         }
514
515                         @Override
516                         public List<LengthConstraint> getLengthConstraints() {
517                             return ImmutableList.of();
518                         }
519                     };
520                 }
521             }
522
523             return EMPTY_RESTRICTIONS;
524         }
525
526         final List<LengthConstraint> length;
527         final List<PatternConstraint> pattern;
528         final List<RangeConstraint> range;
529
530         /*
531          * Take care of extended types.
532          *
533          * Other types which support constraints are check afterwards. There is a slight twist with them, as returned
534          * constraints are the effective view, e.g. they are inherited from base type. Since the constraint is already
535          * enforced by the base type, we want to skip them and not perform duplicate checks.
536          *
537          * We end up emitting ConcreteType instances for YANG base types, which leads to their constraints not being
538          * enforced (most notably decimal64). Therefore we need to make sure we do not strip the next-to-last
539          * restrictions.
540          *
541          * FIXME: this probably not the best solution and needs further analysis.
542          */
543         if (type instanceof BinaryTypeDefinition) {
544             final BinaryTypeDefinition binary = (BinaryTypeDefinition)type;
545             final BinaryTypeDefinition base = binary.getBaseType();
546             if ((base != null) && (base.getBaseType() != null)) {
547                 length = currentOrEmpty(binary.getLengthConstraints(), base.getLengthConstraints());
548             } else {
549                 length = binary.getLengthConstraints();
550             }
551
552             pattern = ImmutableList.of();
553             range = ImmutableList.of();
554         } else if (type instanceof DecimalTypeDefinition) {
555             length = ImmutableList.of();
556             pattern = ImmutableList.of();
557
558             final DecimalTypeDefinition decimal = (DecimalTypeDefinition)type;
559             final DecimalTypeDefinition base = decimal.getBaseType();
560             if ((base != null) && (base.getBaseType() != null)) {
561                 range = currentOrEmpty(decimal.getRangeConstraints(), base.getRangeConstraints());
562             } else {
563                 range = decimal.getRangeConstraints();
564             }
565         } else if (type instanceof IntegerTypeDefinition) {
566             length = ImmutableList.of();
567             pattern = ImmutableList.of();
568
569             final IntegerTypeDefinition integer = (IntegerTypeDefinition)type;
570             final IntegerTypeDefinition base = integer.getBaseType();
571             if ((base != null) && (base.getBaseType() != null)) {
572                 range = currentOrEmpty(integer.getRangeConstraints(), base.getRangeConstraints());
573             } else {
574                 range = integer.getRangeConstraints();
575             }
576         } else if (type instanceof StringTypeDefinition) {
577             final StringTypeDefinition string = (StringTypeDefinition)type;
578             final StringTypeDefinition base = string.getBaseType();
579             if ((base != null) && (base.getBaseType() != null)) {
580                 length = currentOrEmpty(string.getLengthConstraints(), base.getLengthConstraints());
581             } else {
582                 length = string.getLengthConstraints();
583             }
584
585             pattern = uniquePatterns(string);
586             range = ImmutableList.of();
587         } else if (type instanceof UnsignedIntegerTypeDefinition) {
588             length = ImmutableList.of();
589             pattern = ImmutableList.of();
590
591             final UnsignedIntegerTypeDefinition unsigned = (UnsignedIntegerTypeDefinition)type;
592             final UnsignedIntegerTypeDefinition base = unsigned.getBaseType();
593             if ((base != null) && (base.getBaseType() != null)) {
594                 range = currentOrEmpty(unsigned.getRangeConstraints(), base.getRangeConstraints());
595             } else {
596                 range = unsigned.getRangeConstraints();
597             }
598         } else {
599             length = ImmutableList.of();
600             pattern = ImmutableList.of();
601             range = ImmutableList.of();
602         }
603
604         // Now, this may have ended up being empty, too...
605         if (length.isEmpty() && pattern.isEmpty() && range.isEmpty()) {
606             return EMPTY_RESTRICTIONS;
607         }
608
609         // Nope, not empty allocate a holder
610         return new Restrictions() {
611             @Override
612             public List<RangeConstraint> getRangeConstraints() {
613                 return range;
614             }
615             @Override
616             public List<PatternConstraint> getPatternConstraints() {
617                 return pattern;
618             }
619             @Override
620             public List<LengthConstraint> getLengthConstraints() {
621                 return length;
622             }
623             @Override
624             public boolean isEmpty() {
625                 return false;
626             }
627         };
628     }
629
630     /**
631      * Encodes angle brackets in yang statement description
632      * @param description description of a yang statement which is used to generate javadoc comments
633      * @return string with encoded angle brackets
634      */
635     public static String encodeAngleBrackets(String description) {
636         if (description != null) {
637             description = LT_MATCHER.replaceFrom(description, "&lt;");
638             description = GT_MATCHER.replaceFrom(description, "&gt;");
639         }
640         return description;
641     }
642 }