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