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.ImmutableList;
12 import com.google.common.collect.ImmutableList.Builder;
13 import com.google.common.collect.Iterables;
14 import java.io.ByteArrayOutputStream;
15 import java.io.DataOutputStream;
16 import java.io.IOException;
17 import java.security.MessageDigest;
18 import java.security.NoSuchAlgorithmException;
19 import java.util.ArrayList;
20 import java.util.Collection;
21 import java.util.Collections;
22 import java.util.Comparator;
23 import java.util.Iterator;
24 import java.util.List;
25 import java.util.Optional;
26 import java.util.regex.Pattern;
27 import org.opendaylight.mdsal.binding.model.api.AccessModifier;
28 import org.opendaylight.mdsal.binding.model.api.Restrictions;
29 import org.opendaylight.mdsal.binding.model.api.Type;
30 import org.opendaylight.mdsal.binding.model.api.type.builder.GeneratedPropertyBuilder;
31 import org.opendaylight.mdsal.binding.model.api.type.builder.GeneratedTypeBuilderBase;
32 import org.opendaylight.mdsal.binding.model.api.type.builder.MethodSignatureBuilder;
33 import org.opendaylight.mdsal.binding.model.api.type.builder.TypeMemberBuilder;
34 import org.opendaylight.mdsal.binding.spec.naming.BindingMapping;
35 import org.opendaylight.yangtools.yang.common.QName;
36 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
37 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
38 import org.opendaylight.yangtools.yang.model.api.type.BinaryTypeDefinition;
39 import org.opendaylight.yangtools.yang.model.api.type.DecimalTypeDefinition;
40 import org.opendaylight.yangtools.yang.model.api.type.LengthConstraint;
41 import org.opendaylight.yangtools.yang.model.api.type.PatternConstraint;
42 import org.opendaylight.yangtools.yang.model.api.type.RangeConstraint;
43 import org.opendaylight.yangtools.yang.model.api.type.RangeRestrictedTypeDefinition;
44 import org.opendaylight.yangtools.yang.model.api.type.StringTypeDefinition;
45 import org.opendaylight.yangtools.yang.model.util.type.BaseTypes;
46 import org.opendaylight.yangtools.yang.model.util.type.DecimalTypeBuilder;
49 * Contains the methods for converting strings to valid JAVA language strings
50 * (package names, class names, attribute names) and to valid javadoc comments.
52 public final class BindingGeneratorUtil {
55 * Impossible to instantiate this class. All of the methods or attributes are static.
57 private BindingGeneratorUtil() {
62 * Pre-compiled replacement pattern.
64 private static final CharMatcher DOT_MATCHER = CharMatcher.is('.');
65 private static final CharMatcher DASH_COLON_MATCHER = CharMatcher.anyOf("-:");
66 private static final CharMatcher GT_MATCHER = CharMatcher.is('>');
67 private static final CharMatcher LT_MATCHER = CharMatcher.is('<');
68 private static final Pattern UNICODE_CHAR_PATTERN = Pattern.compile("\\\\+u");
70 private static final Restrictions EMPTY_RESTRICTIONS = new Restrictions() {
72 public Optional<LengthConstraint> getLengthConstraint() {
73 return Optional.empty();
77 public List<PatternConstraint> getPatternConstraints() {
78 return Collections.emptyList();
82 public Optional<RangeConstraint<?>> getRangeConstraint() {
83 return Optional.empty();
87 public boolean isEmpty() {
92 private static final Comparator<TypeMemberBuilder<?>> SUID_MEMBER_COMPARATOR =
93 Comparator.comparing(TypeMemberBuilder::getName);
95 private static final Comparator<Type> SUID_NAME_COMPARATOR = Comparator.comparing(Type::getFullyQualifiedName);
98 * Converts <code>parameterName</code> to valid JAVA parameter name. If the <code>parameterName</code> is one
99 * of the JAVA reserved words then it is prefixed with underscore character.
101 * @param parameterName string with the parameter name
102 * @return string with the admissible parameter name
104 public static String resolveJavaReservedWordEquivalency(final String parameterName) {
105 if (parameterName != null && BindingMapping.JAVA_RESERVED_WORDS.contains(parameterName)) {
106 return "_" + parameterName;
108 return parameterName;
112 * Creates package name from specified <code>basePackageName</code> (package name for module)
113 * and <code>schemaPath</code>. Resulting package name is concatenation of <code>basePackageName</code>
114 * and all local names of YANG nodes which are parents of some node for which <code>schemaPath</code> is specified.
116 * @param basePackageName string with package name of the module, MUST be normalized, otherwise this method may
117 * return an invalid string.
118 * @param schemaPath list of names of YANG nodes which are parents of some node + name of this node
119 * @return string with valid JAVA package name
120 * @throws NullPointerException if any of the arguments are null
122 public static String packageNameForGeneratedType(final String basePackageName, final SchemaPath schemaPath) {
123 final int size = Iterables.size(schemaPath.getPathTowardsRoot()) - 1;
125 return basePackageName;
128 return generateNormalizedPackageName(basePackageName, schemaPath.getPathFromRoot(), size);
132 * Creates package name from specified <code>basePackageName</code> (package name for module)
133 * and <code>schemaPath</code> which crosses an augmentation. Resulting package name is concatenation
134 * of <code>basePackageName</code> and all local names of YANG nodes which are parents of some node for which
135 * <code>schemaPath</code> is specified.
137 * @param basePackageName string with package name of the module, MUST be normalized, otherwise this method may
138 * return an invalid string.
139 * @param schemaPath list of names of YANG nodes which are parents of some node + name of this node
140 * @return string with valid JAVA package name
141 * @throws NullPointerException if any of the arguments are null
143 public static String packageNameForAugmentedGeneratedType(final String basePackageName,
144 final SchemaPath schemaPath) {
145 final int size = Iterables.size(schemaPath.getPathTowardsRoot());
147 return basePackageName;
150 return generateNormalizedPackageName(basePackageName, schemaPath.getPathFromRoot(), size);
153 private static String generateNormalizedPackageName(final String base, final Iterable<QName> path, final int size) {
154 final StringBuilder builder = new StringBuilder(base);
155 final Iterator<QName> iterator = path.iterator();
156 for (int i = 0; i < size; ++i) {
158 final String nodeLocalName = iterator.next().getLocalName();
159 // FIXME: Collon ":" is invalid in node local name as per RFC6020, identifier statement.
160 builder.append(DASH_COLON_MATCHER.replaceFrom(nodeLocalName, '.'));
162 return BindingMapping.normalizePackageName(builder.toString());
166 * Converts string <code>token</code> to the cammel case format.
168 * @param token string which should be converted to the cammel case format
169 * @param uppercase boolean value which says whether the first character of the <code>token</code> should be
171 * @return string in the camel case format
172 * @throws IllegalArgumentException
174 * <li>if <code>token</code> without white spaces is empty</li>
175 * <li>if <code>token</code> equals null</li>
178 private static String parseToCamelCase(final String token, final boolean uppercase) {
180 throw new IllegalArgumentException("Name can not be null");
183 String correctStr = DOT_MATCHER.removeFrom(token.trim());
184 if (correctStr.isEmpty()) {
185 throw new IllegalArgumentException("Name can not be empty");
188 correctStr = replaceWithCamelCase(correctStr, ' ');
189 correctStr = replaceWithCamelCase(correctStr, '-');
190 correctStr = replaceWithCamelCase(correctStr, '_');
192 char firstChar = correctStr.charAt(0);
193 firstChar = uppercase ? Character.toUpperCase(firstChar) : Character.toLowerCase(firstChar);
195 if (firstChar >= '0' && firstChar <= '9') {
196 return '_' + correctStr;
198 return firstChar + correctStr.substring(1);
203 * Replaces all the occurrences of the <code>removalChar</code> in the
204 * <code>text</code> with empty string and converts following character to
208 * string with source text which should be converted
210 * character which is sought in the <code>text</code>
211 * @return string which doesn't contain <code>removalChar</code> and has
212 * following characters converted to upper case
213 * @throws IllegalArgumentException
214 * if the length of the returning string has length 0
216 private static String replaceWithCamelCase(final String text, final char removalChar) {
217 int toBeRemovedPos = text.indexOf(removalChar);
218 if (toBeRemovedPos == -1) {
222 final StringBuilder sb = new StringBuilder(text);
223 final String toBeRemoved = String.valueOf(removalChar);
225 sb.replace(toBeRemovedPos, toBeRemovedPos + 1, "");
226 // check if 'toBeRemoved' character is not the only character in
228 if (sb.length() == 0) {
229 throw new IllegalArgumentException("The resulting string can not be empty");
231 final char replacement = Character.toUpperCase(sb.charAt(toBeRemovedPos));
232 sb.setCharAt(toBeRemovedPos, replacement);
233 toBeRemovedPos = sb.indexOf(toBeRemoved);
234 } while (toBeRemovedPos != -1);
236 return sb.toString();
239 private static <T> Iterable<T> sortedCollection(final Comparator<? super T> comparator, final Collection<T> input) {
240 if (input.size() > 1) {
241 final List<T> ret = new ArrayList<>(input);
242 ret.sort(comparator);
249 private static final ThreadLocal<MessageDigest> SHA1_MD = ThreadLocal.withInitial(() -> {
251 return MessageDigest.getInstance("SHA");
252 } catch (final NoSuchAlgorithmException e) {
253 throw new IllegalStateException("Failed to get a SHA digest provider", e);
257 public static long computeDefaultSUID(final GeneratedTypeBuilderBase<?> to) {
258 final ByteArrayOutputStream bout = new ByteArrayOutputStream();
259 try (DataOutputStream dout = new DataOutputStream(bout)) {
260 dout.writeUTF(to.getName());
261 dout.writeInt(to.isAbstract() ? 3 : 7);
263 for (final Type ifc : sortedCollection(SUID_NAME_COMPARATOR, to.getImplementsTypes())) {
264 dout.writeUTF(ifc.getFullyQualifiedName());
267 for (final GeneratedPropertyBuilder gp : sortedCollection(SUID_MEMBER_COMPARATOR, to.getProperties())) {
268 dout.writeUTF(gp.getName());
271 for (final MethodSignatureBuilder m : sortedCollection(SUID_MEMBER_COMPARATOR, to.getMethodDefinitions())) {
272 if (!m.getAccessModifier().equals(AccessModifier.PRIVATE)) {
273 dout.writeUTF(m.getName());
274 dout.write(m.getAccessModifier().ordinal());
279 } catch (final IOException e) {
280 throw new IllegalStateException("Failed to hash object " + to, e);
283 final byte[] hashBytes = SHA1_MD.get().digest(bout.toByteArray());
285 for (int i = Math.min(hashBytes.length, 8) - 1; i >= 0; i--) {
286 hash = hash << 8 | hashBytes[i] & 0xFF;
291 private static <T extends Optional<?>> T currentOrEmpty(final T current, final T base) {
292 return current.equals(base) ? (T)Optional.empty() : current;
295 private static boolean containsConstraint(final StringTypeDefinition type, final PatternConstraint constraint) {
296 for (StringTypeDefinition wlk = type; wlk != null; wlk = wlk.getBaseType()) {
297 if (wlk.getPatternConstraints().contains(constraint)) {
305 private static List<PatternConstraint> uniquePatterns(final StringTypeDefinition type) {
306 final List<PatternConstraint> constraints = type.getPatternConstraints();
307 if (constraints.isEmpty()) {
311 final Builder<PatternConstraint> builder = ImmutableList.builder();
312 boolean filtered = false;
313 for (final PatternConstraint c : constraints) {
314 if (containsConstraint(type.getBaseType(), c)) {
321 return filtered ? builder.build() : constraints;
324 public static Restrictions getRestrictions(final TypeDefinition<?> type) {
325 // Old parser generated types which actually contained based restrictions, but our code deals with that when
326 // binding to core Java types. Hence we'll emit empty restrictions for base types.
327 if (type == null || type.getBaseType() == null) {
328 // Handling of decimal64 has changed in the new parser. It contains range restrictions applied to the type
329 // directly, without an extended type. We need to capture such constraints. In order to retain behavior we
330 // need to analyze the new semantics and see if the constraints have been overridden. To do that we
331 // instantiate a temporary unconstrained type and compare them.
333 // FIXME: looking at the generated code it looks as though we need to pass the restrictions without
335 if (type instanceof DecimalTypeDefinition) {
336 final DecimalTypeDefinition decimal = (DecimalTypeDefinition) type;
337 final DecimalTypeBuilder tmpBuilder = BaseTypes.decimalTypeBuilder(decimal.getPath());
338 tmpBuilder.setFractionDigits(decimal.getFractionDigits());
339 final DecimalTypeDefinition tmp = tmpBuilder.build();
341 if (!tmp.getRangeConstraint().equals(decimal.getRangeConstraint())) {
342 return new Restrictions() {
344 public boolean isEmpty() {
349 public Optional<? extends RangeConstraint<?>> getRangeConstraint() {
350 return decimal.getRangeConstraint();
354 public List<PatternConstraint> getPatternConstraints() {
355 return ImmutableList.of();
359 public Optional<LengthConstraint> getLengthConstraint() {
360 return Optional.empty();
366 return EMPTY_RESTRICTIONS;
369 final Optional<LengthConstraint> length;
370 final List<PatternConstraint> pattern;
371 final Optional<? extends RangeConstraint<?>> range;
374 * Take care of extended types.
376 * Other types which support constraints are check afterwards. There is a slight twist with them, as returned
377 * constraints are the effective view, e.g. they are inherited from base type. Since the constraint is already
378 * enforced by the base type, we want to skip them and not perform duplicate checks.
380 * We end up emitting ConcreteType instances for YANG base types, which leads to their constraints not being
381 * enforced (most notably decimal64). Therefore we need to make sure we do not strip the next-to-last
384 * FIXME: this probably not the best solution and needs further analysis.
386 if (type instanceof BinaryTypeDefinition) {
387 final BinaryTypeDefinition binary = (BinaryTypeDefinition)type;
388 final BinaryTypeDefinition base = binary.getBaseType();
389 if (base != null && base.getBaseType() != null) {
390 length = currentOrEmpty(binary.getLengthConstraint(), base.getLengthConstraint());
392 length = binary.getLengthConstraint();
395 pattern = ImmutableList.of();
396 range = Optional.empty();
397 } else if (type instanceof DecimalTypeDefinition) {
398 length = Optional.empty();
399 pattern = ImmutableList.of();
401 final DecimalTypeDefinition decimal = (DecimalTypeDefinition)type;
402 final DecimalTypeDefinition base = decimal.getBaseType();
403 if (base != null && base.getBaseType() != null) {
404 range = currentOrEmpty(decimal.getRangeConstraint(), base.getRangeConstraint());
406 range = decimal.getRangeConstraint();
408 } else if (type instanceof RangeRestrictedTypeDefinition) {
409 // Integer-like types
410 length = Optional.empty();
411 pattern = ImmutableList.of();
412 range = extractRangeConstraint((RangeRestrictedTypeDefinition<?, ?>)type);
413 } else if (type instanceof StringTypeDefinition) {
414 final StringTypeDefinition string = (StringTypeDefinition)type;
415 final StringTypeDefinition base = string.getBaseType();
416 if (base != null && base.getBaseType() != null) {
417 length = currentOrEmpty(string.getLengthConstraint(), base.getLengthConstraint());
419 length = string.getLengthConstraint();
422 pattern = uniquePatterns(string);
423 range = Optional.empty();
425 length = Optional.empty();
426 pattern = ImmutableList.of();
427 range = Optional.empty();
430 // Now, this may have ended up being empty, too...
431 if (!length.isPresent() && pattern.isEmpty() && !range.isPresent()) {
432 return EMPTY_RESTRICTIONS;
435 // Nope, not empty allocate a holder
436 return new Restrictions() {
438 public Optional<? extends RangeConstraint<?>> getRangeConstraint() {
443 public List<PatternConstraint> getPatternConstraints() {
448 public Optional<LengthConstraint> getLengthConstraint() {
453 public boolean isEmpty() {
459 private static <T extends RangeRestrictedTypeDefinition<?, ?>> Optional<? extends RangeConstraint<?>>
460 extractRangeConstraint(final T def) {
461 final T base = (T) def.getBaseType();
462 if (base != null && base.getBaseType() != null) {
463 return currentOrEmpty(def.getRangeConstraint(), base.getRangeConstraint());
466 return def.getRangeConstraint();
470 * Encodes angle brackets in yang statement description.
472 * @param description description of a yang statement which is used to generate javadoc comments
473 * @return string with encoded angle brackets
475 public static String encodeAngleBrackets(String description) {
476 if (description != null) {
477 description = LT_MATCHER.replaceFrom(description, "<");
478 description = GT_MATCHER.replaceFrom(description, ">");
483 public static String replaceAllIllegalChars(final CharSequence stringBuilder) {
484 final String ret = UNICODE_CHAR_PATTERN.matcher(stringBuilder).replaceAll("\\\\\\\\u");
485 return ret.isEmpty() ? "" : ret;