Binding generator v2 - augment statement #2
[mdsal.git] / binding2 / mdsal-binding2-generator-util / src / main / java / org / opendaylight / mdsal / binding / javav2 / generator / util / BindingGeneratorUtil.java
1 /*
2  * Copyright (c) 2017 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
9 package org.opendaylight.mdsal.binding.javav2.generator.util;
10
11 import com.google.common.annotations.Beta;
12 import com.google.common.base.CharMatcher;
13 import com.google.common.base.Preconditions;
14 import com.google.common.collect.ImmutableList;
15 import com.google.common.collect.ImmutableList.Builder;
16 import com.google.common.collect.Interner;
17 import com.google.common.collect.Interners;
18 import com.google.common.collect.Iterables;
19 import java.io.ByteArrayOutputStream;
20 import java.io.DataOutputStream;
21 import java.io.IOException;
22 import java.security.MessageDigest;
23 import java.security.NoSuchAlgorithmException;
24 import java.util.ArrayList;
25 import java.util.Collection;
26 import java.util.Collections;
27 import java.util.Comparator;
28 import java.util.Iterator;
29 import java.util.List;
30 import org.opendaylight.mdsal.binding.javav2.model.api.AccessModifier;
31 import org.opendaylight.mdsal.binding.javav2.model.api.Restrictions;
32 import org.opendaylight.mdsal.binding.javav2.model.api.Type;
33 import org.opendaylight.mdsal.binding.javav2.model.api.type.builder.GeneratedPropertyBuilder;
34 import org.opendaylight.mdsal.binding.javav2.model.api.type.builder.GeneratedTypeBuilderBase;
35 import org.opendaylight.mdsal.binding.javav2.model.api.type.builder.MethodSignatureBuilder;
36 import org.opendaylight.mdsal.binding.javav2.model.api.type.builder.TypeMemberBuilder;
37 import org.opendaylight.mdsal.binding.javav2.spec.runtime.BindingNamespaceType;
38 import org.opendaylight.yangtools.yang.common.QName;
39 import org.opendaylight.yangtools.yang.model.api.AugmentationSchema;
40 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
41 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
42 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
43 import org.opendaylight.yangtools.yang.model.api.type.BinaryTypeDefinition;
44 import org.opendaylight.yangtools.yang.model.api.type.DecimalTypeDefinition;
45 import org.opendaylight.yangtools.yang.model.api.type.IntegerTypeDefinition;
46 import org.opendaylight.yangtools.yang.model.api.type.LengthConstraint;
47 import org.opendaylight.yangtools.yang.model.api.type.PatternConstraint;
48 import org.opendaylight.yangtools.yang.model.api.type.RangeConstraint;
49 import org.opendaylight.yangtools.yang.model.api.type.StringTypeDefinition;
50 import org.opendaylight.yangtools.yang.model.api.type.UnsignedIntegerTypeDefinition;
51 import org.opendaylight.yangtools.yang.model.util.type.BaseTypes;
52 import org.opendaylight.yangtools.yang.model.util.type.DecimalTypeBuilder;
53
54 /**
55  * Standard Util class that contains various method for converting
56  * input strings to valid JAVA language strings e.g. package names,
57  * class names, attribute names and/or valid JavaDoc comments.
58  */
59 @Beta
60 public final class BindingGeneratorUtil {
61
62     private static final CharMatcher GT_MATCHER = CharMatcher.is('>');
63     private static final CharMatcher LT_MATCHER = CharMatcher.is('<');
64
65     private static final Interner<String> PACKAGE_INTERNER = Interners.newWeakInterner();
66     private static final Comparator<TypeMemberBuilder<?>> SUID_MEMBER_COMPARATOR =
67             Comparator.comparing(TypeMemberBuilder::getName);
68
69     private static final Comparator<Type> SUID_NAME_COMPARATOR =
70             Comparator.comparing(Type::getFullyQualifiedName);
71
72     private static final Restrictions EMPTY_RESTRICTIONS = new Restrictions() {
73         @Override
74         public List<LengthConstraint> getLengthConstraints() {
75             return Collections.emptyList();
76         }
77
78         @Override
79         public List<PatternConstraint> getPatternConstraints() {
80             return Collections.emptyList();
81         }
82
83         @Override
84         public List<RangeConstraint> getRangeConstraints() {
85             return Collections.emptyList();
86         }
87
88         @Override
89         public boolean isEmpty() {
90             return true;
91         }
92     };
93
94     private BindingGeneratorUtil() {
95         throw new UnsupportedOperationException("Utility class");
96     }
97
98     /**
99      * Encodes angle brackets in yang statement description
100      * @param description description of a yang statement which is used to generate javadoc comments
101      * @return string with encoded angle brackets
102      */
103     public static String encodeAngleBrackets(final String description) {
104         String newDesc = description;
105         if (newDesc != null) {
106             newDesc = LT_MATCHER.replaceFrom(newDesc, "&lt;");
107             newDesc = GT_MATCHER.replaceFrom(newDesc, "&gt;");
108         }
109         return newDesc;
110     }
111
112     public static long computeDefaultSUID(final GeneratedTypeBuilderBase<?> to) {
113         final ByteArrayOutputStream bout = new ByteArrayOutputStream();
114         try (final DataOutputStream dout = new DataOutputStream(bout)) {
115             dout.writeUTF(to.getName());
116             dout.writeInt(to.isAbstract() ? 3 : 7);
117
118             for (final Type ifc : sortedCollection(SUID_NAME_COMPARATOR, to.getImplementsTypes())) {
119                 dout.writeUTF(ifc.getFullyQualifiedName());
120             }
121
122             for (final GeneratedPropertyBuilder gp : sortedCollection(SUID_MEMBER_COMPARATOR, to.getProperties())) {
123                 dout.writeUTF(gp.getName());
124             }
125
126             for (final MethodSignatureBuilder m : sortedCollection(SUID_MEMBER_COMPARATOR, to.getMethodDefinitions())) {
127                 if (!(m.getAccessModifier().equals(AccessModifier.PRIVATE))) {
128                     dout.writeUTF(m.getName());
129                     dout.write(m.getAccessModifier().ordinal());
130                 }
131             }
132
133             dout.flush();
134         } catch (final IOException e) {
135             throw new IllegalStateException("Failed to hash object " + to, e);
136         }
137
138         final byte[] hashBytes = SHA1_MD.get().digest(bout.toByteArray());
139         long hash = 0;
140         for (int i = Math.min(hashBytes.length, 8) - 1; i >= 0; i--) {
141             hash = (hash << 8) | (hashBytes[i] & 0xFF);
142         }
143         return hash;
144     }
145
146     /**
147      * Creates package name from specified <code>basePackageName</code> (package
148      * name for module) and <code>schemaPath</code>.
149      *
150      * Resulting package name is concatenation of <code>basePackageName</code>
151      * and all local names of YANG nodes which are parents of some node for
152      * which <code>schemaPath</code> is specified.
153      *
154      * Based on type of node, there is also possible suffix added in order
155      * to prevent package name conflicts.
156      *
157      * @param basePackageName
158      *            string with package name of the module, MUST be normalized,
159      *            otherwise this method may return an invalid string.
160      * @param schemaPath
161      *            list of names of YANG nodes which are parents of some node +
162      *            name of this node
163      * @return string with valid JAVA package name
164      * @throws NullPointerException if any of the arguments are null
165      */
166     public static String packageNameForGeneratedType(final String basePackageName, final SchemaPath schemaPath, final
167         BindingNamespaceType namespaceType) {
168
169         final Iterable<QName> pathTowardsRoot = schemaPath.getPathTowardsRoot();
170         final Iterable<QName> pathFromRoot = schemaPath.getPathFromRoot();
171         final int size = Iterables.size(pathTowardsRoot) - 1;
172         if (size <= 0) {
173             if (namespaceType != null) {
174                 final StringBuilder sb = new StringBuilder();
175                 sb.append(basePackageName)
176                   .append('.')
177                   .append(namespaceType.getPackagePrefix());
178                 return JavaIdentifierNormalizer.normalizeFullPackageName(sb.toString());
179             }
180             return JavaIdentifierNormalizer.normalizeFullPackageName(basePackageName);
181         }
182
183         return generateNormalizedPackageName(basePackageName, pathFromRoot, size, namespaceType);
184     }
185
186     /**
187      * Creates package name from specified <code>basePackageName</code> (package
188      * name for module) and <code>namespaceType</code>.
189      *
190      * Resulting package name is concatenation of <code>basePackageName</code>
191      * and prefix of <code>namespaceType</code>.
192      *
193      * @param basePackageName
194      *            string with package name of the module, MUST be normalized,
195      *            otherwise this method may return an invalid string.
196      * @param namespaceType
197      *            the namespace to which the module belongs
198      * @return string with valid JAVA package name
199      * @throws NullPointerException if any of the arguments are null
200      */
201     public static String packageNameWithNamespacePrefix(final String basePackageName,
202             final BindingNamespaceType namespaceType) {
203         final StringBuilder sb = new StringBuilder();
204         sb.append(basePackageName)
205                 .append('.')
206                 .append(namespaceType.getPackagePrefix());
207         return JavaIdentifierNormalizer.normalizeFullPackageName(sb.toString());
208     }
209
210     public static Restrictions getRestrictions(final TypeDefinition<?> type) {
211         if ((type == null) || (type.getBaseType() == null)) {
212             if (type instanceof DecimalTypeDefinition) {
213                 final DecimalTypeDefinition decimal = (DecimalTypeDefinition) type;
214                 final DecimalTypeBuilder tmpBuilder = BaseTypes.decimalTypeBuilder(decimal.getPath());
215                 tmpBuilder.setFractionDigits(decimal.getFractionDigits());
216                 final DecimalTypeDefinition tmp = tmpBuilder.build();
217
218                 if (!tmp.getRangeConstraints().equals(decimal.getRangeConstraints())) {
219                     return new Restrictions() {
220                         @Override
221                         public boolean isEmpty() {
222                             return false;
223                         }
224
225                         @Override
226                         public List<RangeConstraint> getRangeConstraints() {
227                             return decimal.getRangeConstraints();
228                         }
229
230                         @Override
231                         public List<PatternConstraint> getPatternConstraints() {
232                             return ImmutableList.of();
233                         }
234
235                         @Override
236                         public List<LengthConstraint> getLengthConstraints() {
237                             return ImmutableList.of();
238                         }
239                     };
240                 }
241             }
242
243             return EMPTY_RESTRICTIONS;
244         }
245
246         final List<LengthConstraint> length;
247         final List<PatternConstraint> pattern;
248         final List<RangeConstraint> range;
249
250         /*
251          * Take care of extended types.
252          *
253          * Other types which support constraints are check afterwards. There is a slight twist with them, as returned
254          * constraints are the effective view, e.g. they are inherited from base type. Since the constraint is already
255          * enforced by the base type, we want to skip them and not perform duplicate checks.
256          *
257          * We end up emitting ConcreteType instances for YANG base types, which leads to their constraints not being
258          * enforced (most notably decimal64). Therefore we need to make sure we do not strip the next-to-last
259          * restrictions.
260          */
261         if (type instanceof BinaryTypeDefinition) {
262             final BinaryTypeDefinition binary = (BinaryTypeDefinition)type;
263             final BinaryTypeDefinition base = binary.getBaseType();
264             if ((base != null) && (base.getBaseType() != null)) {
265                 length = currentOrEmpty(binary.getLengthConstraints(), base.getLengthConstraints());
266             } else {
267                 length = binary.getLengthConstraints();
268             }
269
270             pattern = ImmutableList.of();
271             range = ImmutableList.of();
272         } else if (type instanceof DecimalTypeDefinition) {
273             length = ImmutableList.of();
274             pattern = ImmutableList.of();
275
276             final DecimalTypeDefinition decimal = (DecimalTypeDefinition)type;
277             final DecimalTypeDefinition base = decimal.getBaseType();
278             if ((base != null) && (base.getBaseType() != null)) {
279                 range = currentOrEmpty(decimal.getRangeConstraints(), base.getRangeConstraints());
280             } else {
281                 range = decimal.getRangeConstraints();
282             }
283         } else if (type instanceof IntegerTypeDefinition) {
284             length = ImmutableList.of();
285             pattern = ImmutableList.of();
286
287             final IntegerTypeDefinition integer = (IntegerTypeDefinition)type;
288             final IntegerTypeDefinition base = integer.getBaseType();
289             if ((base != null) && (base.getBaseType() != null)) {
290                 range = currentOrEmpty(integer.getRangeConstraints(), base.getRangeConstraints());
291             } else {
292                 range = integer.getRangeConstraints();
293             }
294         } else if (type instanceof StringTypeDefinition) {
295             final StringTypeDefinition string = (StringTypeDefinition)type;
296             final StringTypeDefinition base = string.getBaseType();
297             if ((base != null) && (base.getBaseType() != null)) {
298                 length = currentOrEmpty(string.getLengthConstraints(), base.getLengthConstraints());
299             } else {
300                 length = string.getLengthConstraints();
301             }
302
303             pattern = uniquePatterns(string);
304             range = ImmutableList.of();
305         } else if (type instanceof UnsignedIntegerTypeDefinition) {
306             length = ImmutableList.of();
307             pattern = ImmutableList.of();
308
309             final UnsignedIntegerTypeDefinition unsigned = (UnsignedIntegerTypeDefinition)type;
310             final UnsignedIntegerTypeDefinition base = unsigned.getBaseType();
311             if ((base != null) && (base.getBaseType() != null)) {
312                 range = currentOrEmpty(unsigned.getRangeConstraints(), base.getRangeConstraints());
313             } else {
314                 range = unsigned.getRangeConstraints();
315             }
316         } else {
317             length = ImmutableList.of();
318             pattern = ImmutableList.of();
319             range = ImmutableList.of();
320         }
321
322         // Now, this may have ended up being empty, too...
323         if (length.isEmpty() && pattern.isEmpty() && range.isEmpty()) {
324             return EMPTY_RESTRICTIONS;
325         }
326
327         // Nope, not empty allocate a holder
328         return new Restrictions() {
329             @Override
330             public List<RangeConstraint> getRangeConstraints() {
331                 return range;
332             }
333             @Override
334             public List<PatternConstraint> getPatternConstraints() {
335                 return pattern;
336             }
337             @Override
338             public List<LengthConstraint> getLengthConstraints() {
339                 return length;
340             }
341             @Override
342             public boolean isEmpty() {
343                 return false;
344             }
345         };
346     }
347
348     /**
349      * Creates package name from specified <code>basePackageName</code> (package
350      * name for module) and <code>schemaPath</code> which crosses an augmentation.
351      *
352      * Resulting package name is concatenation of <code>basePackageName</code>
353      * and all local names of YANG nodes which are parents of some node for
354      * which <code>schemaPath</code> is specified.
355      *
356      * Based on type of node, there is also possible suffix added in order
357      * to prevent package name conflicts.
358      *
359      * @param basePackageName
360      *            string with package name of the module, MUST be normalized,
361      *            otherwise this method may return an invalid string.
362      * @param schemaPath
363      *            list of names of YANG nodes which are parents of some node +
364      *            name of this node
365      * @return string with valid JAVA package name
366      * @throws NullPointerException if any of the arguments are null
367      */
368     public static String packageNameForAugmentedGeneratedType(final String basePackageName, final SchemaPath schemaPath) {
369         final Iterable<QName> pathTowardsRoot = schemaPath.getPathTowardsRoot();
370         final Iterable<QName> pathFromRoot = schemaPath.getPathFromRoot();
371         final int size = Iterables.size(pathTowardsRoot);
372         if (size == 0) {
373             return basePackageName;
374         }
375
376         return generateNormalizedPackageName(basePackageName, pathFromRoot, size, BindingNamespaceType.Data);
377     }
378
379     /**
380      * Creates package name from <code>parentAugmentPackageName</code> (package
381      * name for direct parent augmentation) and <code>augmentationSchema</code> .
382      *
383      * Resulting package name is concatenation of <code>parentAugmentPackageName</code>
384      * and the local name of <code>schemaPath</code>.
385      *
386      * Based on type of node, there is also possible suffix added in order
387      * to prevent package name conflicts.
388      *
389      * @param parentAugmentPackageName
390      *            string with package name of direct parent augmentation, MUST be normalized,
391      *            otherwise this method may return an invalid string.
392      * @param augmentationSchema
393      *            augmentation schema which is direct son of parent augmentation.
394      * @return string with valid JAVA package name
395      * @throws NullPointerException if any of the arguments are null
396      */
397     public static String packageNameForAugmentedGeneratedType(final String parentAugmentPackageName,
398                                                               final AugmentationSchema augmentationSchema) {
399         final QName last = augmentationSchema.getTargetPath().getLastComponent();
400
401         return generateNormalizedPackageName(parentAugmentPackageName, last);
402     }
403
404     public static String packageNameForSubGeneratedType(final String basePackageName, final SchemaNode node,
405                                                         final BindingNamespaceType namespaceType) {
406         final String parent = packageNameForGeneratedType(basePackageName, node.getPath(), namespaceType);
407         final QName last = node.getPath().getLastComponent();
408
409         return generateNormalizedPackageName(parent, last);
410     }
411
412     public static String replacePackageTopNamespace(final String basePackageName,
413             final String toReplacePackageName,
414             final BindingNamespaceType toReplaceNameSpace,
415             final BindingNamespaceType replacedNameSpace) {
416         Preconditions.checkArgument(basePackageName != null);
417         String normalizeBasePackageName = JavaIdentifierNormalizer.normalizeFullPackageName(basePackageName);
418
419         if (!normalizeBasePackageName.equals(toReplacePackageName)) {
420             final String topPackageName = new StringBuilder(normalizeBasePackageName)
421                     .append('.').append(toReplaceNameSpace.getPackagePrefix()).toString();
422
423             Preconditions.checkState(toReplacePackageName.equals(topPackageName)
424                             || toReplacePackageName.contains(topPackageName),
425                     "Package name to replace does not belong to the given namespace to replace!");
426
427             return new StringBuilder(normalizeBasePackageName)
428                     .append('.')
429                     .append(replacedNameSpace.getPackagePrefix())
430                     .append(toReplacePackageName.substring(topPackageName.length()))
431                     .toString();
432         } else {
433             return new StringBuilder(normalizeBasePackageName)
434                     .append('.').append(replacedNameSpace.getPackagePrefix()).toString();
435         }
436     }
437
438     private static final ThreadLocal<MessageDigest> SHA1_MD = new ThreadLocal<MessageDigest>() {
439         @Override
440         protected MessageDigest initialValue() {
441             try {
442                 return MessageDigest.getInstance("SHA");
443             } catch (final NoSuchAlgorithmException e) {
444                 throw new IllegalStateException("Failed to get a SHA digest provider", e);
445             }
446         }
447     };
448
449     private static String generateNormalizedPackageName(final String base, final Iterable<QName> path, final int
450             size, final BindingNamespaceType namespaceType) {
451         final StringBuilder builder = new StringBuilder(base);
452         if (namespaceType != null) {
453             builder.append('.').append(namespaceType.getPackagePrefix());
454         }
455         final Iterator<QName> iterator = path.iterator();
456         for (int i = 0; i < size; ++i) {
457             builder.append('.');
458             final String nodeLocalName = iterator.next().getLocalName();
459             builder.append(nodeLocalName);
460         }
461         final String normalizedPackageName = JavaIdentifierNormalizer.normalizeFullPackageName(builder.toString());
462         // Prevent duplication of input
463         PACKAGE_INTERNER.intern(normalizedPackageName);
464         return normalizedPackageName;
465     }
466
467     private static String generateNormalizedPackageName(final String parent, final QName path) {
468         final StringBuilder sb = new StringBuilder(parent)
469                 .append('.')
470                 .append(path.getLocalName());
471
472         final String normalizedPackageName = JavaIdentifierNormalizer.normalizeFullPackageName(sb.toString());
473         // Prevent duplication of input
474         PACKAGE_INTERNER.intern(normalizedPackageName);
475         return normalizedPackageName;
476     }
477
478     private static <T> Iterable<T> sortedCollection(final Comparator<? super T> comparator, final Collection<T> input) {
479         if (input.size() > 1) {
480             final List<T> ret = new ArrayList<>(input);
481             ret.sort(comparator);
482             return ret;
483         } else {
484             return input;
485         }
486     }
487
488     private static <T> List<T> currentOrEmpty(final List<T> current, final List<T> base) {
489         return current.equals(base) ? ImmutableList.of() : current;
490     }
491
492     private static List<PatternConstraint> uniquePatterns(final StringTypeDefinition type) {
493         final List<PatternConstraint> constraints = type.getPatternConstraints();
494         if (constraints.isEmpty()) {
495             return constraints;
496         }
497
498         final Builder<PatternConstraint> builder = ImmutableList.builder();
499         boolean filtered = false;
500         for (final PatternConstraint c : constraints) {
501             if (containsConstraint(type.getBaseType(), c)) {
502                 filtered = true;
503             } else {
504                 builder.add(c);
505             }
506         }
507
508         return filtered ? builder.build() : constraints;
509     }
510
511     private static boolean containsConstraint(final StringTypeDefinition type, final PatternConstraint constraint) {
512         for (StringTypeDefinition wlk = type; wlk != null; wlk = wlk.getBaseType()) {
513             if (wlk.getPatternConstraints().contains(constraint)) {
514                 return true;
515             }
516         }
517
518         return false;
519     }
520
521     //TODO: further implementation of static util methods...
522 }