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