Mark typedef types as TypeObject
[mdsal.git] / binding / mdsal-binding-generator-util / src / main / java / org / opendaylight / mdsal / binding / model / 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.model.util;
9
10 import com.google.common.base.CharMatcher;
11 import com.google.common.collect.Collections2;
12 import com.google.common.collect.ImmutableList;
13 import com.google.common.collect.ImmutableList.Builder;
14 import com.google.common.collect.Iterables;
15 import java.io.ByteArrayOutputStream;
16 import java.io.DataOutputStream;
17 import java.io.IOException;
18 import java.security.MessageDigest;
19 import java.security.NoSuchAlgorithmException;
20 import java.util.ArrayList;
21 import java.util.Collection;
22 import java.util.Collections;
23 import java.util.Comparator;
24 import java.util.Iterator;
25 import java.util.List;
26 import java.util.Optional;
27 import java.util.regex.Pattern;
28 import org.opendaylight.mdsal.binding.model.api.AccessModifier;
29 import org.opendaylight.mdsal.binding.model.api.Restrictions;
30 import org.opendaylight.mdsal.binding.model.api.Type;
31 import org.opendaylight.mdsal.binding.model.api.type.builder.GeneratedPropertyBuilder;
32 import org.opendaylight.mdsal.binding.model.api.type.builder.GeneratedTypeBuilderBase;
33 import org.opendaylight.mdsal.binding.model.api.type.builder.MethodSignatureBuilder;
34 import org.opendaylight.mdsal.binding.model.api.type.builder.TypeMemberBuilder;
35 import org.opendaylight.mdsal.binding.spec.naming.BindingMapping;
36 import org.opendaylight.yangtools.yang.common.QName;
37 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
38 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
39 import org.opendaylight.yangtools.yang.model.api.type.BinaryTypeDefinition;
40 import org.opendaylight.yangtools.yang.model.api.type.DecimalTypeDefinition;
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.RangeRestrictedTypeDefinition;
45 import org.opendaylight.yangtools.yang.model.api.type.StringTypeDefinition;
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 are static.
57      */
58     private BindingGeneratorUtil() {
59
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     private static final Pattern UNICODE_CHAR_PATTERN = Pattern.compile("\\\\+u");
70
71     private static final Restrictions EMPTY_RESTRICTIONS = new Restrictions() {
72         @Override
73         public Optional<LengthConstraint> getLengthConstraint() {
74             return Optional.empty();
75         }
76
77         @Override
78         public List<PatternConstraint> getPatternConstraints() {
79             return Collections.emptyList();
80         }
81
82         @Override
83         public Optional<RangeConstraint<?>> getRangeConstraint() {
84             return Optional.empty();
85         }
86
87         @Override
88         public boolean isEmpty() {
89             return true;
90         }
91     };
92
93     private static final Comparator<TypeMemberBuilder<?>> SUID_MEMBER_COMPARATOR =
94         Comparator.comparing(TypeMemberBuilder::getName);
95
96     private static final Comparator<Type> SUID_NAME_COMPARATOR = Comparator.comparing(Type::getFullyQualifiedName);
97
98     /**
99      * Converts <code>parameterName</code> to valid JAVA parameter name. If the <code>parameterName</code> is one
100      * of the JAVA reserved words then it is prefixed with underscore character.
101      *
102      * @param parameterName string with the parameter name
103      * @return string with the admissible parameter name
104      */
105     public static String resolveJavaReservedWordEquivalency(final String parameterName) {
106         if (parameterName != null && BindingMapping.JAVA_RESERVED_WORDS.contains(parameterName)) {
107             return "_" + parameterName;
108         }
109         return parameterName;
110     }
111
112     /**
113      * Creates package name from specified <code>basePackageName</code> (package name for module)
114      * and <code>schemaPath</code>. Resulting package name is concatenation of <code>basePackageName</code>
115      * and all local names of YANG nodes which are parents of some node for which <code>schemaPath</code> is specified.
116      *
117      * @param basePackageName string with package name of the module, MUST be normalized, otherwise this method may
118      *                        return an invalid string.
119      * @param schemaPath list of names of YANG nodes which are parents of some node + name of this node
120      * @return string with valid JAVA package name
121      * @throws NullPointerException if any of the arguments are null
122      */
123     public static String packageNameForGeneratedType(final String basePackageName, final SchemaPath schemaPath) {
124         final int size = Iterables.size(schemaPath.getPathTowardsRoot()) - 1;
125         if (size <= 0) {
126             return basePackageName;
127         }
128
129         return generateNormalizedPackageName(basePackageName, schemaPath.getPathFromRoot(), size);
130     }
131
132     /**
133      * Creates package name from specified <code>basePackageName</code> (package name for module)
134      * and <code>schemaPath</code> which crosses an augmentation. Resulting package name is concatenation
135      * of <code>basePackageName</code> and all local names of YANG nodes which are parents of some node for which
136      * <code>schemaPath</code> is specified.
137      *
138      * @param basePackageName string with package name of the module, MUST be normalized, otherwise this method may
139      *                        return an invalid string.
140      * @param schemaPath list of names of YANG nodes which are parents of some node + name of this node
141      * @return string with valid JAVA package name
142      * @throws NullPointerException if any of the arguments are null
143      */
144     public static String packageNameForAugmentedGeneratedType(final String basePackageName,
145             final SchemaPath schemaPath) {
146         final int size = Iterables.size(schemaPath.getPathTowardsRoot());
147         if (size == 0) {
148             return basePackageName;
149         }
150
151         return generateNormalizedPackageName(basePackageName, schemaPath.getPathFromRoot(), size);
152     }
153
154     private static String generateNormalizedPackageName(final String base, final Iterable<QName> path, final int size) {
155         final StringBuilder builder = new StringBuilder(base);
156         final Iterator<QName> iterator = path.iterator();
157         for (int i = 0; i < size; ++i) {
158             builder.append('.');
159             final String nodeLocalName = iterator.next().getLocalName();
160             // FIXME: Collon ":" is invalid in node local name as per RFC6020, identifier statement.
161             builder.append(DASH_COLON_MATCHER.replaceFrom(nodeLocalName, '.'));
162         }
163         return BindingMapping.normalizePackageName(builder.toString());
164     }
165
166     private static <T> Iterable<T> sortedCollection(final Comparator<? super T> comparator, final Collection<T> input) {
167         if (input.size() <= 1) {
168             return input;
169         }
170
171         final List<T> ret = new ArrayList<>(input);
172         ret.sort(comparator);
173         return ret;
174     }
175
176     private static final ThreadLocal<MessageDigest> SHA1_MD = ThreadLocal.withInitial(() -> {
177         try {
178             return MessageDigest.getInstance("SHA");
179         } catch (final NoSuchAlgorithmException e) {
180             throw new IllegalStateException("Failed to get a SHA digest provider", e);
181         }
182     });
183
184     public static long computeDefaultSUID(final GeneratedTypeBuilderBase<?> to) {
185         final ByteArrayOutputStream bout = new ByteArrayOutputStream();
186         try (DataOutputStream dout = new DataOutputStream(bout)) {
187             dout.writeUTF(to.getName());
188             dout.writeInt(to.isAbstract() ? 3 : 7);
189
190             for (final Type ifc : sortedCollection(SUID_NAME_COMPARATOR, filteredImplementsTypes(to))) {
191                 dout.writeUTF(ifc.getFullyQualifiedName());
192             }
193
194             for (final GeneratedPropertyBuilder gp : sortedCollection(SUID_MEMBER_COMPARATOR, to.getProperties())) {
195                 dout.writeUTF(gp.getName());
196             }
197
198             for (final MethodSignatureBuilder m : sortedCollection(SUID_MEMBER_COMPARATOR, to.getMethodDefinitions())) {
199                 if (!m.getAccessModifier().equals(AccessModifier.PRIVATE)) {
200                     dout.writeUTF(m.getName());
201                     dout.write(m.getAccessModifier().ordinal());
202                 }
203             }
204
205             dout.flush();
206         } catch (final IOException e) {
207             throw new IllegalStateException("Failed to hash object " + to, e);
208         }
209
210         final byte[] hashBytes = SHA1_MD.get().digest(bout.toByteArray());
211         long hash = 0;
212         for (int i = Math.min(hashBytes.length, 8) - 1; i >= 0; i--) {
213             hash = hash << 8 | hashBytes[i] & 0xFF;
214         }
215         return hash;
216     }
217
218     private static Collection<Type> filteredImplementsTypes(final GeneratedTypeBuilderBase<?> to) {
219         return Collections2.filter(to.getImplementsTypes(), item -> !BindingTypes.TYPE_OBJECT.equals(item));
220     }
221
222     private static <T extends Optional<?>> T currentOrEmpty(final T current, final T base) {
223         return current.equals(base) ? (T)Optional.empty() : current;
224     }
225
226     private static boolean containsConstraint(final StringTypeDefinition type, final PatternConstraint constraint) {
227         for (StringTypeDefinition wlk = type; wlk != null; wlk = wlk.getBaseType()) {
228             if (wlk.getPatternConstraints().contains(constraint)) {
229                 return true;
230             }
231         }
232
233         return false;
234     }
235
236     private static List<PatternConstraint> uniquePatterns(final StringTypeDefinition type) {
237         final List<PatternConstraint> constraints = type.getPatternConstraints();
238         if (constraints.isEmpty()) {
239             return constraints;
240         }
241
242         final Builder<PatternConstraint> builder = ImmutableList.builder();
243         boolean filtered = false;
244         for (final PatternConstraint c : constraints) {
245             if (containsConstraint(type.getBaseType(), c)) {
246                 filtered = true;
247             } else {
248                 builder.add(c);
249             }
250         }
251
252         return filtered ? builder.build() : constraints;
253     }
254
255     public static Restrictions getRestrictions(final TypeDefinition<?> type) {
256         // Old parser generated types which actually contained based restrictions, but our code deals with that when
257         // binding to core Java types. Hence we'll emit empty restrictions for base types.
258         if (type == null || type.getBaseType() == null) {
259             // Handling of decimal64 has changed in the new parser. It contains range restrictions applied to the type
260             // directly, without an extended type. We need to capture such constraints. In order to retain behavior we
261             // need to analyze the new semantics and see if the constraints have been overridden. To do that we
262             // instantiate a temporary unconstrained type and compare them.
263             //
264             // FIXME: looking at the generated code it looks as though we need to pass the restrictions without
265             //        comparison
266             if (type instanceof DecimalTypeDefinition) {
267                 final DecimalTypeDefinition decimal = (DecimalTypeDefinition) type;
268                 final DecimalTypeBuilder tmpBuilder = BaseTypes.decimalTypeBuilder(decimal.getPath());
269                 tmpBuilder.setFractionDigits(decimal.getFractionDigits());
270                 final DecimalTypeDefinition tmp = tmpBuilder.build();
271
272                 if (!tmp.getRangeConstraint().equals(decimal.getRangeConstraint())) {
273                     return new Restrictions() {
274                         @Override
275                         public boolean isEmpty() {
276                             return false;
277                         }
278
279                         @Override
280                         public Optional<? extends RangeConstraint<?>> getRangeConstraint() {
281                             return decimal.getRangeConstraint();
282                         }
283
284                         @Override
285                         public List<PatternConstraint> getPatternConstraints() {
286                             return ImmutableList.of();
287                         }
288
289                         @Override
290                         public Optional<LengthConstraint> getLengthConstraint() {
291                             return Optional.empty();
292                         }
293                     };
294                 }
295             }
296
297             return EMPTY_RESTRICTIONS;
298         }
299
300         final Optional<LengthConstraint> length;
301         final List<PatternConstraint> pattern;
302         final Optional<? extends RangeConstraint<?>> range;
303
304         /*
305          * Take care of extended types.
306          *
307          * Other types which support constraints are check afterwards. There is a slight twist with them, as returned
308          * constraints are the effective view, e.g. they are inherited from base type. Since the constraint is already
309          * enforced by the base type, we want to skip them and not perform duplicate checks.
310          *
311          * We end up emitting ConcreteType instances for YANG base types, which leads to their constraints not being
312          * enforced (most notably decimal64). Therefore we need to make sure we do not strip the next-to-last
313          * restrictions.
314          *
315          * FIXME: this probably not the best solution and needs further analysis.
316          */
317         if (type instanceof BinaryTypeDefinition) {
318             final BinaryTypeDefinition binary = (BinaryTypeDefinition)type;
319             final BinaryTypeDefinition base = binary.getBaseType();
320             if (base != null && base.getBaseType() != null) {
321                 length = currentOrEmpty(binary.getLengthConstraint(), base.getLengthConstraint());
322             } else {
323                 length = binary.getLengthConstraint();
324             }
325
326             pattern = ImmutableList.of();
327             range = Optional.empty();
328         } else if (type instanceof DecimalTypeDefinition) {
329             length = Optional.empty();
330             pattern = ImmutableList.of();
331
332             final DecimalTypeDefinition decimal = (DecimalTypeDefinition)type;
333             final DecimalTypeDefinition base = decimal.getBaseType();
334             if (base != null && base.getBaseType() != null) {
335                 range = currentOrEmpty(decimal.getRangeConstraint(), base.getRangeConstraint());
336             } else {
337                 range = decimal.getRangeConstraint();
338             }
339         } else if (type instanceof RangeRestrictedTypeDefinition) {
340             // Integer-like types
341             length = Optional.empty();
342             pattern = ImmutableList.of();
343             range = extractRangeConstraint((RangeRestrictedTypeDefinition<?, ?>)type);
344         } else if (type instanceof StringTypeDefinition) {
345             final StringTypeDefinition string = (StringTypeDefinition)type;
346             final StringTypeDefinition base = string.getBaseType();
347             if (base != null && base.getBaseType() != null) {
348                 length = currentOrEmpty(string.getLengthConstraint(), base.getLengthConstraint());
349             } else {
350                 length = string.getLengthConstraint();
351             }
352
353             pattern = uniquePatterns(string);
354             range = Optional.empty();
355         } else {
356             length = Optional.empty();
357             pattern = ImmutableList.of();
358             range = Optional.empty();
359         }
360
361         // Now, this may have ended up being empty, too...
362         if (!length.isPresent() && pattern.isEmpty() && !range.isPresent()) {
363             return EMPTY_RESTRICTIONS;
364         }
365
366         // Nope, not empty allocate a holder
367         return new Restrictions() {
368             @Override
369             public Optional<? extends RangeConstraint<?>> getRangeConstraint() {
370                 return range;
371             }
372
373             @Override
374             public List<PatternConstraint> getPatternConstraints() {
375                 return pattern;
376             }
377
378             @Override
379             public Optional<LengthConstraint> getLengthConstraint() {
380                 return length;
381             }
382
383             @Override
384             public boolean isEmpty() {
385                 return false;
386             }
387         };
388     }
389
390     private static <T extends RangeRestrictedTypeDefinition<?, ?>> Optional<? extends RangeConstraint<?>>
391             extractRangeConstraint(final T def) {
392         final T base = (T) def.getBaseType();
393         if (base != null && base.getBaseType() != null) {
394             return currentOrEmpty(def.getRangeConstraint(), base.getRangeConstraint());
395         }
396
397         return def.getRangeConstraint();
398     }
399
400     /**
401      * Encodes angle brackets in yang statement description.
402      *
403      * @param description description of a yang statement which is used to generate javadoc comments
404      * @return string with encoded angle brackets
405      */
406     public static String encodeAngleBrackets(String description) {
407         if (description != null) {
408             description = LT_MATCHER.replaceFrom(description, "&lt;");
409             description = GT_MATCHER.replaceFrom(description, "&gt;");
410         }
411         return description;
412     }
413
414     public static String replaceAllIllegalChars(final CharSequence stringBuilder) {
415         final String ret = UNICODE_CHAR_PATTERN.matcher(stringBuilder).replaceAll("\\\\\\\\u");
416         return ret.isEmpty() ? "" : ret;
417     }
418 }