2 * Copyright (c) 2017 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
9 package org.opendaylight.mdsal.binding.javav2.generator.util;
11 import com.google.common.annotations.Beta;
12 import com.google.common.base.CharMatcher;
13 import com.google.common.base.Preconditions;
14 import com.google.common.collect.ImmutableList;
15 import com.google.common.collect.ImmutableList.Builder;
16 import com.google.common.collect.Interner;
17 import com.google.common.collect.Interners;
18 import com.google.common.collect.Iterables;
19 import java.io.ByteArrayOutputStream;
20 import java.io.DataOutputStream;
21 import java.io.IOException;
22 import java.math.BigDecimal;
23 import java.security.MessageDigest;
24 import java.security.NoSuchAlgorithmException;
25 import java.util.ArrayList;
26 import java.util.Collection;
27 import java.util.Collections;
28 import java.util.Comparator;
29 import java.util.Iterator;
30 import java.util.List;
31 import java.util.Optional;
32 import java.util.regex.Pattern;
33 import org.opendaylight.mdsal.binding.javav2.model.api.AccessModifier;
34 import org.opendaylight.mdsal.binding.javav2.model.api.Restrictions;
35 import org.opendaylight.mdsal.binding.javav2.model.api.Type;
36 import org.opendaylight.mdsal.binding.javav2.model.api.type.builder.GeneratedPropertyBuilder;
37 import org.opendaylight.mdsal.binding.javav2.model.api.type.builder.GeneratedTypeBuilderBase;
38 import org.opendaylight.mdsal.binding.javav2.model.api.type.builder.MethodSignatureBuilder;
39 import org.opendaylight.mdsal.binding.javav2.model.api.type.builder.TypeMemberBuilder;
40 import org.opendaylight.mdsal.binding.javav2.spec.runtime.BindingNamespaceType;
41 import org.opendaylight.yangtools.yang.common.QName;
42 import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
43 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
44 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
45 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
46 import org.opendaylight.yangtools.yang.model.api.type.BinaryTypeDefinition;
47 import org.opendaylight.yangtools.yang.model.api.type.DecimalTypeDefinition;
48 import org.opendaylight.yangtools.yang.model.api.type.LengthConstraint;
49 import org.opendaylight.yangtools.yang.model.api.type.PatternConstraint;
50 import org.opendaylight.yangtools.yang.model.api.type.RangeConstraint;
51 import org.opendaylight.yangtools.yang.model.api.type.RangeRestrictedTypeDefinition;
52 import org.opendaylight.yangtools.yang.model.api.type.StringTypeDefinition;
53 import org.opendaylight.yangtools.yang.model.util.type.BaseTypes;
54 import org.opendaylight.yangtools.yang.model.util.type.DecimalTypeBuilder;
57 * Standard Util class that contains various method for converting
58 * input strings to valid JAVA language strings e.g. package names,
59 * class names, attribute names and/or valid JavaDoc comments.
62 public final class BindingGeneratorUtil {
64 private static final CharMatcher GT_MATCHER = CharMatcher.is('>');
65 private static final CharMatcher LT_MATCHER = CharMatcher.is('<');
66 private static final Pattern UNICODE_CHAR_PATTERN = Pattern.compile("\\\\+u");
68 private static final Interner<String> PACKAGE_INTERNER = Interners.newWeakInterner();
69 private static final Comparator<TypeMemberBuilder<?>> SUID_MEMBER_COMPARATOR =
70 Comparator.comparing(TypeMemberBuilder::getName);
72 private static final Comparator<Type> SUID_NAME_COMPARATOR =
73 Comparator.comparing(Type::getFullyQualifiedName);
75 private static final Restrictions EMPTY_RESTRICTIONS = new Restrictions() {
77 public Optional<LengthConstraint> getLengthConstraint() {
78 return Optional.empty();
82 public List<PatternConstraint> getPatternConstraints() {
83 return Collections.emptyList();
87 public Optional<RangeConstraint<?>> getRangeConstraint() {
88 return Optional.empty();
92 public boolean isEmpty() {
97 private BindingGeneratorUtil() {
98 throw new UnsupportedOperationException("Utility class");
102 * Encodes angle brackets in yang statement description
103 * @param description description of a yang statement which is used to generate javadoc comments
104 * @return string with encoded angle brackets
106 public static String encodeAngleBrackets(final String description) {
107 String newDesc = description;
108 if (newDesc != null) {
109 newDesc = LT_MATCHER.replaceFrom(newDesc, "<");
110 newDesc = GT_MATCHER.replaceFrom(newDesc, ">");
115 public static long computeDefaultSUID(final GeneratedTypeBuilderBase<?> to) {
116 final ByteArrayOutputStream bout = new ByteArrayOutputStream();
117 try (final DataOutputStream dout = new DataOutputStream(bout)) {
118 dout.writeUTF(to.getName());
119 dout.writeInt(to.isAbstract() ? 3 : 7);
121 for (final Type ifc : sortedCollection(SUID_NAME_COMPARATOR, to.getImplementsTypes())) {
122 dout.writeUTF(ifc.getFullyQualifiedName());
125 for (final GeneratedPropertyBuilder gp : sortedCollection(SUID_MEMBER_COMPARATOR, to.getProperties())) {
126 dout.writeUTF(gp.getName());
129 for (final MethodSignatureBuilder m : sortedCollection(SUID_MEMBER_COMPARATOR, to.getMethodDefinitions())) {
130 if (!m.getAccessModifier().equals(AccessModifier.PRIVATE)) {
131 dout.writeUTF(m.getName());
132 dout.write(m.getAccessModifier().ordinal());
137 } catch (final IOException e) {
138 throw new IllegalStateException("Failed to hash object " + to, e);
141 final byte[] hashBytes = SHA1_MD.get().digest(bout.toByteArray());
143 for (int i = Math.min(hashBytes.length, 8) - 1; i >= 0; i--) {
144 hash = hash << 8 | hashBytes[i] & 0xFF;
150 * Creates package name from specified <code>basePackageName</code> (package
151 * name for module) and <code>schemaPath</code>.
153 * Resulting package name is concatenation of <code>basePackageName</code>
154 * and all local names of YANG nodes which are parents of some node for
155 * which <code>schemaPath</code> is specified.
157 * Based on type of node, there is also possible suffix added in order
158 * to prevent package name conflicts.
160 * @param basePackageName
161 * string with package name of the module, MUST be normalized,
162 * otherwise this method may return an invalid string.
164 * list of names of YANG nodes which are parents of some node +
166 * @return string with valid JAVA package name
167 * @throws NullPointerException if any of the arguments are null
169 public static String packageNameForGeneratedType(final String basePackageName, final SchemaPath schemaPath, final
170 BindingNamespaceType namespaceType) {
172 final Iterable<QName> pathTowardsRoot = schemaPath.getPathTowardsRoot();
173 final Iterable<QName> pathFromRoot = schemaPath.getPathFromRoot();
174 final int size = Iterables.size(pathTowardsRoot) - 1;
176 if (namespaceType != null) {
177 final StringBuilder sb = new StringBuilder();
178 sb.append(basePackageName)
180 .append(namespaceType.getPackagePrefix());
181 return JavaIdentifierNormalizer.normalizeFullPackageName(sb.toString());
183 return JavaIdentifierNormalizer.normalizeFullPackageName(basePackageName);
186 return generateNormalizedPackageName(basePackageName, pathFromRoot, size, namespaceType);
190 * Creates package name from specified <code>basePackageName</code> (package
191 * name for module) and <code>namespaceType</code>.
193 * Resulting package name is concatenation of <code>basePackageName</code>
194 * and prefix of <code>namespaceType</code>.
196 * @param basePackageName
197 * string with package name of the module, MUST be normalized,
198 * otherwise this method may return an invalid string.
199 * @param namespaceType
200 * the namespace to which the module belongs
201 * @return string with valid JAVA package name
202 * @throws NullPointerException if any of the arguments are null
204 public static String packageNameWithNamespacePrefix(final String basePackageName,
205 final BindingNamespaceType namespaceType) {
206 final StringBuilder sb = new StringBuilder();
207 sb.append(basePackageName)
209 .append(namespaceType.getPackagePrefix());
210 return JavaIdentifierNormalizer.normalizeFullPackageName(sb.toString());
213 public static Restrictions getRestrictions(final TypeDefinition<?> type) {
214 if (type == null || type.getBaseType() == null) {
215 if (type instanceof DecimalTypeDefinition) {
216 final DecimalTypeDefinition decimal = (DecimalTypeDefinition) type;
217 final DecimalTypeBuilder tmpBuilder = BaseTypes.decimalTypeBuilder(decimal.getPath());
218 tmpBuilder.setFractionDigits(decimal.getFractionDigits());
219 final DecimalTypeDefinition tmp = tmpBuilder.build();
221 if (!tmp.getRangeConstraint().equals(decimal.getRangeConstraint())) {
222 return new Restrictions() {
224 public boolean isEmpty() {
229 public Optional<RangeConstraint<BigDecimal>> getRangeConstraint() {
230 return decimal.getRangeConstraint();
234 public List<PatternConstraint> getPatternConstraints() {
235 return ImmutableList.of();
239 public Optional<LengthConstraint> getLengthConstraint() {
240 return Optional.empty();
246 return EMPTY_RESTRICTIONS;
249 final Optional<LengthConstraint> length;
250 final List<PatternConstraint> pattern;
251 final Optional<? extends RangeConstraint<?>> range;
254 * Take care of extended types.
256 * Other types which support constraints are check afterwards. There is a slight twist with them, as returned
257 * constraints are the effective view, e.g. they are inherited from base type. Since the constraint is already
258 * enforced by the base type, we want to skip them and not perform duplicate checks.
260 * We end up emitting ConcreteType instances for YANG base types, which leads to their constraints not being
261 * enforced (most notably decimal64). Therefore we need to make sure we do not strip the next-to-last
264 if (type instanceof BinaryTypeDefinition) {
265 final BinaryTypeDefinition binary = (BinaryTypeDefinition)type;
266 final BinaryTypeDefinition base = binary.getBaseType();
267 if (base != null && base.getBaseType() != null) {
268 length = currentOrEmpty(binary.getLengthConstraint(), base.getLengthConstraint());
270 length = binary.getLengthConstraint();
273 pattern = ImmutableList.of();
274 range = Optional.empty();
275 } else if (type instanceof DecimalTypeDefinition) {
276 length = Optional.empty();
277 pattern = ImmutableList.of();
279 final DecimalTypeDefinition decimal = (DecimalTypeDefinition)type;
280 final DecimalTypeDefinition base = decimal.getBaseType();
281 if (base != null && base.getBaseType() != null) {
282 range = currentOrEmpty(decimal.getRangeConstraint(), base.getRangeConstraint());
284 range = decimal.getRangeConstraint();
286 } else if (type instanceof RangeRestrictedTypeDefinition) {
287 // Integer-like types
288 length = Optional.empty();
289 pattern = ImmutableList.of();
290 range = extractRangeConstraint((RangeRestrictedTypeDefinition<?, ?>)type);
291 } else if (type instanceof StringTypeDefinition) {
292 final StringTypeDefinition string = (StringTypeDefinition)type;
293 final StringTypeDefinition base = string.getBaseType();
294 if (base != null && base.getBaseType() != null) {
295 length = currentOrEmpty(string.getLengthConstraint(), base.getLengthConstraint());
297 length = string.getLengthConstraint();
300 pattern = uniquePatterns(string);
301 range = Optional.empty();
303 length = Optional.empty();
304 pattern = ImmutableList.of();
305 range = Optional.empty();
308 // Now, this may have ended up being empty, too...
309 if (!length.isPresent() && pattern.isEmpty() && !range.isPresent()) {
310 return EMPTY_RESTRICTIONS;
313 // Nope, not empty allocate a holder
314 return new Restrictions() {
316 public Optional<? extends RangeConstraint<?>> getRangeConstraint() {
320 public List<PatternConstraint> getPatternConstraints() {
324 public Optional<LengthConstraint> getLengthConstraint() {
328 public boolean isEmpty() {
335 * Creates package name from specified <code>basePackageName</code> (package
336 * name for module) and <code>schemaPath</code> which crosses an augmentation.
338 * Resulting package name is concatenation of <code>basePackageName</code>
339 * and all local names of YANG nodes which are parents of some node for
340 * which <code>schemaPath</code> is specified.
342 * Based on type of node, there is also possible suffix added in order
343 * to prevent package name conflicts.
345 * @param basePackageName
346 * string with package name of the module, MUST be normalized,
347 * otherwise this method may return an invalid string.
349 * list of names of YANG nodes which are parents of some node +
351 * @return string with valid JAVA package name
352 * @throws NullPointerException if any of the arguments are null
354 public static String packageNameForAugmentedGeneratedType(final String basePackageName, final SchemaPath schemaPath) {
355 final Iterable<QName> pathTowardsRoot = schemaPath.getPathTowardsRoot();
356 final Iterable<QName> pathFromRoot = schemaPath.getPathFromRoot();
357 final int size = Iterables.size(pathTowardsRoot);
359 return basePackageName;
362 return generateNormalizedPackageName(basePackageName, pathFromRoot, size, BindingNamespaceType.Data);
366 * Creates package name from <code>parentAugmentPackageName</code> (package
367 * name for direct parent augmentation) and <code>augmentationSchema</code> .
369 * Resulting package name is concatenation of <code>parentAugmentPackageName</code>
370 * and the local name of <code>schemaPath</code>.
372 * Based on type of node, there is also possible suffix added in order
373 * to prevent package name conflicts.
375 * @param parentAugmentPackageName
376 * string with package name of direct parent augmentation, MUST be normalized,
377 * otherwise this method may return an invalid string.
378 * @param augmentationSchema
379 * augmentation schema which is direct son of parent augmentation.
380 * @return string with valid JAVA package name
381 * @throws NullPointerException if any of the arguments are null
383 public static String packageNameForAugmentedGeneratedType(final String parentAugmentPackageName,
384 final AugmentationSchemaNode augmentationSchema) {
385 final QName last = augmentationSchema.getTargetPath().getLastComponent();
387 return generateNormalizedPackageName(parentAugmentPackageName, last);
390 public static String packageNameForSubGeneratedType(final String basePackageName, final SchemaNode node,
391 final BindingNamespaceType namespaceType) {
392 final String parent = packageNameForGeneratedType(basePackageName, node.getPath(), namespaceType);
393 final QName last = node.getPath().getLastComponent();
395 return generateNormalizedPackageName(parent, last);
398 public static String replacePackageTopNamespace(final String basePackageName,
399 final String toReplacePackageName,
400 final BindingNamespaceType toReplaceNameSpace,
401 final BindingNamespaceType replacedNameSpace) {
402 Preconditions.checkArgument(basePackageName != null);
403 String normalizeBasePackageName = JavaIdentifierNormalizer.normalizeFullPackageName(basePackageName);
405 if (!normalizeBasePackageName.equals(toReplacePackageName)) {
406 final String topPackageName = new StringBuilder(normalizeBasePackageName)
407 .append('.').append(toReplaceNameSpace.getPackagePrefix()).toString();
409 Preconditions.checkState(toReplacePackageName.equals(topPackageName)
410 || toReplacePackageName.contains(topPackageName),
411 "Package name to replace does not belong to the given namespace to replace!");
413 return new StringBuilder(normalizeBasePackageName)
415 .append(replacedNameSpace.getPackagePrefix())
416 .append(toReplacePackageName.substring(topPackageName.length()))
419 return new StringBuilder(normalizeBasePackageName)
420 .append('.').append(replacedNameSpace.getPackagePrefix()).toString();
424 private static final ThreadLocal<MessageDigest> SHA1_MD = new ThreadLocal<MessageDigest>() {
426 protected MessageDigest initialValue() {
428 return MessageDigest.getInstance("SHA");
429 } catch (final NoSuchAlgorithmException e) {
430 throw new IllegalStateException("Failed to get a SHA digest provider", e);
435 private static String generateNormalizedPackageName(final String base, final Iterable<QName> path, final int
436 size, final BindingNamespaceType namespaceType) {
437 final StringBuilder builder = new StringBuilder(base);
438 if (namespaceType != null) {
439 builder.append('.').append(namespaceType.getPackagePrefix());
441 final Iterator<QName> iterator = path.iterator();
442 for (int i = 0; i < size; ++i) {
444 final String nodeLocalName = iterator.next().getLocalName();
445 builder.append(nodeLocalName);
447 final String normalizedPackageName = JavaIdentifierNormalizer.normalizeFullPackageName(builder.toString());
448 // Prevent duplication of input
449 PACKAGE_INTERNER.intern(normalizedPackageName);
450 return normalizedPackageName;
453 private static String generateNormalizedPackageName(final String parent, final QName path) {
454 final StringBuilder sb = new StringBuilder(parent)
456 .append(path.getLocalName());
458 final String normalizedPackageName = JavaIdentifierNormalizer.normalizeFullPackageName(sb.toString());
459 // Prevent duplication of input
460 PACKAGE_INTERNER.intern(normalizedPackageName);
461 return normalizedPackageName;
464 private static <T> Iterable<T> sortedCollection(final Comparator<? super T> comparator, final Collection<T> input) {
465 if (input.size() > 1) {
466 final List<T> ret = new ArrayList<>(input);
467 ret.sort(comparator);
474 private static <T extends Optional<?>> T currentOrEmpty(final T current, final T base) {
475 return current.equals(base) ? (T)Optional.empty() : current;
478 private static List<PatternConstraint> uniquePatterns(final StringTypeDefinition type) {
479 final List<PatternConstraint> constraints = type.getPatternConstraints();
480 if (constraints.isEmpty()) {
484 final Builder<PatternConstraint> builder = ImmutableList.builder();
485 boolean filtered = false;
486 for (final PatternConstraint c : constraints) {
487 if (containsConstraint(type.getBaseType(), c)) {
494 return filtered ? builder.build() : constraints;
497 private static boolean containsConstraint(final StringTypeDefinition type, final PatternConstraint constraint) {
498 for (StringTypeDefinition wlk = type; wlk != null; wlk = wlk.getBaseType()) {
499 if (wlk.getPatternConstraints().contains(constraint)) {
507 private static <T extends RangeRestrictedTypeDefinition<?, ?>> Optional<? extends RangeConstraint<?>>
508 extractRangeConstraint(final T def) {
509 final T base = (T) def.getBaseType();
510 if (base != null && base.getBaseType() != null) {
511 return currentOrEmpty(def.getRangeConstraint(), base.getRangeConstraint());
514 return def.getRangeConstraint();
517 public static String replaceAllIllegalChars(final CharSequence stringBuilder){
518 final String ret = UNICODE_CHAR_PATTERN.matcher(stringBuilder).replaceAll("\\\\\\\\u");
519 return ret.isEmpty() ? "" : ret;