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.collect.ImmutableList;
14 import com.google.common.collect.ImmutableList.Builder;
15 import com.google.common.collect.Interner;
16 import com.google.common.collect.Interners;
17 import com.google.common.collect.Iterables;
18 import java.io.ByteArrayOutputStream;
19 import java.io.DataOutputStream;
20 import java.io.IOException;
21 import java.security.MessageDigest;
22 import java.security.NoSuchAlgorithmException;
23 import java.util.ArrayList;
24 import java.util.Collection;
25 import java.util.Collections;
26 import java.util.Comparator;
27 import java.util.Iterator;
28 import java.util.List;
29 import org.opendaylight.mdsal.binding.javav2.model.api.AccessModifier;
30 import org.opendaylight.mdsal.binding.javav2.model.api.Restrictions;
31 import org.opendaylight.mdsal.binding.javav2.model.api.Type;
32 import org.opendaylight.mdsal.binding.javav2.model.api.type.builder.GeneratedPropertyBuilder;
33 import org.opendaylight.mdsal.binding.javav2.model.api.type.builder.GeneratedTypeBuilderBase;
34 import org.opendaylight.mdsal.binding.javav2.model.api.type.builder.MethodSignatureBuilder;
35 import org.opendaylight.mdsal.binding.javav2.model.api.type.builder.TypeMemberBuilder;
36 import org.opendaylight.mdsal.binding.javav2.spec.runtime.BindingNamespaceType;
37 import org.opendaylight.yangtools.yang.common.QName;
38 import org.opendaylight.yangtools.yang.model.api.AugmentationSchema;
39 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
40 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
41 import org.opendaylight.yangtools.yang.model.api.type.BinaryTypeDefinition;
42 import org.opendaylight.yangtools.yang.model.api.type.DecimalTypeDefinition;
43 import org.opendaylight.yangtools.yang.model.api.type.IntegerTypeDefinition;
44 import org.opendaylight.yangtools.yang.model.api.type.LengthConstraint;
45 import org.opendaylight.yangtools.yang.model.api.type.PatternConstraint;
46 import org.opendaylight.yangtools.yang.model.api.type.RangeConstraint;
47 import org.opendaylight.yangtools.yang.model.api.type.StringTypeDefinition;
48 import org.opendaylight.yangtools.yang.model.api.type.UnsignedIntegerTypeDefinition;
49 import org.opendaylight.yangtools.yang.model.util.type.BaseTypes;
50 import org.opendaylight.yangtools.yang.model.util.type.DecimalTypeBuilder;
53 * Standard Util class that contains various method for converting
54 * input strings to valid JAVA language strings e.g. package names,
55 * class names, attribute names and/or valid JavaDoc comments.
58 public final class BindingGeneratorUtil {
60 private static final CharMatcher GT_MATCHER = CharMatcher.is('>');
61 private static final CharMatcher LT_MATCHER = CharMatcher.is('<');
63 private static final Interner<String> PACKAGE_INTERNER = Interners.newWeakInterner();
64 private static final Comparator<TypeMemberBuilder<?>> SUID_MEMBER_COMPARATOR =
65 Comparator.comparing(TypeMemberBuilder::getName);
67 private static final Comparator<Type> SUID_NAME_COMPARATOR =
68 Comparator.comparing(Type::getFullyQualifiedName);
70 private static final Restrictions EMPTY_RESTRICTIONS = new Restrictions() {
72 public List<LengthConstraint> getLengthConstraints() {
73 return Collections.emptyList();
77 public List<PatternConstraint> getPatternConstraints() {
78 return Collections.emptyList();
82 public List<RangeConstraint> getRangeConstraints() {
83 return Collections.emptyList();
87 public boolean isEmpty() {
92 private BindingGeneratorUtil() {
93 throw new UnsupportedOperationException("Utility class");
97 * Encodes angle brackets in yang statement description
98 * @param description description of a yang statement which is used to generate javadoc comments
99 * @return string with encoded angle brackets
101 public static String encodeAngleBrackets(final String description) {
102 String newDesc = description;
103 if (newDesc != null) {
104 newDesc = LT_MATCHER.replaceFrom(newDesc, "<");
105 newDesc = GT_MATCHER.replaceFrom(newDesc, ">");
110 public static long computeDefaultSUID(final GeneratedTypeBuilderBase<?> to) {
111 final ByteArrayOutputStream bout = new ByteArrayOutputStream();
112 try (final DataOutputStream dout = new DataOutputStream(bout)) {
113 dout.writeUTF(to.getName());
114 dout.writeInt(to.isAbstract() ? 3 : 7);
116 for (final Type ifc : sortedCollection(SUID_NAME_COMPARATOR, to.getImplementsTypes())) {
117 dout.writeUTF(ifc.getFullyQualifiedName());
120 for (final GeneratedPropertyBuilder gp : sortedCollection(SUID_MEMBER_COMPARATOR, to.getProperties())) {
121 dout.writeUTF(gp.getName());
124 for (final MethodSignatureBuilder m : sortedCollection(SUID_MEMBER_COMPARATOR, to.getMethodDefinitions())) {
125 if (!(m.getAccessModifier().equals(AccessModifier.PRIVATE))) {
126 dout.writeUTF(m.getName());
127 dout.write(m.getAccessModifier().ordinal());
132 } catch (final IOException e) {
133 throw new IllegalStateException("Failed to hash object " + to, e);
136 final byte[] hashBytes = SHA1_MD.get().digest(bout.toByteArray());
138 for (int i = Math.min(hashBytes.length, 8) - 1; i >= 0; i--) {
139 hash = (hash << 8) | (hashBytes[i] & 0xFF);
145 * Creates package name from specified <code>basePackageName</code> (package
146 * name for module) and <code>schemaPath</code>.
148 * Resulting package name is concatenation of <code>basePackageName</code>
149 * and all local names of YANG nodes which are parents of some node for
150 * which <code>schemaPath</code> is specified.
152 * Based on type of node, there is also possible suffix added in order
153 * to prevent package name conflicts.
155 * @param basePackageName
156 * string with package name of the module, MUST be normalized,
157 * otherwise this method may return an invalid string.
159 * list of names of YANG nodes which are parents of some node +
161 * @return string with valid JAVA package name
162 * @throws NullPointerException if any of the arguments are null
164 public static String packageNameForGeneratedType(final String basePackageName, final SchemaPath schemaPath, final
165 BindingNamespaceType namespaceType) {
167 final Iterable<QName> pathTowardsRoot = schemaPath.getPathTowardsRoot();
168 final Iterable<QName> pathFromRoot = schemaPath.getPathFromRoot();
169 final int size = Iterables.size(pathTowardsRoot) - 1;
171 if (namespaceType != null) {
172 final StringBuilder sb = new StringBuilder();
173 sb.append(basePackageName)
175 .append(namespaceType.getPackagePrefix());
176 return JavaIdentifierNormalizer.normalizeFullPackageName(sb.toString());
178 return JavaIdentifierNormalizer.normalizeFullPackageName(basePackageName);
181 return generateNormalizedPackageName(basePackageName, pathFromRoot, size, namespaceType);
185 * Creates package name from specified <code>basePackageName</code> (package
186 * name for module) and <code>namespaceType</code>.
188 * Resulting package name is concatenation of <code>basePackageName</code>
189 * and prefix of <code>namespaceType</code>.
191 * @param basePackageName
192 * string with package name of the module, MUST be normalized,
193 * otherwise this method may return an invalid string.
194 * @param namespaceType
195 * the namespace to which the module belongs
196 * @return string with valid JAVA package name
197 * @throws NullPointerException if any of the arguments are null
199 public static String packageNameWithNamespacePrefix(final String basePackageName, final
200 BindingNamespaceType namespaceType) {
201 final StringBuilder sb = new StringBuilder();
202 sb.append(basePackageName)
204 .append(namespaceType.getPackagePrefix());
205 return JavaIdentifierNormalizer.normalizeFullPackageName(sb.toString());
208 public static Restrictions getRestrictions(final TypeDefinition<?> type) {
209 if ((type == null) || (type.getBaseType() == null)) {
210 if (type instanceof DecimalTypeDefinition) {
211 final DecimalTypeDefinition decimal = (DecimalTypeDefinition) type;
212 final DecimalTypeBuilder tmpBuilder = BaseTypes.decimalTypeBuilder(decimal.getPath());
213 tmpBuilder.setFractionDigits(decimal.getFractionDigits());
214 final DecimalTypeDefinition tmp = tmpBuilder.build();
216 if (!tmp.getRangeConstraints().equals(decimal.getRangeConstraints())) {
217 return new Restrictions() {
219 public boolean isEmpty() {
224 public List<RangeConstraint> getRangeConstraints() {
225 return decimal.getRangeConstraints();
229 public List<PatternConstraint> getPatternConstraints() {
230 return ImmutableList.of();
234 public List<LengthConstraint> getLengthConstraints() {
235 return ImmutableList.of();
241 return EMPTY_RESTRICTIONS;
244 final List<LengthConstraint> length;
245 final List<PatternConstraint> pattern;
246 final List<RangeConstraint> range;
249 * Take care of extended types.
251 * Other types which support constraints are check afterwards. There is a slight twist with them, as returned
252 * constraints are the effective view, e.g. they are inherited from base type. Since the constraint is already
253 * enforced by the base type, we want to skip them and not perform duplicate checks.
255 * We end up emitting ConcreteType instances for YANG base types, which leads to their constraints not being
256 * enforced (most notably decimal64). Therefore we need to make sure we do not strip the next-to-last
259 if (type instanceof BinaryTypeDefinition) {
260 final BinaryTypeDefinition binary = (BinaryTypeDefinition)type;
261 final BinaryTypeDefinition base = binary.getBaseType();
262 if ((base != null) && (base.getBaseType() != null)) {
263 length = currentOrEmpty(binary.getLengthConstraints(), base.getLengthConstraints());
265 length = binary.getLengthConstraints();
268 pattern = ImmutableList.of();
269 range = ImmutableList.of();
270 } else if (type instanceof DecimalTypeDefinition) {
271 length = ImmutableList.of();
272 pattern = ImmutableList.of();
274 final DecimalTypeDefinition decimal = (DecimalTypeDefinition)type;
275 final DecimalTypeDefinition base = decimal.getBaseType();
276 if ((base != null) && (base.getBaseType() != null)) {
277 range = currentOrEmpty(decimal.getRangeConstraints(), base.getRangeConstraints());
279 range = decimal.getRangeConstraints();
281 } else if (type instanceof IntegerTypeDefinition) {
282 length = ImmutableList.of();
283 pattern = ImmutableList.of();
285 final IntegerTypeDefinition integer = (IntegerTypeDefinition)type;
286 final IntegerTypeDefinition base = integer.getBaseType();
287 if ((base != null) && (base.getBaseType() != null)) {
288 range = currentOrEmpty(integer.getRangeConstraints(), base.getRangeConstraints());
290 range = integer.getRangeConstraints();
292 } else if (type instanceof StringTypeDefinition) {
293 final StringTypeDefinition string = (StringTypeDefinition)type;
294 final StringTypeDefinition base = string.getBaseType();
295 if ((base != null) && (base.getBaseType() != null)) {
296 length = currentOrEmpty(string.getLengthConstraints(), base.getLengthConstraints());
298 length = string.getLengthConstraints();
301 pattern = uniquePatterns(string);
302 range = ImmutableList.of();
303 } else if (type instanceof UnsignedIntegerTypeDefinition) {
304 length = ImmutableList.of();
305 pattern = ImmutableList.of();
307 final UnsignedIntegerTypeDefinition unsigned = (UnsignedIntegerTypeDefinition)type;
308 final UnsignedIntegerTypeDefinition base = unsigned.getBaseType();
309 if ((base != null) && (base.getBaseType() != null)) {
310 range = currentOrEmpty(unsigned.getRangeConstraints(), base.getRangeConstraints());
312 range = unsigned.getRangeConstraints();
315 length = ImmutableList.of();
316 pattern = ImmutableList.of();
317 range = ImmutableList.of();
320 // Now, this may have ended up being empty, too...
321 if (length.isEmpty() && pattern.isEmpty() && range.isEmpty()) {
322 return EMPTY_RESTRICTIONS;
325 // Nope, not empty allocate a holder
326 return new Restrictions() {
328 public List<RangeConstraint> getRangeConstraints() {
332 public List<PatternConstraint> getPatternConstraints() {
336 public List<LengthConstraint> getLengthConstraints() {
340 public boolean isEmpty() {
347 * Creates package name from specified <code>basePackageName</code> (package
348 * name for module) and <code>schemaPath</code> which crosses an augmentation.
350 * Resulting package name is concatenation of <code>basePackageName</code>
351 * and all local names of YANG nodes which are parents of some node for
352 * which <code>schemaPath</code> is specified.
354 * Based on type of node, there is also possible suffix added in order
355 * to prevent package name conflicts.
357 * @param basePackageName
358 * string with package name of the module, MUST be normalized,
359 * otherwise this method may return an invalid string.
361 * list of names of YANG nodes which are parents of some node +
363 * @return string with valid JAVA package name
364 * @throws NullPointerException if any of the arguments are null
366 public static String packageNameForAugmentedGeneratedType(final String basePackageName, final SchemaPath schemaPath) {
367 final Iterable<QName> pathTowardsRoot = schemaPath.getPathTowardsRoot();
368 final Iterable<QName> pathFromRoot = schemaPath.getPathFromRoot();
369 final int size = Iterables.size(pathTowardsRoot);
371 return basePackageName;
374 return generateNormalizedPackageName(basePackageName, pathFromRoot, size, BindingNamespaceType.Data);
378 * Creates package name from <code>parentAugmentPackageName</code> (package
379 * name for direct parent augmentation) and <code>augmentationSchema</code> .
381 * Resulting package name is concatenation of <code>parentAugmentPackageName</code>
382 * and the local name of <code>schemaPath</code>.
384 * Based on type of node, there is also possible suffix added in order
385 * to prevent package name conflicts.
387 * @param parentAugmentPackageName
388 * string with package name of direct parent augmentation, MUST be normalized,
389 * otherwise this method may return an invalid string.
390 * @param augmentationSchema
391 * augmentation schema which is direct son of parent augmentation.
392 * @return string with valid JAVA package name
393 * @throws NullPointerException if any of the arguments are null
395 public static String packageNameForAugmentedGeneratedType(final String parentAugmentPackageName,
396 final AugmentationSchema augmentationSchema) {
397 final QName last = augmentationSchema.getTargetPath().getLastComponent();
399 return generateNormalizedPackageName(parentAugmentPackageName, last);
402 private static final ThreadLocal<MessageDigest> SHA1_MD = new ThreadLocal<MessageDigest>() {
404 protected MessageDigest initialValue() {
406 return MessageDigest.getInstance("SHA");
407 } catch (final NoSuchAlgorithmException e) {
408 throw new IllegalStateException("Failed to get a SHA digest provider", e);
413 private static String generateNormalizedPackageName(final String base, final Iterable<QName> path, final int
414 size, final BindingNamespaceType namespaceType) {
415 final StringBuilder builder = new StringBuilder(base);
416 if (namespaceType != null) {
417 builder.append('.').append(namespaceType.getPackagePrefix());
419 final Iterator<QName> iterator = path.iterator();
420 for (int i = 0; i < size; ++i) {
422 final String nodeLocalName = iterator.next().getLocalName();
423 builder.append(nodeLocalName);
425 final String normalizedPackageName = JavaIdentifierNormalizer.normalizeFullPackageName(builder.toString());
426 // Prevent duplication of input
427 PACKAGE_INTERNER.intern(normalizedPackageName);
428 return normalizedPackageName;
431 private static String generateNormalizedPackageName(final String parent, final QName path) {
432 final StringBuilder sb = new StringBuilder(parent)
434 .append(path.getLocalName());
436 final String normalizedPackageName = JavaIdentifierNormalizer.normalizeFullPackageName(sb.toString());
437 // Prevent duplication of input
438 PACKAGE_INTERNER.intern(normalizedPackageName);
439 return normalizedPackageName;
442 private static <T> Iterable<T> sortedCollection(final Comparator<? super T> comparator, final Collection<T> input) {
443 if (input.size() > 1) {
444 final List<T> ret = new ArrayList<>(input);
445 ret.sort(comparator);
452 private static <T> List<T> currentOrEmpty(final List<T> current, final List<T> base) {
453 return current.equals(base) ? ImmutableList.of() : current;
456 private static List<PatternConstraint> uniquePatterns(final StringTypeDefinition type) {
457 final List<PatternConstraint> constraints = type.getPatternConstraints();
458 if (constraints.isEmpty()) {
462 final Builder<PatternConstraint> builder = ImmutableList.builder();
463 boolean filtered = false;
464 for (final PatternConstraint c : constraints) {
465 if (containsConstraint(type.getBaseType(), c)) {
472 return filtered ? builder.build() : constraints;
475 private static boolean containsConstraint(final StringTypeDefinition type, final PatternConstraint constraint) {
476 for (StringTypeDefinition wlk = type; wlk != null; wlk = wlk.getBaseType()) {
477 if (wlk.getPatternConstraints().contains(constraint)) {
485 //TODO: further implementation of static util methods...