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.java.api.generator.util;
11 import static java.util.Objects.requireNonNull;
13 import com.google.common.base.CharMatcher;
14 import com.google.common.base.Preconditions;
15 import com.google.common.base.Splitter;
16 import com.google.common.base.Strings;
17 import com.google.common.collect.Iterables;
18 import com.google.common.collect.Lists;
20 import java.util.List;
21 import java.util.Optional;
22 import java.util.StringTokenizer;
23 import java.util.function.Function;
24 import java.util.regex.Pattern;
26 import org.opendaylight.mdsal.binding.javav2.generator.util.BindingGeneratorUtil;
27 import org.opendaylight.mdsal.binding.javav2.generator.util.Types;
28 import org.opendaylight.mdsal.binding.javav2.model.api.AccessModifier;
29 import org.opendaylight.mdsal.binding.javav2.model.api.ConcreteType;
30 import org.opendaylight.mdsal.binding.javav2.model.api.GeneratedProperty;
31 import org.opendaylight.mdsal.binding.javav2.model.api.GeneratedTransferObject;
32 import org.opendaylight.mdsal.binding.javav2.model.api.GeneratedType;
33 import org.opendaylight.mdsal.binding.javav2.model.api.GeneratedTypeForBuilder;
34 import org.opendaylight.mdsal.binding.javav2.model.api.MethodSignature;
35 import org.opendaylight.mdsal.binding.javav2.model.api.Restrictions;
36 import org.opendaylight.mdsal.binding.javav2.model.api.Type;
37 import org.opendaylight.mdsal.binding.javav2.model.api.TypeComment;
38 import org.opendaylight.mdsal.binding.javav2.model.api.TypeMember;
39 import org.opendaylight.mdsal.binding.javav2.model.api.YangSourceDefinition;
40 import org.opendaylight.mdsal.binding.javav2.model.api.YangSourceDefinition.Multiple;
41 import org.opendaylight.mdsal.binding.javav2.model.api.YangSourceDefinition.Single;
42 import org.opendaylight.mdsal.binding.javav2.spec.runtime.BindingNamespaceType;
43 import org.opendaylight.yangtools.concepts.Builder;
44 import org.opendaylight.yangtools.yang.common.QName;
45 import org.opendaylight.yangtools.yang.model.api.DocumentedNode;
46 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
47 import org.opendaylight.yangtools.yang.model.api.Module;
48 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
49 import org.opendaylight.yangtools.yang.model.api.YangStmtMapping;
50 import org.opendaylight.yangtools.yang.model.api.meta.DeclaredStatement;
51 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
52 import org.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement;
53 import org.opendaylight.yangtools.yang.model.export.DeclaredStatementFormatter;
55 public final class TextTemplateUtil {
57 public static final String DOT = ".";
59 private static final char NEW_LINE = '\n';
60 private static final String UNDERSCORE = "_";
61 private static final CharMatcher NEWLINE_OR_TAB = CharMatcher.anyOf("\n\t");
62 private static final CharMatcher NL_MATCHER = CharMatcher.is(NEW_LINE);
63 private static final CharMatcher AMP_MATCHER = CharMatcher.is('&');
64 private static final CharMatcher GT_MATCHER = CharMatcher.is('>');
65 private static final CharMatcher LT_MATCHER = CharMatcher.is('<');
66 private static final Splitter NL_SPLITTER = Splitter.on(NL_MATCHER);
67 private static final Splitter BSDOT_SPLITTER = Splitter.on(".");
69 private static final Pattern TAIL_COMMENT_PATTERN = Pattern.compile("*/", Pattern.LITERAL);
70 private static final Pattern MULTIPLE_SPACES_PATTERN = Pattern.compile(" +");
72 private static DeclaredStatementFormatter YANG_FORMATTER = DeclaredStatementFormatter.builder()
73 .addIgnoredStatement(YangStmtMapping.CONTACT)
74 .addIgnoredStatement(YangStmtMapping.DESCRIPTION)
75 .addIgnoredStatement(YangStmtMapping.REFERENCE)
76 .addIgnoredStatement(YangStmtMapping.ORGANIZATION)
79 private TextTemplateUtil() {
80 throw new UnsupportedOperationException("Util class");
84 * Makes start of getter name LowerCase.
86 * @param str getter name without prefix
87 * @return getter name starting in LowerCase
89 public static String toFirstLower(final String str) {
90 return str != null && str.length() != 0 ? Character.isLowerCase(str.charAt(0)) ? str : str.length() == 1
91 ? str.toLowerCase() : str.substring(0, 1).toLowerCase() + str.substring(1) : str;
95 * Wraps text as documentation, used in enum description.
97 * @param text text for wrapping
98 * @return wrapped text
100 public static String wrapToDocumentation(final String text) {
101 if (text.isEmpty()) {
104 final StringBuilder sb = new StringBuilder();
107 for (final String t : NL_SPLITTER.split(text)) {
116 return sb.toString();
120 * Returns formatted Javadoc, based on type.
122 * @param typeName given type name
123 * @return formatted Javadoc, based on type
125 public static String formatDataForJavaDocBuilder(final String typeName) {
126 final StringBuilder stringBuilder = new StringBuilder();
127 stringBuilder.append("Class that builds {@link ")
129 .append("} instances.")
133 return stringBuilder.toString();
137 * Returns formatted Javadoc with possible additional comment, based on type.
139 * @param type given type
140 * @param additionalComment additional comment to format
141 * @return formatted Javadoc with possible additional comment, based on type
143 public static String formatDataForJavaDoc(final GeneratedType type, final String additionalComment) {
144 final StringBuilder javaDoc = new StringBuilder();
145 javaDoc.append(formatDataForJavaDoc(type)).append(additionalComment);
146 return javaDoc.toString();
150 * Returns formatted type description.
152 * @param type given type
153 * @return formatted type description
155 public static String formatDataForJavaDoc(final GeneratedType type) {
156 final StringBuilder javaDoc = new StringBuilder();
157 final TypeComment comment = type.getComment();
158 if (comment != null) {
159 javaDoc.append(comment.getJavadoc())
165 appendSnippet(javaDoc, type);
167 return javaDoc.toString();
170 private static String formatDataForJavaDoc(final TypeMember type, final String additionalComment) {
171 final StringBuilder javaDoc = new StringBuilder();
172 if (type.getComment() != null && !type.getComment().isEmpty()) {
173 javaDoc.append(formatToParagraph(type.getComment(), 0))
178 javaDoc.append(additionalComment);
179 return wrapToDocumentation(javaDoc.toString());
182 private static void appendSnippet(final StringBuilder sb, final GeneratedType type) {
183 Optional<YangSourceDefinition> optDef = type.getYangSourceDefinition();
184 if (optDef.isPresent()) {
185 YangSourceDefinition def = optDef.get();
188 if (def instanceof Single) {
189 DocumentedNode node = ((Single) def).getNode();
191 .append("This class represents the following YANG schema fragment defined in module <b>")
192 .append(def.getModule().argument()).append("</b>\n")
194 appendYangSnippet(sb, def.getModule(), ((EffectiveStatement<?, ?>) node).getDeclared());
197 if (node instanceof SchemaNode) {
198 sb.append("The schema path to identify an instance is\n")
200 .append(formatSchemaPath(def.getModule().argument(), ((SchemaNode) node).getPath()
204 if (hasBuilderClass(type)) {
205 final String builderName = new StringBuilder()
206 .append(((GeneratedTypeForBuilder) type).getPackageNameForBuilder())
207 .append(".").append(type.getName()).append("Builder").toString();
209 sb.append("\n<p>To create instances of this class use {@link ").append(builderName)
211 .append("@see ").append(builderName).append('\n');
212 if (node instanceof ListSchemaNode) {
213 final StringBuilder linkToKeyClass = new StringBuilder();
215 final String[] namespace = Iterables.toArray(
216 BSDOT_SPLITTER.split(type.getFullyQualifiedName()), String.class);
217 final String className = namespace[namespace.length - 1];
219 linkToKeyClass.append(BindingGeneratorUtil.packageNameForSubGeneratedType(
220 ((GeneratedTypeForBuilder) type).getBasePackageName(), (SchemaNode) node,
221 BindingNamespaceType.Key)).append('.').append(className).append("Key");
223 List<QName> keyDef = ((ListSchemaNode) node).getKeyDefinition();
224 if (keyDef != null && !keyDef.isEmpty()) {
225 sb.append("@see ").append(linkToKeyClass);
231 } else if (def instanceof Multiple) {
232 sb.append("<pre>\n");
233 for (SchemaNode node : ((Multiple) def).getNodes()) {
234 appendYangSnippet(sb, def.getModule(), ((EffectiveStatement<?, ?>) node).getDeclared());
236 sb.append("</pre>\n");
241 private static void appendYangSnippet(StringBuilder sb, ModuleEffectiveStatement module,
242 DeclaredStatement<?> stmt) {
243 for (String str : YANG_FORMATTER.toYangTextSnippet(module, stmt)) {
244 sb.append(encodeAngleBrackets(encodeJavadocSymbols(str)));
248 public static boolean hasBuilderClass(final GeneratedType type) {
249 return type instanceof GeneratedTypeForBuilder;
252 public static String formatSchemaPath(final String moduleName, final Iterable<QName> schemaPath) {
253 final StringBuilder sb = new StringBuilder();
254 sb.append(moduleName);
256 QName currentElement = Iterables.getFirst(schemaPath, null);
257 for (final QName pathElement : schemaPath) {
259 if (!currentElement.getNamespace().equals(pathElement.getNamespace())) {
260 currentElement = pathElement;
261 sb.append(pathElement);
263 sb.append(pathElement.getLocalName());
266 return sb.toString();
270 * Returns properties names in formatted string.
272 * @param properties list of given properties
273 * @return properties names in formatted string
275 //FIXME: this needs further clarification in future patch
276 public static String valueForBits(final List<GeneratedProperty> properties) {
277 return String.join(",", Lists.transform(properties, TextTemplateUtil::fieldName));
281 * Returns parameter name, based on given Type.
283 * @param returnType given type
284 * @param paramName parameter name
285 * @return parameter name, based on given Type
287 public static String paramValue(final Type returnType, final String paramName) {
288 return returnType instanceof ConcreteType ? paramName : paramName + ".getValue()";
292 * Template method which generates JAVA comments. InterfaceTemplate
294 * @param comment comment string with the comment for whole JAVA class
295 * @return string with comment in JAVA format
297 public static String asJavadoc(final String comment) {
298 return comment == null ? "" : wrapToDocumentation(formatToParagraph(comment.trim(), 0));
302 * Returns related Javadoc.
304 * @param methodSignature method signature
305 * @return related Javadoc
307 public static String getJavaDocForInterface(final MethodSignature methodSignature) {
308 if (methodSignature.getReturnType() == Types.VOID) {
311 final StringBuilder javaDoc = new StringBuilder();
312 javaDoc.append("@return ")
313 .append(asCode(methodSignature.getReturnType().getFullyQualifiedName()))
315 .append(asCode(propertyNameFromGetter(methodSignature)))
317 .append(asCode("null"))
318 .append(" if not present");
319 return formatDataForJavaDoc(methodSignature, javaDoc.toString());
322 private static String asCode(final String text) {
323 return "<code>" + text + "</code>";
327 * Encodes angle brackets in yang statement description.
329 * @param description description of a yang statement which is used to generate javadoc comments
330 * @return string with encoded angle brackets
332 public static String encodeAngleBrackets(String description) {
333 if (description != null) {
334 description = LT_MATCHER.replaceFrom(description, "<");
335 description = GT_MATCHER.replaceFrom(description, ">");
341 * Returns collection of properties as formatted String.
343 * @param properties list of given properties
344 * @return generated properties as formatted String
346 public static String propsAsArgs(final Iterable<GeneratedProperty> properties) {
347 return String.join(",", Iterables.transform(properties, prop -> "\"" + prop.getName() + "\""));
351 * Returns properties as formatted String.
353 * @param properties list of given properties
354 * @param booleanName Java Boolean type name
355 * @return Properties as formatted String
357 public static String propsAsList(final Iterable<GeneratedProperty> properties, final String booleanName) {
358 return String.join(",", Iterables.transform(properties,
359 prop -> "properties.get(i++).equals(defaultValue) ? " + booleanName + ".TRUE : null"));
363 * Extracts available restrictions from given type.
365 * @param currentType given type
366 * @return restrictions from given type
368 public static Restrictions getRestrictions(final Type currentType) {
369 Restrictions restrictions = null;
370 if (currentType instanceof ConcreteType) {
371 restrictions = ((ConcreteType) currentType).getRestrictions();
372 } else if (currentType instanceof GeneratedTransferObject) {
373 restrictions = ((GeneratedTransferObject) currentType).getRestrictions();
379 * sets fieldname according to property for return type.
380 * method(type fieldname)
382 * @param property type from getter
383 * @return underscored string form
385 public static String fieldName(final GeneratedProperty property) {
386 final String name = requireNonNull(property.getName());
387 return UNDERSCORE.concat(name);
391 * Template method which generates sequence of the names of the class attributes.
393 * @param parameters group of generated property instances which are transformed to the sequence of parameter names
394 * @return string with the list of the parameter names
396 public static String asArguments(final List<GeneratedProperty> parameters) {
397 return String.join(", ", Lists.transform(parameters, TextTemplateUtil::fieldName));
401 * Helper method for building getter.
403 * @param field property name
404 * @return getter for property
406 public static String getterMethodName(final GeneratedProperty field) {
407 final Type type = requireNonNull(field.getReturnType());
408 final String name = requireNonNull(field.getName());
409 final String prefix = Types.BOOLEAN.equals(type) ? "is" : "get";
410 return prefix.concat(toFirstUpper(name));
414 * Returns built setter method body from input parameters.
416 * @param field generated property
417 * @param typeName type name
418 * @param returnTypeName return type name
419 * @return built setter method body
421 public static String setterMethod(final GeneratedProperty field, final String typeName, final String
423 final StringBuilder stringBuilder = new StringBuilder();
424 stringBuilder.append("public ")
427 .append(toFirstUpper(field.getName()))
429 .append(returnTypeName)
430 .append(" value) {\n this.")
431 .append(fieldName(field))
432 .append(" = value;\n return this;\n}\n");
433 return stringBuilder.toString();
437 * Returns simple name of underlying class.
439 * @return Simple name of underlying class
441 public static String getSimpleNameForBuilder() {
442 return Builder.class.getSimpleName();
446 * Makes start of getter name uppercase.
448 * @param str getter name without prefix
449 * @return getter name starting in uppercase
451 public static String toFirstUpper(final String str) {
452 return str != null && str.length() != 0 ? Character.isUpperCase(str.charAt(0)) ? str : str.length() == 1
453 ? str.toUpperCase() : str.substring(0, 1).toUpperCase() + str.substring(1) : str;
457 * Cuts prefix from getter name.
459 * @param getter getter name
460 * @return getter name without prefix
462 public static String propertyNameFromGetter(final MethodSignature getter) {
463 final String name = requireNonNull(getter.getName());
465 if (name.startsWith("is")) {
467 } else if (name.startsWith("get")) {
472 return toFirstLower(name.substring(prefix));
476 * Returns list of properties as formatted String.
478 * @param properties input list of generated properties
479 * @return formatted property list as String
481 public static String getPropertyList(final List<GeneratedProperty> properties) {
482 return String.join(", ", Lists.transform(properties, prop -> "base." + getterMethodName(prop) + "()"));
486 * util method for unionTemplateBuilderTemplate.
488 * @return string with clarification for javadoc
490 public static String getClarification() {
491 final StringBuilder clarification = new StringBuilder();
492 clarification.append("The purpose of generated class in src/main/java for Union types is to create new "
493 + "instances of unions from a string representation.\n")
494 .append("In some cases it is very difficult to automate it since there can be unions such as (uint32 "
495 + "- uint16), or (string - uint32).\n")
497 .append("The reason behind putting it under src/main/java is:\n")
498 .append("This class is generated in form of a stub and needs to be finished by the user. This class "
499 + "is generated only once to prevent\n")
500 .append("loss of user code.\n")
502 return clarification.toString();
506 * Returns source path as String.
508 * @param module module
509 * @param moduleFilePathResolver function module to module file path
510 * @return formatted String source path
512 public static String getSourcePath(final Module module, final Function<Module, Optional<String>>
513 moduleFilePathResolver) {
514 final Optional<String> moduleFilePath = moduleFilePathResolver.apply(module);
515 Preconditions.checkArgument(moduleFilePath.isPresent(), "Module file path for %s is not present", module);
517 return moduleFilePath.get();
521 * Util method for unionTemplateBuilderTemplate.
523 * @param modifier enum representing Java access modifier
524 * @return needed access modifier
526 public static String getAccessModifier(final AccessModifier modifier) {
540 * Return formatted description.
541 * @param text Content of tag description
542 * @param nextLineIndent Number of spaces from left side default is 12
543 * @return formatted description
545 private static String formatToParagraph(final String text, final int nextLineIndent) {
546 if (Strings.isNullOrEmpty(text)) {
549 boolean isFirstElementOnNewLineEmptyChar = false;
550 final StringBuilder sb = new StringBuilder();
551 final StringBuilder lineBuilder = new StringBuilder();
552 final String lineIndent = Strings.repeat(" ", nextLineIndent);
553 final String textToFormat = NEWLINE_OR_TAB.removeFrom(encodeJavadocSymbols(text));
554 final String formattedText = MULTIPLE_SPACES_PATTERN.matcher(textToFormat).replaceAll(" ");
555 final StringTokenizer tokenizer = new StringTokenizer(formattedText, " ", true);
557 while (tokenizer.hasMoreElements()) {
558 final String nextElement = tokenizer.nextElement().toString();
560 if (lineBuilder.length() + nextElement.length() > 80) {
561 // Trim trailing whitespace
562 for (int i = lineBuilder.length() - 1; i >= 0 && lineBuilder.charAt(i) != ' '; --i) {
563 lineBuilder.setLength(i);
565 // Trim leading whitespace
566 while (lineBuilder.charAt(0) == ' ') {
567 lineBuilder.deleteCharAt(0);
569 sb.append(lineBuilder).append('\n');
570 lineBuilder.setLength(0);
572 if (nextLineIndent > 0) {
573 sb.append(lineIndent);
576 if (" ".equals(nextElement)) {
577 isFirstElementOnNewLineEmptyChar = true;
580 if (isFirstElementOnNewLineEmptyChar) {
581 isFirstElementOnNewLineEmptyChar = false;
583 lineBuilder.append(nextElement);
586 return sb.append(lineBuilder).append('\n').toString();
589 private static String encodeJavadocSymbols(final String description) {
590 return Strings.isNullOrEmpty(description) ? description
591 : AMP_MATCHER.replaceFrom(TAIL_COMMENT_PATTERN.matcher(description).replaceAll("*/"), "&");