Bump versions to 14.0.0-SNAPSHOT
[mdsal.git] / binding / mdsal-binding-generator / src / main / java / org / opendaylight / mdsal / binding / generator / 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;
9
10 import com.google.common.annotations.Beta;
11 import com.google.common.base.CharMatcher;
12 import com.google.common.collect.ImmutableList;
13 import java.util.List;
14 import java.util.Optional;
15 import java.util.regex.Pattern;
16 import org.opendaylight.mdsal.binding.model.api.Restrictions;
17 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
18 import org.opendaylight.yangtools.yang.model.api.type.BinaryTypeDefinition;
19 import org.opendaylight.yangtools.yang.model.api.type.DecimalTypeDefinition;
20 import org.opendaylight.yangtools.yang.model.api.type.LengthConstraint;
21 import org.opendaylight.yangtools.yang.model.api.type.PatternConstraint;
22 import org.opendaylight.yangtools.yang.model.api.type.RangeConstraint;
23 import org.opendaylight.yangtools.yang.model.api.type.RangeRestrictedTypeDefinition;
24 import org.opendaylight.yangtools.yang.model.api.type.StringTypeDefinition;
25 import org.opendaylight.yangtools.yang.model.ri.type.BaseTypes;
26 import org.opendaylight.yangtools.yang.model.ri.type.DecimalTypeBuilder;
27
28 /**
29  * Contains the methods for converting strings to valid JAVA language strings
30  * (package names, class names, attribute names) and to valid javadoc comments.
31  */
32 @Beta
33 public final class BindingGeneratorUtil {
34     /**
35      * Pre-compiled replacement pattern.
36      */
37     private static final CharMatcher GT_MATCHER = CharMatcher.is('>');
38     private static final CharMatcher LT_MATCHER = CharMatcher.is('<');
39     private static final Pattern UNICODE_CHAR_PATTERN = Pattern.compile("\\\\+u");
40
41     private BindingGeneratorUtil() {
42         // Hidden on purpose
43     }
44
45     public static Restrictions getRestrictions(final TypeDefinition<?> type) {
46         // Old parser generated types which actually contained based restrictions, but our code deals with that when
47         // binding to core Java types. Hence we'll emit empty restrictions for base types.
48         if (type == null || type.getBaseType() == null) {
49             // Handling of decimal64 has changed in the new parser. It contains range restrictions applied to the type
50             // directly, without an extended type. We need to capture such constraints. In order to retain behavior we
51             // need to analyze the new semantics and see if the constraints have been overridden. To do that we
52             // instantiate a temporary unconstrained type and compare them.
53             //
54             // FIXME: looking at the generated code it looks as though we need to pass the restrictions without
55             //        comparison
56             if (type instanceof DecimalTypeDefinition decimal) {
57                 final DecimalTypeBuilder tmpBuilder = BaseTypes.decimalTypeBuilder(decimal.getQName());
58                 tmpBuilder.setFractionDigits(decimal.getFractionDigits());
59                 final DecimalTypeDefinition tmp = tmpBuilder.build();
60
61                 if (!tmp.getRangeConstraint().equals(decimal.getRangeConstraint())) {
62                     return Restrictions.of(decimal.getRangeConstraint().orElse(null));
63                 }
64             }
65
66             return Restrictions.empty();
67         }
68
69
70         /*
71          * Take care of extended types.
72          *
73          * Other types which support constraints are check afterwards. There is a slight twist with them, as returned
74          * constraints are the effective view, e.g. they are inherited from base type. Since the constraint is already
75          * enforced by the base type, we want to skip them and not perform duplicate checks.
76          *
77          * We end up emitting ConcreteType instances for YANG base types, which leads to their constraints not being
78          * enforced (most notably decimal64). Therefore we need to make sure we do not strip the next-to-last
79          * restrictions.
80          *
81          * FIXME: this probably not the best solution and needs further analysis.
82          */
83         if (type instanceof BinaryTypeDefinition binary) {
84             final BinaryTypeDefinition base = binary.getBaseType();
85             final Optional<LengthConstraint> length;
86             if (base != null && base.getBaseType() != null) {
87                 length = currentOrEmpty(binary.getLengthConstraint(), base.getLengthConstraint());
88             } else {
89                 length = binary.getLengthConstraint();
90             }
91             return Restrictions.of(length.orElse(null));
92         } else if (type instanceof DecimalTypeDefinition decimal) {
93             final DecimalTypeDefinition base = decimal.getBaseType();
94             final Optional<? extends RangeConstraint<?>> range;
95             if (base != null && base.getBaseType() != null) {
96                 range = currentOrEmpty(decimal.getRangeConstraint(), base.getRangeConstraint());
97             } else {
98                 range = decimal.getRangeConstraint();
99             }
100             return Restrictions.of(range.orElse(null));
101         } else if (type instanceof RangeRestrictedTypeDefinition) {
102             // Integer-like types
103             return Restrictions.of(extractRangeConstraint((RangeRestrictedTypeDefinition<?, ?>) type).orElse(null));
104         } else if (type instanceof StringTypeDefinition string) {
105             final StringTypeDefinition base = string.getBaseType();
106             final Optional<LengthConstraint> length;
107             if (base != null && base.getBaseType() != null) {
108                 length = currentOrEmpty(string.getLengthConstraint(), base.getLengthConstraint());
109             } else {
110                 length = string.getLengthConstraint();
111             }
112             return Restrictions.of(uniquePatterns(string), length.orElse(null));
113         } else {
114             return Restrictions.empty();
115         }
116     }
117
118     private static <T extends RangeRestrictedTypeDefinition<?, ?>> Optional<? extends RangeConstraint<?>>
119             extractRangeConstraint(final T def) {
120         final T base = (T) def.getBaseType();
121         if (base != null && base.getBaseType() != null) {
122             return currentOrEmpty(def.getRangeConstraint(), base.getRangeConstraint());
123         }
124
125         return def.getRangeConstraint();
126     }
127
128     private static <T> Optional<T> currentOrEmpty(final Optional<T> current, final Optional<?> base) {
129         return current.equals(base) ? Optional.empty() : current;
130     }
131
132     private static boolean containsConstraint(final StringTypeDefinition type, final PatternConstraint constraint) {
133         for (StringTypeDefinition wlk = type; wlk != null; wlk = wlk.getBaseType()) {
134             if (wlk.getPatternConstraints().contains(constraint)) {
135                 return true;
136             }
137         }
138
139         return false;
140     }
141
142     private static List<PatternConstraint> uniquePatterns(final StringTypeDefinition type) {
143         final List<PatternConstraint> constraints = type.getPatternConstraints();
144         if (constraints.isEmpty()) {
145             return constraints;
146         }
147
148         final var builder = ImmutableList.<PatternConstraint>builder();
149         boolean filtered = false;
150         for (final PatternConstraint c : constraints) {
151             if (containsConstraint(type.getBaseType(), c)) {
152                 filtered = true;
153             } else {
154                 builder.add(c);
155             }
156         }
157
158         return filtered ? builder.build() : constraints;
159     }
160
161     /**
162      * Encodes angle brackets in yang statement description.
163      *
164      * @param description description of a yang statement which is used to generate javadoc comments
165      * @return string with encoded angle brackets
166      */
167     public static String encodeAngleBrackets(String description) {
168         if (description != null) {
169             description = LT_MATCHER.replaceFrom(description, "&lt;");
170             description = GT_MATCHER.replaceFrom(description, "&gt;");
171         }
172         return description;
173     }
174
175     /**
176      * Escape potential unicode references so that the resulting string is safe to put into a {@code .java} file. This
177      * processing is required to ensure this text we want to append does not end up with eligible backslashes. See
178      * <a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-3.html#jls-3.3">Java Language Specification</a>
179      * for more information.
180      *
181      * @param str Input string
182      * @return A string with all backslashes made ineligible
183      */
184     public static String replaceAllIllegalChars(final String str) {
185         final int backslash = str.indexOf('\\');
186         return backslash == -1 ? str : defangUnicodeEscapes(str);
187     }
188
189     private static String defangUnicodeEscapes(final String str) {
190         // TODO: we should be able to receive the first offset from the non-deprecated method and perform a manual
191         //       check for eligibility and escape -- that would be faster I think.
192         final String ret = UNICODE_CHAR_PATTERN.matcher(str).replaceAll("\\\\\\\\u");
193         return ret.isEmpty() ? "" : ret;
194     }
195 }