2 * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.mdsal.binding.model.util;
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.ImmutableSet;
15 import com.google.common.collect.Iterables;
16 import java.io.ByteArrayOutputStream;
17 import java.io.DataOutputStream;
18 import java.io.IOException;
19 import java.security.MessageDigest;
20 import java.security.NoSuchAlgorithmException;
21 import java.util.ArrayList;
22 import java.util.Collection;
23 import java.util.Collections;
24 import java.util.Comparator;
25 import java.util.Iterator;
26 import java.util.List;
27 import java.util.Optional;
28 import java.util.regex.Pattern;
29 import org.opendaylight.mdsal.binding.model.api.AccessModifier;
30 import org.opendaylight.mdsal.binding.model.api.Restrictions;
31 import org.opendaylight.mdsal.binding.model.api.Type;
32 import org.opendaylight.mdsal.binding.model.api.type.builder.GeneratedPropertyBuilder;
33 import org.opendaylight.mdsal.binding.model.api.type.builder.GeneratedTypeBuilderBase;
34 import org.opendaylight.mdsal.binding.model.api.type.builder.MethodSignatureBuilder;
35 import org.opendaylight.mdsal.binding.model.api.type.builder.TypeMemberBuilder;
36 import org.opendaylight.mdsal.binding.spec.naming.BindingMapping;
37 import org.opendaylight.yangtools.yang.common.QName;
38 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
39 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
40 import org.opendaylight.yangtools.yang.model.api.type.BinaryTypeDefinition;
41 import org.opendaylight.yangtools.yang.model.api.type.DecimalTypeDefinition;
42 import org.opendaylight.yangtools.yang.model.api.type.LengthConstraint;
43 import org.opendaylight.yangtools.yang.model.api.type.PatternConstraint;
44 import org.opendaylight.yangtools.yang.model.api.type.RangeConstraint;
45 import org.opendaylight.yangtools.yang.model.api.type.RangeRestrictedTypeDefinition;
46 import org.opendaylight.yangtools.yang.model.api.type.StringTypeDefinition;
47 import org.opendaylight.yangtools.yang.model.ri.type.BaseTypes;
48 import org.opendaylight.yangtools.yang.model.ri.type.DecimalTypeBuilder;
51 * Contains the methods for converting strings to valid JAVA language strings
52 * (package names, class names, attribute names) and to valid javadoc comments.
54 public final class BindingGeneratorUtil {
57 * Impossible to instantiate this class. All of the methods or attributes are static.
59 private BindingGeneratorUtil() {
64 * Pre-compiled replacement pattern.
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");
71 private static final Restrictions EMPTY_RESTRICTIONS = new Restrictions() {
73 public Optional<LengthConstraint> getLengthConstraint() {
74 return Optional.empty();
78 public List<PatternConstraint> getPatternConstraints() {
79 return Collections.emptyList();
83 public Optional<RangeConstraint<?>> getRangeConstraint() {
84 return Optional.empty();
88 public boolean isEmpty() {
93 private static final Comparator<TypeMemberBuilder<?>> SUID_MEMBER_COMPARATOR =
94 Comparator.comparing(TypeMemberBuilder::getName);
96 private static final Comparator<Type> SUID_NAME_COMPARATOR = Comparator.comparing(Type::getFullyQualifiedName);
98 private static final ImmutableSet<?> IGNORED_INTERFACES =
99 ImmutableSet.of(BindingTypes.TYPE_OBJECT, BindingTypes.SCALAR_TYPE_OBJECT);
102 * Converts <code>parameterName</code> to valid JAVA parameter name. If the <code>parameterName</code> is one
103 * of the JAVA reserved words then it is prefixed with underscore character.
105 * @param parameterName string with the parameter name
106 * @return string with the admissible parameter name
108 public static String resolveJavaReservedWordEquivalency(final String parameterName) {
109 if (parameterName != null && BindingMapping.JAVA_RESERVED_WORDS.contains(parameterName)) {
110 return "_" + parameterName;
112 return parameterName;
116 * Creates package name from specified <code>basePackageName</code> (package name for module)
117 * and <code>schemaPath</code>. Resulting package name is concatenation of <code>basePackageName</code>
118 * and all local names of YANG nodes which are parents of some node for which <code>schemaPath</code> is specified.
120 * @param basePackageName string with package name of the module, MUST be normalized, otherwise this method may
121 * return an invalid string.
122 * @param schemaPath list of names of YANG nodes which are parents of some node + name of this node
123 * @return string with valid JAVA package name
124 * @throws NullPointerException if any of the arguments are null
126 public static String packageNameForGeneratedType(final String basePackageName, final SchemaPath schemaPath) {
127 final int size = Iterables.size(schemaPath.getPathTowardsRoot()) - 1;
129 return basePackageName;
132 return generateNormalizedPackageName(basePackageName, schemaPath.getPathFromRoot(), size);
136 * Creates package name from specified <code>basePackageName</code> (package name for module)
137 * and <code>schemaPath</code> which crosses an augmentation. Resulting package name is concatenation
138 * of <code>basePackageName</code> and all local names of YANG nodes which are parents of some node for which
139 * <code>schemaPath</code> is specified.
141 * @param basePackageName string with package name of the module, MUST be normalized, otherwise this method may
142 * return an invalid string.
143 * @param schemaPath list of names of YANG nodes which are parents of some node + name of this node
144 * @return string with valid JAVA package name
145 * @throws NullPointerException if any of the arguments are null
147 public static String packageNameForAugmentedGeneratedType(final String basePackageName,
148 final SchemaPath schemaPath) {
149 final int size = Iterables.size(schemaPath.getPathTowardsRoot());
151 return basePackageName;
154 return generateNormalizedPackageName(basePackageName, schemaPath.getPathFromRoot(), size);
157 private static String generateNormalizedPackageName(final String base, final Iterable<QName> path, final int size) {
158 final StringBuilder builder = new StringBuilder(base);
159 final Iterator<QName> iterator = path.iterator();
160 for (int i = 0; i < size; ++i) {
162 final String nodeLocalName = iterator.next().getLocalName();
163 // FIXME: Collon ":" is invalid in node local name as per RFC6020, identifier statement.
164 builder.append(DASH_COLON_MATCHER.replaceFrom(nodeLocalName, '.'));
166 return BindingMapping.normalizePackageName(builder.toString());
169 private static <T> Iterable<T> sortedCollection(final Comparator<? super T> comparator, final Collection<T> input) {
170 if (input.size() <= 1) {
174 final List<T> ret = new ArrayList<>(input);
175 ret.sort(comparator);
179 private static final ThreadLocal<MessageDigest> SHA1_MD = ThreadLocal.withInitial(() -> {
181 return MessageDigest.getInstance("SHA");
182 } catch (final NoSuchAlgorithmException e) {
183 throw new IllegalStateException("Failed to get a SHA digest provider", e);
187 public static long computeDefaultSUID(final GeneratedTypeBuilderBase<?> to) {
188 final ByteArrayOutputStream bout = new ByteArrayOutputStream();
189 try (DataOutputStream dout = new DataOutputStream(bout)) {
190 dout.writeUTF(to.getName());
191 dout.writeInt(to.isAbstract() ? 3 : 7);
193 for (final Type ifc : sortedCollection(SUID_NAME_COMPARATOR, filteredImplementsTypes(to))) {
194 dout.writeUTF(ifc.getFullyQualifiedName());
197 for (final GeneratedPropertyBuilder gp : sortedCollection(SUID_MEMBER_COMPARATOR, to.getProperties())) {
198 dout.writeUTF(gp.getName());
201 for (final MethodSignatureBuilder m : sortedCollection(SUID_MEMBER_COMPARATOR, to.getMethodDefinitions())) {
202 if (!m.getAccessModifier().equals(AccessModifier.PRIVATE)) {
203 dout.writeUTF(m.getName());
204 dout.write(m.getAccessModifier().ordinal());
209 } catch (final IOException e) {
210 throw new IllegalStateException("Failed to hash object " + to, e);
213 final byte[] hashBytes = SHA1_MD.get().digest(bout.toByteArray());
215 for (int i = Math.min(hashBytes.length, 8) - 1; i >= 0; i--) {
216 hash = hash << 8 | hashBytes[i] & 0xFF;
221 private static Collection<Type> filteredImplementsTypes(final GeneratedTypeBuilderBase<?> to) {
222 return Collections2.filter(to.getImplementsTypes(), item -> !IGNORED_INTERFACES.contains(item));
225 private static <T extends Optional<?>> T currentOrEmpty(final T current, final T base) {
226 return current.equals(base) ? (T)Optional.empty() : current;
229 private static boolean containsConstraint(final StringTypeDefinition type, final PatternConstraint constraint) {
230 for (StringTypeDefinition wlk = type; wlk != null; wlk = wlk.getBaseType()) {
231 if (wlk.getPatternConstraints().contains(constraint)) {
239 private static List<PatternConstraint> uniquePatterns(final StringTypeDefinition type) {
240 final List<PatternConstraint> constraints = type.getPatternConstraints();
241 if (constraints.isEmpty()) {
245 final Builder<PatternConstraint> builder = ImmutableList.builder();
246 boolean filtered = false;
247 for (final PatternConstraint c : constraints) {
248 if (containsConstraint(type.getBaseType(), c)) {
255 return filtered ? builder.build() : constraints;
258 public static Restrictions getRestrictions(final TypeDefinition<?> type) {
259 // Old parser generated types which actually contained based restrictions, but our code deals with that when
260 // binding to core Java types. Hence we'll emit empty restrictions for base types.
261 if (type == null || type.getBaseType() == null) {
262 // Handling of decimal64 has changed in the new parser. It contains range restrictions applied to the type
263 // directly, without an extended type. We need to capture such constraints. In order to retain behavior we
264 // need to analyze the new semantics and see if the constraints have been overridden. To do that we
265 // instantiate a temporary unconstrained type and compare them.
267 // FIXME: looking at the generated code it looks as though we need to pass the restrictions without
269 if (type instanceof DecimalTypeDefinition) {
270 final DecimalTypeDefinition decimal = (DecimalTypeDefinition) type;
271 final DecimalTypeBuilder tmpBuilder = BaseTypes.decimalTypeBuilder(decimal.getQName());
272 tmpBuilder.setFractionDigits(decimal.getFractionDigits());
273 final DecimalTypeDefinition tmp = tmpBuilder.build();
275 if (!tmp.getRangeConstraint().equals(decimal.getRangeConstraint())) {
276 return new Restrictions() {
278 public boolean isEmpty() {
283 public Optional<? extends RangeConstraint<?>> getRangeConstraint() {
284 return decimal.getRangeConstraint();
288 public List<PatternConstraint> getPatternConstraints() {
289 return ImmutableList.of();
293 public Optional<LengthConstraint> getLengthConstraint() {
294 return Optional.empty();
300 return EMPTY_RESTRICTIONS;
303 final Optional<LengthConstraint> length;
304 final List<PatternConstraint> pattern;
305 final Optional<? extends RangeConstraint<?>> range;
308 * Take care of extended types.
310 * Other types which support constraints are check afterwards. There is a slight twist with them, as returned
311 * constraints are the effective view, e.g. they are inherited from base type. Since the constraint is already
312 * enforced by the base type, we want to skip them and not perform duplicate checks.
314 * We end up emitting ConcreteType instances for YANG base types, which leads to their constraints not being
315 * enforced (most notably decimal64). Therefore we need to make sure we do not strip the next-to-last
318 * FIXME: this probably not the best solution and needs further analysis.
320 if (type instanceof BinaryTypeDefinition) {
321 final BinaryTypeDefinition binary = (BinaryTypeDefinition)type;
322 final BinaryTypeDefinition base = binary.getBaseType();
323 if (base != null && base.getBaseType() != null) {
324 length = currentOrEmpty(binary.getLengthConstraint(), base.getLengthConstraint());
326 length = binary.getLengthConstraint();
329 pattern = ImmutableList.of();
330 range = Optional.empty();
331 } else if (type instanceof DecimalTypeDefinition) {
332 length = Optional.empty();
333 pattern = ImmutableList.of();
335 final DecimalTypeDefinition decimal = (DecimalTypeDefinition)type;
336 final DecimalTypeDefinition base = decimal.getBaseType();
337 if (base != null && base.getBaseType() != null) {
338 range = currentOrEmpty(decimal.getRangeConstraint(), base.getRangeConstraint());
340 range = decimal.getRangeConstraint();
342 } else if (type instanceof RangeRestrictedTypeDefinition) {
343 // Integer-like types
344 length = Optional.empty();
345 pattern = ImmutableList.of();
346 range = extractRangeConstraint((RangeRestrictedTypeDefinition<?, ?>)type);
347 } else if (type instanceof StringTypeDefinition) {
348 final StringTypeDefinition string = (StringTypeDefinition)type;
349 final StringTypeDefinition base = string.getBaseType();
350 if (base != null && base.getBaseType() != null) {
351 length = currentOrEmpty(string.getLengthConstraint(), base.getLengthConstraint());
353 length = string.getLengthConstraint();
356 pattern = uniquePatterns(string);
357 range = Optional.empty();
359 length = Optional.empty();
360 pattern = ImmutableList.of();
361 range = Optional.empty();
364 // Now, this may have ended up being empty, too...
365 if (!length.isPresent() && pattern.isEmpty() && !range.isPresent()) {
366 return EMPTY_RESTRICTIONS;
369 // Nope, not empty allocate a holder
370 return new Restrictions() {
372 public Optional<? extends RangeConstraint<?>> getRangeConstraint() {
377 public List<PatternConstraint> getPatternConstraints() {
382 public Optional<LengthConstraint> getLengthConstraint() {
387 public boolean isEmpty() {
393 private static <T extends RangeRestrictedTypeDefinition<?, ?>> Optional<? extends RangeConstraint<?>>
394 extractRangeConstraint(final T def) {
395 final T base = (T) def.getBaseType();
396 if (base != null && base.getBaseType() != null) {
397 return currentOrEmpty(def.getRangeConstraint(), base.getRangeConstraint());
400 return def.getRangeConstraint();
404 * Encodes angle brackets in yang statement description.
406 * @param description description of a yang statement which is used to generate javadoc comments
407 * @return string with encoded angle brackets
409 public static String encodeAngleBrackets(String description) {
410 if (description != null) {
411 description = LT_MATCHER.replaceFrom(description, "<");
412 description = GT_MATCHER.replaceFrom(description, ">");
418 * Escape potential unicode references so that the resulting string is safe to put into a {@code .java} file. This
419 * processing is required to ensure this text we want to append does not end up with eligible backslashes. See
420 * <a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-3.html#jls-3.3">Java Language Specification</a>
421 * for more information.
423 * @param str Input string
424 * @return A string with all backslashes made ineligible
426 public static String replaceAllIllegalChars(final String str) {
427 final int backslash = str.indexOf('\\');
428 return backslash == -1 ? str : defangUnicodeEscapes(str);
431 private static String defangUnicodeEscapes(final String str) {
432 // TODO: we should be able to receive the first offset from the non-deprecated method and perform a manual
433 // check for eligibility and escape -- that would be faster I think.
434 final String ret = UNICODE_CHAR_PATTERN.matcher(str).replaceAll("\\\\\\\\u");
435 return ret.isEmpty() ? "" : ret;