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.security.MessageDigest;
23 import java.security.NoSuchAlgorithmException;
24 import java.util.ArrayList;
25 import java.util.Collection;
26 import java.util.Collections;
27 import java.util.Comparator;
28 import java.util.Iterator;
29 import java.util.List;
30 import org.opendaylight.mdsal.binding.javav2.model.api.AccessModifier;
31 import org.opendaylight.mdsal.binding.javav2.model.api.Restrictions;
32 import org.opendaylight.mdsal.binding.javav2.model.api.Type;
33 import org.opendaylight.mdsal.binding.javav2.model.api.type.builder.GeneratedPropertyBuilder;
34 import org.opendaylight.mdsal.binding.javav2.model.api.type.builder.GeneratedTypeBuilderBase;
35 import org.opendaylight.mdsal.binding.javav2.model.api.type.builder.MethodSignatureBuilder;
36 import org.opendaylight.mdsal.binding.javav2.model.api.type.builder.TypeMemberBuilder;
37 import org.opendaylight.mdsal.binding.javav2.spec.runtime.BindingNamespaceType;
38 import org.opendaylight.yangtools.yang.common.QName;
39 import org.opendaylight.yangtools.yang.model.api.AugmentationSchema;
40 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
41 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
42 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
43 import org.opendaylight.yangtools.yang.model.api.type.BinaryTypeDefinition;
44 import org.opendaylight.yangtools.yang.model.api.type.DecimalTypeDefinition;
45 import org.opendaylight.yangtools.yang.model.api.type.IntegerTypeDefinition;
46 import org.opendaylight.yangtools.yang.model.api.type.LengthConstraint;
47 import org.opendaylight.yangtools.yang.model.api.type.PatternConstraint;
48 import org.opendaylight.yangtools.yang.model.api.type.RangeConstraint;
49 import org.opendaylight.yangtools.yang.model.api.type.StringTypeDefinition;
50 import org.opendaylight.yangtools.yang.model.api.type.UnsignedIntegerTypeDefinition;
51 import org.opendaylight.yangtools.yang.model.util.type.BaseTypes;
52 import org.opendaylight.yangtools.yang.model.util.type.DecimalTypeBuilder;
55 * Standard Util class that contains various method for converting
56 * input strings to valid JAVA language strings e.g. package names,
57 * class names, attribute names and/or valid JavaDoc comments.
60 public final class BindingGeneratorUtil {
62 private static final CharMatcher GT_MATCHER = CharMatcher.is('>');
63 private static final CharMatcher LT_MATCHER = CharMatcher.is('<');
65 private static final Interner<String> PACKAGE_INTERNER = Interners.newWeakInterner();
66 private static final Comparator<TypeMemberBuilder<?>> SUID_MEMBER_COMPARATOR =
67 Comparator.comparing(TypeMemberBuilder::getName);
69 private static final Comparator<Type> SUID_NAME_COMPARATOR =
70 Comparator.comparing(Type::getFullyQualifiedName);
72 private static final Restrictions EMPTY_RESTRICTIONS = new Restrictions() {
74 public List<LengthConstraint> getLengthConstraints() {
75 return Collections.emptyList();
79 public List<PatternConstraint> getPatternConstraints() {
80 return Collections.emptyList();
84 public List<RangeConstraint> getRangeConstraints() {
85 return Collections.emptyList();
89 public boolean isEmpty() {
94 private BindingGeneratorUtil() {
95 throw new UnsupportedOperationException("Utility class");
99 * Encodes angle brackets in yang statement description
100 * @param description description of a yang statement which is used to generate javadoc comments
101 * @return string with encoded angle brackets
103 public static String encodeAngleBrackets(final String description) {
104 String newDesc = description;
105 if (newDesc != null) {
106 newDesc = LT_MATCHER.replaceFrom(newDesc, "<");
107 newDesc = GT_MATCHER.replaceFrom(newDesc, ">");
112 public static long computeDefaultSUID(final GeneratedTypeBuilderBase<?> to) {
113 final ByteArrayOutputStream bout = new ByteArrayOutputStream();
114 try (final DataOutputStream dout = new DataOutputStream(bout)) {
115 dout.writeUTF(to.getName());
116 dout.writeInt(to.isAbstract() ? 3 : 7);
118 for (final Type ifc : sortedCollection(SUID_NAME_COMPARATOR, to.getImplementsTypes())) {
119 dout.writeUTF(ifc.getFullyQualifiedName());
122 for (final GeneratedPropertyBuilder gp : sortedCollection(SUID_MEMBER_COMPARATOR, to.getProperties())) {
123 dout.writeUTF(gp.getName());
126 for (final MethodSignatureBuilder m : sortedCollection(SUID_MEMBER_COMPARATOR, to.getMethodDefinitions())) {
127 if (!(m.getAccessModifier().equals(AccessModifier.PRIVATE))) {
128 dout.writeUTF(m.getName());
129 dout.write(m.getAccessModifier().ordinal());
134 } catch (final IOException e) {
135 throw new IllegalStateException("Failed to hash object " + to, e);
138 final byte[] hashBytes = SHA1_MD.get().digest(bout.toByteArray());
140 for (int i = Math.min(hashBytes.length, 8) - 1; i >= 0; i--) {
141 hash = (hash << 8) | (hashBytes[i] & 0xFF);
147 * Creates package name from specified <code>basePackageName</code> (package
148 * name for module) and <code>schemaPath</code>.
150 * Resulting package name is concatenation of <code>basePackageName</code>
151 * and all local names of YANG nodes which are parents of some node for
152 * which <code>schemaPath</code> is specified.
154 * Based on type of node, there is also possible suffix added in order
155 * to prevent package name conflicts.
157 * @param basePackageName
158 * string with package name of the module, MUST be normalized,
159 * otherwise this method may return an invalid string.
161 * list of names of YANG nodes which are parents of some node +
163 * @return string with valid JAVA package name
164 * @throws NullPointerException if any of the arguments are null
166 public static String packageNameForGeneratedType(final String basePackageName, final SchemaPath schemaPath, final
167 BindingNamespaceType namespaceType) {
169 final Iterable<QName> pathTowardsRoot = schemaPath.getPathTowardsRoot();
170 final Iterable<QName> pathFromRoot = schemaPath.getPathFromRoot();
171 final int size = Iterables.size(pathTowardsRoot) - 1;
173 if (namespaceType != null) {
174 final StringBuilder sb = new StringBuilder();
175 sb.append(basePackageName)
177 .append(namespaceType.getPackagePrefix());
178 return JavaIdentifierNormalizer.normalizeFullPackageName(sb.toString());
180 return JavaIdentifierNormalizer.normalizeFullPackageName(basePackageName);
183 return generateNormalizedPackageName(basePackageName, pathFromRoot, size, namespaceType);
187 * Creates package name from specified <code>basePackageName</code> (package
188 * name for module) and <code>namespaceType</code>.
190 * Resulting package name is concatenation of <code>basePackageName</code>
191 * and prefix of <code>namespaceType</code>.
193 * @param basePackageName
194 * string with package name of the module, MUST be normalized,
195 * otherwise this method may return an invalid string.
196 * @param namespaceType
197 * the namespace to which the module belongs
198 * @return string with valid JAVA package name
199 * @throws NullPointerException if any of the arguments are null
201 public static String packageNameWithNamespacePrefix(final String basePackageName, final
202 BindingNamespaceType namespaceType) {
203 final StringBuilder sb = new StringBuilder();
204 sb.append(basePackageName)
206 .append(namespaceType.getPackagePrefix());
207 return JavaIdentifierNormalizer.normalizeFullPackageName(sb.toString());
210 public static Restrictions getRestrictions(final TypeDefinition<?> type) {
211 if ((type == null) || (type.getBaseType() == null)) {
212 if (type instanceof DecimalTypeDefinition) {
213 final DecimalTypeDefinition decimal = (DecimalTypeDefinition) type;
214 final DecimalTypeBuilder tmpBuilder = BaseTypes.decimalTypeBuilder(decimal.getPath());
215 tmpBuilder.setFractionDigits(decimal.getFractionDigits());
216 final DecimalTypeDefinition tmp = tmpBuilder.build();
218 if (!tmp.getRangeConstraints().equals(decimal.getRangeConstraints())) {
219 return new Restrictions() {
221 public boolean isEmpty() {
226 public List<RangeConstraint> getRangeConstraints() {
227 return decimal.getRangeConstraints();
231 public List<PatternConstraint> getPatternConstraints() {
232 return ImmutableList.of();
236 public List<LengthConstraint> getLengthConstraints() {
237 return ImmutableList.of();
243 return EMPTY_RESTRICTIONS;
246 final List<LengthConstraint> length;
247 final List<PatternConstraint> pattern;
248 final List<RangeConstraint> range;
251 * Take care of extended types.
253 * Other types which support constraints are check afterwards. There is a slight twist with them, as returned
254 * constraints are the effective view, e.g. they are inherited from base type. Since the constraint is already
255 * enforced by the base type, we want to skip them and not perform duplicate checks.
257 * We end up emitting ConcreteType instances for YANG base types, which leads to their constraints not being
258 * enforced (most notably decimal64). Therefore we need to make sure we do not strip the next-to-last
261 if (type instanceof BinaryTypeDefinition) {
262 final BinaryTypeDefinition binary = (BinaryTypeDefinition)type;
263 final BinaryTypeDefinition base = binary.getBaseType();
264 if ((base != null) && (base.getBaseType() != null)) {
265 length = currentOrEmpty(binary.getLengthConstraints(), base.getLengthConstraints());
267 length = binary.getLengthConstraints();
270 pattern = ImmutableList.of();
271 range = ImmutableList.of();
272 } else if (type instanceof DecimalTypeDefinition) {
273 length = ImmutableList.of();
274 pattern = ImmutableList.of();
276 final DecimalTypeDefinition decimal = (DecimalTypeDefinition)type;
277 final DecimalTypeDefinition base = decimal.getBaseType();
278 if ((base != null) && (base.getBaseType() != null)) {
279 range = currentOrEmpty(decimal.getRangeConstraints(), base.getRangeConstraints());
281 range = decimal.getRangeConstraints();
283 } else if (type instanceof IntegerTypeDefinition) {
284 length = ImmutableList.of();
285 pattern = ImmutableList.of();
287 final IntegerTypeDefinition integer = (IntegerTypeDefinition)type;
288 final IntegerTypeDefinition base = integer.getBaseType();
289 if ((base != null) && (base.getBaseType() != null)) {
290 range = currentOrEmpty(integer.getRangeConstraints(), base.getRangeConstraints());
292 range = integer.getRangeConstraints();
294 } else if (type instanceof StringTypeDefinition) {
295 final StringTypeDefinition string = (StringTypeDefinition)type;
296 final StringTypeDefinition base = string.getBaseType();
297 if ((base != null) && (base.getBaseType() != null)) {
298 length = currentOrEmpty(string.getLengthConstraints(), base.getLengthConstraints());
300 length = string.getLengthConstraints();
303 pattern = uniquePatterns(string);
304 range = ImmutableList.of();
305 } else if (type instanceof UnsignedIntegerTypeDefinition) {
306 length = ImmutableList.of();
307 pattern = ImmutableList.of();
309 final UnsignedIntegerTypeDefinition unsigned = (UnsignedIntegerTypeDefinition)type;
310 final UnsignedIntegerTypeDefinition base = unsigned.getBaseType();
311 if ((base != null) && (base.getBaseType() != null)) {
312 range = currentOrEmpty(unsigned.getRangeConstraints(), base.getRangeConstraints());
314 range = unsigned.getRangeConstraints();
317 length = ImmutableList.of();
318 pattern = ImmutableList.of();
319 range = ImmutableList.of();
322 // Now, this may have ended up being empty, too...
323 if (length.isEmpty() && pattern.isEmpty() && range.isEmpty()) {
324 return EMPTY_RESTRICTIONS;
327 // Nope, not empty allocate a holder
328 return new Restrictions() {
330 public List<RangeConstraint> getRangeConstraints() {
334 public List<PatternConstraint> getPatternConstraints() {
338 public List<LengthConstraint> getLengthConstraints() {
342 public boolean isEmpty() {
349 * Creates package name from specified <code>basePackageName</code> (package
350 * name for module) and <code>schemaPath</code> which crosses an augmentation.
352 * Resulting package name is concatenation of <code>basePackageName</code>
353 * and all local names of YANG nodes which are parents of some node for
354 * which <code>schemaPath</code> is specified.
356 * Based on type of node, there is also possible suffix added in order
357 * to prevent package name conflicts.
359 * @param basePackageName
360 * string with package name of the module, MUST be normalized,
361 * otherwise this method may return an invalid string.
363 * list of names of YANG nodes which are parents of some node +
365 * @return string with valid JAVA package name
366 * @throws NullPointerException if any of the arguments are null
368 public static String packageNameForAugmentedGeneratedType(final String basePackageName, final SchemaPath schemaPath) {
369 final Iterable<QName> pathTowardsRoot = schemaPath.getPathTowardsRoot();
370 final Iterable<QName> pathFromRoot = schemaPath.getPathFromRoot();
371 final int size = Iterables.size(pathTowardsRoot);
373 return basePackageName;
376 return generateNormalizedPackageName(basePackageName, pathFromRoot, size, BindingNamespaceType.Data);
380 * Creates package name from <code>parentAugmentPackageName</code> (package
381 * name for direct parent augmentation) and <code>augmentationSchema</code> .
383 * Resulting package name is concatenation of <code>parentAugmentPackageName</code>
384 * and the local name of <code>schemaPath</code>.
386 * Based on type of node, there is also possible suffix added in order
387 * to prevent package name conflicts.
389 * @param parentAugmentPackageName
390 * string with package name of direct parent augmentation, MUST be normalized,
391 * otherwise this method may return an invalid string.
392 * @param augmentationSchema
393 * augmentation schema which is direct son of parent augmentation.
394 * @return string with valid JAVA package name
395 * @throws NullPointerException if any of the arguments are null
397 public static String packageNameForAugmentedGeneratedType(final String parentAugmentPackageName,
398 final AugmentationSchema augmentationSchema) {
399 final QName last = augmentationSchema.getTargetPath().getLastComponent();
401 return generateNormalizedPackageName(parentAugmentPackageName, last);
404 public static String packageNameForSubGeneratedType(final String basePackageName, final SchemaNode node,
405 final BindingNamespaceType namespaceType) {
406 final String parent = packageNameForGeneratedType(basePackageName, node.getPath(), namespaceType);
407 final QName last = node.getPath().getLastComponent();
409 return generateNormalizedPackageName(parent, last);
412 public static String replacePackageTopNamespace(final String basePackageName,
413 final String toReplacePackageName,
414 final BindingNamespaceType toReplaceNameSpace,
415 final BindingNamespaceType replacedNameSpace) {
416 Preconditions.checkArgument(basePackageName != null);
417 String normalizeBasePackageName = JavaIdentifierNormalizer.normalizeFullPackageName(basePackageName);
419 if (!normalizeBasePackageName.equals(toReplacePackageName)) {
420 final String topPackageName = new StringBuilder(normalizeBasePackageName)
421 .append('.').append(toReplaceNameSpace.getPackagePrefix()).toString();
423 Preconditions.checkState(toReplacePackageName.equals(topPackageName)
424 || toReplacePackageName.contains(topPackageName),
425 "Package name to replace does not belong to the given namespace to replace!");
427 return new StringBuilder(normalizeBasePackageName)
429 .append(replacedNameSpace.getPackagePrefix())
430 .append(toReplacePackageName.substring(topPackageName.length()))
433 return new StringBuilder(normalizeBasePackageName)
434 .append('.').append(replacedNameSpace.getPackagePrefix()).toString();
438 private static final ThreadLocal<MessageDigest> SHA1_MD = new ThreadLocal<MessageDigest>() {
440 protected MessageDigest initialValue() {
442 return MessageDigest.getInstance("SHA");
443 } catch (final NoSuchAlgorithmException e) {
444 throw new IllegalStateException("Failed to get a SHA digest provider", e);
449 private static String generateNormalizedPackageName(final String base, final Iterable<QName> path, final int
450 size, final BindingNamespaceType namespaceType) {
451 final StringBuilder builder = new StringBuilder(base);
452 if (namespaceType != null) {
453 builder.append('.').append(namespaceType.getPackagePrefix());
455 final Iterator<QName> iterator = path.iterator();
456 for (int i = 0; i < size; ++i) {
458 final String nodeLocalName = iterator.next().getLocalName();
459 builder.append(nodeLocalName);
461 final String normalizedPackageName = JavaIdentifierNormalizer.normalizeFullPackageName(builder.toString());
462 // Prevent duplication of input
463 PACKAGE_INTERNER.intern(normalizedPackageName);
464 return normalizedPackageName;
467 private static String generateNormalizedPackageName(final String parent, final QName path) {
468 final StringBuilder sb = new StringBuilder(parent)
470 .append(path.getLocalName());
472 final String normalizedPackageName = JavaIdentifierNormalizer.normalizeFullPackageName(sb.toString());
473 // Prevent duplication of input
474 PACKAGE_INTERNER.intern(normalizedPackageName);
475 return normalizedPackageName;
478 private static <T> Iterable<T> sortedCollection(final Comparator<? super T> comparator, final Collection<T> input) {
479 if (input.size() > 1) {
480 final List<T> ret = new ArrayList<>(input);
481 ret.sort(comparator);
488 private static <T> List<T> currentOrEmpty(final List<T> current, final List<T> base) {
489 return current.equals(base) ? ImmutableList.of() : current;
492 private static List<PatternConstraint> uniquePatterns(final StringTypeDefinition type) {
493 final List<PatternConstraint> constraints = type.getPatternConstraints();
494 if (constraints.isEmpty()) {
498 final Builder<PatternConstraint> builder = ImmutableList.builder();
499 boolean filtered = false;
500 for (final PatternConstraint c : constraints) {
501 if (containsConstraint(type.getBaseType(), c)) {
508 return filtered ? builder.build() : constraints;
511 private static boolean containsConstraint(final StringTypeDefinition type, final PatternConstraint constraint) {
512 for (StringTypeDefinition wlk = type; wlk != null; wlk = wlk.getBaseType()) {
513 if (wlk.getPatternConstraints().contains(constraint)) {
521 //TODO: further implementation of static util methods...