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 com.google.common.base.CharMatcher;
12 import com.google.common.base.Preconditions;
13 import com.google.common.base.Splitter;
14 import com.google.common.base.Strings;
15 import com.google.common.collect.Iterables;
16 import com.google.common.collect.Lists;
17 import java.util.Collection;
18 import java.util.List;
19 import java.util.Optional;
20 import java.util.StringTokenizer;
21 import java.util.function.Function;
22 import java.util.regex.Pattern;
23 import org.opendaylight.mdsal.binding.javav2.generator.util.BindingGeneratorUtil;
24 import org.opendaylight.mdsal.binding.javav2.generator.util.Types;
25 import org.opendaylight.mdsal.binding.javav2.generator.util.YangSnippetCleaner;
26 import org.opendaylight.mdsal.binding.javav2.java.api.yang.txt.yangTemplateForModule;
27 import org.opendaylight.mdsal.binding.javav2.java.api.yang.txt.yangTemplateForNode;
28 import org.opendaylight.mdsal.binding.javav2.java.api.yang.txt.yangTemplateForNodes;
29 import org.opendaylight.mdsal.binding.javav2.model.api.AccessModifier;
30 import org.opendaylight.mdsal.binding.javav2.model.api.ConcreteType;
31 import org.opendaylight.mdsal.binding.javav2.model.api.GeneratedProperty;
32 import org.opendaylight.mdsal.binding.javav2.model.api.GeneratedTransferObject;
33 import org.opendaylight.mdsal.binding.javav2.model.api.GeneratedType;
34 import org.opendaylight.mdsal.binding.javav2.model.api.GeneratedTypeForBuilder;
35 import org.opendaylight.mdsal.binding.javav2.model.api.MethodSignature;
36 import org.opendaylight.mdsal.binding.javav2.model.api.Restrictions;
37 import org.opendaylight.mdsal.binding.javav2.model.api.Type;
38 import org.opendaylight.mdsal.binding.javav2.model.api.TypeComment;
39 import org.opendaylight.mdsal.binding.javav2.model.api.TypeMember;
40 import org.opendaylight.mdsal.binding.javav2.model.api.YangSourceDefinition;
41 import org.opendaylight.mdsal.binding.javav2.model.api.YangSourceDefinition.Multiple;
42 import org.opendaylight.mdsal.binding.javav2.model.api.YangSourceDefinition.Single;
43 import org.opendaylight.mdsal.binding.javav2.spec.runtime.BindingNamespaceType;
44 import org.opendaylight.yangtools.concepts.Builder;
45 import org.opendaylight.yangtools.yang.common.QName;
46 import org.opendaylight.yangtools.yang.model.api.DocumentedNode;
47 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
48 import org.opendaylight.yangtools.yang.model.api.Module;
49 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
51 public final class TextTemplateUtil {
53 public static final String DOT = ".";
54 public static final String PATTERN_CONSTANT_NAME = "PATTERN_CONSTANTS";
56 private static final char NEW_LINE = '\n';
57 private static final String UNDERSCORE = "_";
58 private static final CharMatcher NEWLINE_OR_TAB = CharMatcher.anyOf("\n\t");
59 private static final CharMatcher NL_MATCHER = CharMatcher.is(NEW_LINE);
60 private static final CharMatcher AMP_MATCHER = CharMatcher.is('&');
61 private static final CharMatcher GT_MATCHER = CharMatcher.is('>');
62 private static final CharMatcher LT_MATCHER = CharMatcher.is('<');
63 private static final Splitter NL_SPLITTER = Splitter.on(NL_MATCHER);
64 private static final Splitter BSDOT_SPLITTER = Splitter.on(".");
66 private static final Pattern TAIL_COMMENT_PATTERN = Pattern.compile("*/", Pattern.LITERAL);
67 private static final Pattern MULTIPLE_SPACES_PATTERN = Pattern.compile(" +");
69 private TextTemplateUtil() {
70 throw new UnsupportedOperationException("Util class");
74 * Makes start of getter name LowerCase
76 * @param s getter name without prefix
77 * @return getter name starting in LowerCase
79 public static String toFirstLower(final String s) {
80 return s != null && s.length() != 0 ? Character.isLowerCase(s.charAt(0)) ? s : s.length() == 1 ?
81 s.toLowerCase() : s.substring(0, 1).toLowerCase() + s.substring(1) : s;
85 * Wraps text as documentation, used in enum description
87 * @param text text for wrapping
88 * @return wrapped text
90 public static String wrapToDocumentation(final String text) {
94 final StringBuilder sb = new StringBuilder(NEW_LINE);
97 for (final String t : NL_SPLITTER.split(text)) {
106 return sb.toString();
110 * Returns formatted Javadoc, based on type
111 * @param typeName given type name
112 * @return formatted Javadoc, based on type
114 public static String formatDataForJavaDocBuilder(final String typeName) {
115 final StringBuilder stringBuilder = new StringBuilder();
116 stringBuilder.append("Class that builds {@link ")
118 .append("} instances.")
122 return stringBuilder.toString();
126 * Returns formatted Javadoc with possible additional comment, based on type
127 * @param type given type
128 * @param additionalComment additional comment to format
129 * @return formatted Javadoc with possible additional comment, based on type
131 public static String formatDataForJavaDoc(final GeneratedType type, final String additionalComment) {
132 final StringBuilder javaDoc = new StringBuilder();
133 javaDoc.append(formatDataForJavaDoc(type)).append(additionalComment);
134 return javaDoc.toString();
137 private static void appendSnippet(final StringBuilder sb, final GeneratedType type) {
138 Optional<YangSourceDefinition> optDef = type.getYangSourceDefinition();
139 if (optDef.isPresent()) {
140 YangSourceDefinition def = optDef.get();
143 if (def instanceof Single) {
144 DocumentedNode node = ((Single) def).getNode();
146 .append("This class represents the following YANG schema fragment defined in module <b>")
147 .append(def.getModule().getName()).append("</b>\n")
149 .append(encodeAngleBrackets(encodeJavadocSymbols(
150 YangSnippetCleaner.clean(generateYangSnippet(node, def.getModule())))))
153 if (node instanceof SchemaNode) {
154 sb.append("The schema path to identify an instance is\n")
156 .append(formatSchemaPath(def.getModule().getName(), ((SchemaNode) node).getPath().getPathFromRoot()))
159 if (hasBuilderClass(type)) {
160 final String builderName = new StringBuilder()
161 .append(((GeneratedTypeForBuilder) type).getPackageNameForBuilder())
162 .append(".").append(type.getName()).append("Builder").toString();
164 sb.append("\n<p>To create instances of this class use {@link ").append(builderName)
166 .append("@see ").append(builderName).append('\n');
167 if (node instanceof ListSchemaNode) {
168 final StringBuilder linkToKeyClass = new StringBuilder();
170 final String[] namespace = Iterables.toArray(
171 BSDOT_SPLITTER.split(type.getFullyQualifiedName()), String.class);
172 final String className = namespace[namespace.length - 1];
174 linkToKeyClass.append(BindingGeneratorUtil.packageNameForSubGeneratedType(
175 ((GeneratedTypeForBuilder) type).getBasePackageName(), (SchemaNode) node,
176 BindingNamespaceType.Key)).append('.').append(className).append("Key");
178 List<QName> keyDef = ((ListSchemaNode) node).getKeyDefinition();
179 if (keyDef != null && !keyDef.isEmpty()) {
180 sb.append("@see ").append(linkToKeyClass);
186 } else if (def instanceof Multiple) {
187 sb.append("<pre>\n");
188 sb.append(encodeAngleBrackets(encodeJavadocSymbols(
189 YangSnippetCleaner.clean(generateYangSnippet(((Multiple) def).getNodes(), def.getModule())))));
190 sb.append("</pre>\n");
196 * Generate a YANG snippet for specified SchemaNode.
198 * @param node node for which to generate a snippet
199 * @return YANG snippet
201 private static String generateYangSnippet(final DocumentedNode node, final Module module) {
202 if (node instanceof Module) {
203 return yangTemplateForModule.render((Module) node).body();
204 } else if (node instanceof SchemaNode) {
205 return yangTemplateForNode.render((SchemaNode) node, module).body();
208 throw new IllegalArgumentException("Not supported.");
211 private static String generateYangSnippet(final Collection<? extends SchemaNode> nodes, final Module module) {
212 return yangTemplateForNodes.render(nodes, module).body();
215 public static boolean hasBuilderClass(final GeneratedType type) {
216 return type instanceof GeneratedTypeForBuilder;
219 public static String formatSchemaPath(final String moduleName, final Iterable<QName> schemaPath) {
220 final StringBuilder sb = new StringBuilder();
221 sb.append(moduleName);
223 QName currentElement = Iterables.getFirst(schemaPath, null);
224 for (final QName pathElement : schemaPath) {
226 if (!currentElement.getNamespace().equals(pathElement.getNamespace())) {
227 currentElement = pathElement;
228 sb.append(pathElement);
230 sb.append(pathElement.getLocalName());
233 return sb.toString();
237 * Returns properties names in formatted string
238 * @param properties list of given properties
239 * @return properties names in formatted string
241 //FIXME: this needs further clarification in future patch
242 public static String valueForBits(final List<GeneratedProperty> properties) {
243 return String.join(",", Lists.transform(properties, TextTemplateUtil::fieldName));
247 * Returns formatted type description
248 * @param type given type
249 * @return formatted type description
251 public static String formatDataForJavaDoc(final GeneratedType type) {
252 final StringBuilder javaDoc = new StringBuilder();
253 final TypeComment comment = type.getComment();
254 if (comment != null) {
255 javaDoc.append(comment.getJavadoc())
261 appendSnippet(javaDoc, type);
263 return javaDoc.toString();
267 * Returns parameter name, based on given Type
268 * @param returnType given type
269 * @param paramName parameter name
270 * @return parameter name, based on given Type
272 public static String paramValue(final Type returnType, final String paramName) {
273 return returnType instanceof ConcreteType ? paramName : paramName + ".getValue()";
277 * Template method which generates JAVA comments. InterfaceTemplate
279 * @param comment comment string with the comment for whole JAVA class
280 * @return string with comment in JAVA format
282 public static String asJavadoc(final String comment) {
283 return comment == null ? "" : wrapToDocumentation(formatToParagraph(comment.trim(), 0));
286 private static String formatDataForJavaDoc(final TypeMember type, final String additionalComment) {
287 final StringBuilder javaDoc = new StringBuilder();
288 if (type.getComment() != null && !type.getComment().isEmpty()) {
289 javaDoc.append(formatToParagraph(type.getComment(), 0))
294 javaDoc.append(additionalComment);
295 return wrapToDocumentation(javaDoc.toString());
299 * Returns related Javadoc
300 * @param methodSignature method signature
301 * @return related Javadoc
303 public static String getJavaDocForInterface(final MethodSignature methodSignature) {
304 if (methodSignature.getReturnType() == Types.VOID) {
307 final StringBuilder javaDoc = new StringBuilder();
308 javaDoc.append("@return ")
309 .append(asCode(methodSignature.getReturnType().getFullyQualifiedName()))
311 .append(asCode(propertyNameFromGetter(methodSignature)))
313 .append(asCode("null"))
314 .append(" if not present");
315 return formatDataForJavaDoc(methodSignature, javaDoc.toString());
318 private static String asCode(final String text) {
319 return "<code>" + text + "</code>";
323 * Encodes angle brackets in yang statement description
324 * @param description description of a yang statement which is used to generate javadoc comments
325 * @return string with encoded angle brackets
327 public static String encodeAngleBrackets(String description) {
328 if (description != null) {
329 description = LT_MATCHER.replaceFrom(description, "<");
330 description = GT_MATCHER.replaceFrom(description, ">");
336 * Returns collection of properties as formatted String
337 * @param properties list of given properties
338 * @return generated properties as formatted String
340 public static String propsAsArgs(final Iterable<GeneratedProperty> properties) {
341 return String.join(",", Iterables.transform(properties, prop -> "\"" + prop.getName() + "\""));
345 * Returns properties as formatted String
346 * @param properties list of given properties
347 * @param booleanName Java Boolean type name
348 * @return Properties as formatted String
350 public static String propsAsList(final Iterable<GeneratedProperty> properties, final String booleanName) {
351 return String.join(",", Iterables.transform(properties,
352 prop -> "properties.get(i++).equals(defaultValue) ? " + booleanName + ".TRUE : null"));
356 * Extracts available restrictions from given type
357 * @param currentType given type
358 * @return restrictions from given type
360 public static Restrictions getRestrictions(final Type currentType) {
361 Restrictions restrictions = null;
362 if (currentType instanceof ConcreteType) {
363 restrictions = ((ConcreteType) currentType).getRestrictions();
364 } else if (currentType instanceof GeneratedTransferObject) {
365 restrictions = ((GeneratedTransferObject) currentType).getRestrictions();
371 * sets fieldname according to property for return type
372 * method(type fieldname)
374 * @param property type from getter
375 * @return underscored string form
377 public static String fieldName(final GeneratedProperty property) {
378 final String name = Preconditions.checkNotNull(property.getName());
379 return UNDERSCORE.concat(name);
383 * Template method which generates sequence of the names of the class attributes.
385 * @param parameters group of generated property instances which are transformed to the sequence of parameter names
386 * @return string with the list of the parameter names
388 public static String asArguments(final List<GeneratedProperty> parameters) {
389 return String.join(", ", Lists.transform(parameters, TextTemplateUtil::fieldName));
393 * Helper method for building getter.
395 * @param field property name
396 * @return getter for property
398 public static String getterMethodName(final GeneratedProperty field) {
399 final Type type = Preconditions.checkNotNull(field.getReturnType());
400 final String name = Preconditions.checkNotNull(field.getName());
401 final String prefix = Types.BOOLEAN.equals(type) ? "is" : "get";
402 return prefix.concat(toFirstUpper(name));
406 * Returns built setter method body from input parameters.
407 * @param field generated property
408 * @param typeName type name
409 * @param returnTypeName return type name
410 * @return built setter method body
412 public static String setterMethod(final GeneratedProperty field, final String typeName, final String returnTypeName) {
413 final StringBuilder stringBuilder = new StringBuilder();
414 stringBuilder.append("public ")
417 .append(toFirstUpper(field.getName()))
419 .append(returnTypeName)
420 .append(" value) {\n this.")
421 .append(fieldName(field))
422 .append(" = value;\n return this;\n}\n");
423 return stringBuilder.toString();
427 * Returns simple name of underlying class
428 * @return Simple name of underlying class
430 public static String getSimpleNameForBuilder() {
431 return Builder.class.getSimpleName();
435 * Makes start of getter name uppercase
437 * @param s getter name without prefix
438 * @return getter name starting in uppercase
440 public static String toFirstUpper(final String s) {
441 return s != null && s.length() != 0 ? Character.isUpperCase(s.charAt(0)) ? s : s.length() == 1 ?
442 s.toUpperCase() : s.substring(0, 1).toUpperCase() + s.substring(1) : s;
446 * Cuts prefix from getter name.
448 * @param getter getter name
449 * @return getter name without prefix
451 public static String propertyNameFromGetter(final MethodSignature getter) {
452 final String name = Preconditions.checkNotNull(getter.getName());
454 if (name.startsWith("is")) {
456 } else if (name.startsWith("get")) {
461 return toFirstLower(name.substring(prefix));
465 * Returns list of properties as formatted String.
466 * @param properties input list of generated properties
467 * @return formatted property list as String
469 public static String getPropertyList(final List<GeneratedProperty> properties) {
470 return String.join(", ", Lists.transform(properties, prop -> "base." + getterMethodName(prop) + "()"));
474 * util method for unionTemplateBuilderTemplate
475 * @return string with clarification for javadoc
477 public static String getClarification() {
478 final StringBuilder clarification = new StringBuilder();
479 clarification.append("The purpose of generated class in src/main/java for Union types is to create new instances of unions from a string representation.\n")
480 .append("In some cases it is very difficult to automate it since there can be unions such as (uint32 - uint16), or (string - uint32).\n")
482 .append("The reason behind putting it under src/main/java is:\n")
483 .append("This class is generated in form of a stub and needs to be finished by the user. This class is generated only once to prevent\n")
484 .append("loss of user code.\n")
486 return clarification.toString();
490 * Returns source path as String.
491 * @param module module
492 * @param moduleFilePathResolver function module to module file path
493 * @return formatted String source path
495 public static String getSourcePath(final Module module, final Function<Module, Optional<String>> moduleFilePathResolver) {
496 final java.util.Optional<String> moduleFilePath = moduleFilePathResolver.apply(module);
497 Preconditions.checkArgument(moduleFilePath.isPresent(),"Module file path for %s is not present", module);
499 return moduleFilePath.get();
503 * Util method for unionTemplateBuilderTemplate.
504 * @param modifier enum representing Java access modifier
505 * @return needed access modifier
507 public static String getAccessModifier(final AccessModifier modifier) {
509 case PUBLIC: return "public ";
510 case PROTECTED: return "protected ";
511 case PRIVATE: return "private ";
517 * @param text Content of tag description
518 * @param nextLineIndent Number of spaces from left side default is 12
519 * @return formatted description
521 private static String formatToParagraph(final String text, final int nextLineIndent) {
522 if (Strings.isNullOrEmpty(text)) {
525 boolean isFirstElementOnNewLineEmptyChar = false;
526 final StringBuilder sb = new StringBuilder();
527 final StringBuilder lineBuilder = new StringBuilder();
528 final String lineIndent = Strings.repeat(" ", nextLineIndent);
529 final String textToFormat = NEWLINE_OR_TAB.removeFrom(encodeJavadocSymbols(text));
530 final String formattedText = MULTIPLE_SPACES_PATTERN.matcher(textToFormat).replaceAll(" ");
531 final StringTokenizer tokenizer = new StringTokenizer(formattedText, " ", true);
533 while (tokenizer.hasMoreElements()) {
534 final String nextElement = tokenizer.nextElement().toString();
536 if (lineBuilder.length() + nextElement.length() > 80) {
537 // Trim trailing whitespace
538 for (int i = lineBuilder.length() - 1; i >= 0 && lineBuilder.charAt(i) != ' '; --i) {
539 lineBuilder.setLength(i);
541 // Trim leading whitespace
542 while (lineBuilder.charAt(0) == ' ') {
543 lineBuilder.deleteCharAt(0);
545 sb.append(lineBuilder).append('\n');
546 lineBuilder.setLength(0);
548 if (nextLineIndent > 0) {
549 sb.append(lineIndent);
552 if (" ".equals(nextElement)) {
553 isFirstElementOnNewLineEmptyChar = true;
556 if (isFirstElementOnNewLineEmptyChar) {
557 isFirstElementOnNewLineEmptyChar = false;
559 lineBuilder.append(nextElement);
562 return sb.append(lineBuilder).append('\n').toString();
565 private static String encodeJavadocSymbols(final String description) {
566 return Strings.isNullOrEmpty(description) ? description
567 : AMP_MATCHER.replaceFrom(TAIL_COMMENT_PATTERN.matcher(description).replaceAll("*/"), "&");