99652f592a6849d2fee4a47a3f2817ee49d1f8f1
[mdsal.git] / binding2 / mdsal-binding2-java-api-generator / src / main / java / org / opendaylight / mdsal / binding / javav2 / java / api / generator / util / TextTemplateUtil.java
1 /*
2  * Copyright (c) 2017 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8
9 package org.opendaylight.mdsal.binding.javav2.java.api.generator.util;
10
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
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
24 import org.opendaylight.mdsal.binding.javav2.generator.util.BindingGeneratorUtil;
25 import org.opendaylight.mdsal.binding.javav2.generator.util.Types;
26 import org.opendaylight.mdsal.binding.javav2.model.api.AccessModifier;
27 import org.opendaylight.mdsal.binding.javav2.model.api.ConcreteType;
28 import org.opendaylight.mdsal.binding.javav2.model.api.GeneratedProperty;
29 import org.opendaylight.mdsal.binding.javav2.model.api.GeneratedTransferObject;
30 import org.opendaylight.mdsal.binding.javav2.model.api.GeneratedType;
31 import org.opendaylight.mdsal.binding.javav2.model.api.GeneratedTypeForBuilder;
32 import org.opendaylight.mdsal.binding.javav2.model.api.MethodSignature;
33 import org.opendaylight.mdsal.binding.javav2.model.api.Restrictions;
34 import org.opendaylight.mdsal.binding.javav2.model.api.Type;
35 import org.opendaylight.mdsal.binding.javav2.model.api.TypeComment;
36 import org.opendaylight.mdsal.binding.javav2.model.api.TypeMember;
37 import org.opendaylight.mdsal.binding.javav2.model.api.YangSourceDefinition;
38 import org.opendaylight.mdsal.binding.javav2.model.api.YangSourceDefinition.Multiple;
39 import org.opendaylight.mdsal.binding.javav2.model.api.YangSourceDefinition.Single;
40 import org.opendaylight.mdsal.binding.javav2.spec.runtime.BindingNamespaceType;
41 import org.opendaylight.yangtools.concepts.Builder;
42 import org.opendaylight.yangtools.yang.common.QName;
43 import org.opendaylight.yangtools.yang.model.api.DocumentedNode;
44 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
45 import org.opendaylight.yangtools.yang.model.api.Module;
46 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
47 import org.opendaylight.yangtools.yang.model.api.YangStmtMapping;
48 import org.opendaylight.yangtools.yang.model.api.meta.DeclaredStatement;
49 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
50 import org.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement;
51 import org.opendaylight.yangtools.yang.model.export.DeclaredStatementFormatter;
52
53 public final class TextTemplateUtil {
54
55     public static final String DOT = ".";
56
57     private static final char NEW_LINE = '\n';
58     private static final String UNDERSCORE = "_";
59     private static final CharMatcher NEWLINE_OR_TAB = CharMatcher.anyOf("\n\t");
60     private static final CharMatcher NL_MATCHER = CharMatcher.is(NEW_LINE);
61     private static final CharMatcher AMP_MATCHER = CharMatcher.is('&');
62     private static final CharMatcher GT_MATCHER = CharMatcher.is('>');
63     private static final CharMatcher LT_MATCHER = CharMatcher.is('<');
64     private static final Splitter NL_SPLITTER = Splitter.on(NL_MATCHER);
65     private static final Splitter BSDOT_SPLITTER = Splitter.on(".");
66
67     private static final Pattern TAIL_COMMENT_PATTERN = Pattern.compile("*/", Pattern.LITERAL);
68     private static final Pattern MULTIPLE_SPACES_PATTERN = Pattern.compile(" +");
69
70     private static DeclaredStatementFormatter YANG_FORMATTER = DeclaredStatementFormatter.builder()
71             .addIgnoredStatement(YangStmtMapping.CONTACT)
72             .addIgnoredStatement(YangStmtMapping.DESCRIPTION)
73             .addIgnoredStatement(YangStmtMapping.REFERENCE)
74             .addIgnoredStatement(YangStmtMapping.ORGANIZATION)
75             .build();
76
77     private TextTemplateUtil() {
78         throw new UnsupportedOperationException("Util class");
79     }
80
81     /**
82      * Makes start of getter name LowerCase.
83      *
84      * @param str getter name without prefix
85      * @return getter name starting in LowerCase
86      */
87     public static String toFirstLower(final String str) {
88         return str != null && str.length() != 0 ? Character.isLowerCase(str.charAt(0)) ? str : str.length() == 1
89                 ? str.toLowerCase() : str.substring(0, 1).toLowerCase() + str.substring(1) : str;
90     }
91
92     /**
93      * Wraps text as documentation, used in enum description.
94      *
95      * @param text text for wrapping
96      * @return wrapped text
97      */
98     public static String wrapToDocumentation(final String text) {
99         if (text.isEmpty()) {
100             return "";
101         }
102         final StringBuilder sb = new StringBuilder();
103         sb.append("/**");
104         sb.append(NEW_LINE);
105         for (final String t : NL_SPLITTER.split(text)) {
106             if (!t.isEmpty()) {
107                 sb.append(" * ");
108                 sb.append(t);
109                 sb.append(NEW_LINE);
110             }
111         }
112         sb.append(" */");
113         sb.append(NEW_LINE);
114         return sb.toString();
115     }
116
117     /**
118      * Returns formatted Javadoc, based on type.
119      *
120      * @param typeName given type name
121      * @return formatted Javadoc, based on type
122      */
123     public static String formatDataForJavaDocBuilder(final String typeName) {
124         final StringBuilder stringBuilder = new StringBuilder();
125         stringBuilder.append("Class that builds {@link ")
126                 .append(typeName)
127                 .append("} instances.")
128                 .append(NEW_LINE)
129                 .append("@see ")
130                 .append(typeName);
131         return stringBuilder.toString();
132     }
133
134     /**
135      * Returns formatted Javadoc with possible additional comment, based on type.
136      *
137      * @param type              given type
138      * @param additionalComment additional comment to format
139      * @return formatted Javadoc with possible additional comment, based on type
140      */
141     public static String formatDataForJavaDoc(final GeneratedType type, final String additionalComment) {
142         final StringBuilder javaDoc = new StringBuilder();
143         javaDoc.append(formatDataForJavaDoc(type)).append(additionalComment);
144         return javaDoc.toString();
145     }
146
147     /**
148      * Returns formatted type description.
149      *
150      * @param type given type
151      * @return formatted type description
152      */
153     public static String formatDataForJavaDoc(final GeneratedType type) {
154         final StringBuilder javaDoc = new StringBuilder();
155         final TypeComment comment = type.getComment();
156         if (comment != null) {
157             javaDoc.append(comment.getJavadoc())
158                     .append(NEW_LINE)
159                     .append(NEW_LINE)
160                     .append(NEW_LINE);
161         }
162
163         appendSnippet(javaDoc, type);
164
165         return javaDoc.toString();
166     }
167
168     private static String formatDataForJavaDoc(final TypeMember type, final String additionalComment) {
169         final StringBuilder javaDoc = new StringBuilder();
170         if (type.getComment() != null && !type.getComment().isEmpty()) {
171             javaDoc.append(formatToParagraph(type.getComment(), 0))
172                     .append(NEW_LINE)
173                     .append(NEW_LINE)
174                     .append(NEW_LINE);
175         }
176         javaDoc.append(additionalComment);
177         return wrapToDocumentation(javaDoc.toString());
178     }
179
180     private static void appendSnippet(final StringBuilder sb, final GeneratedType type) {
181         Optional<YangSourceDefinition> optDef = type.getYangSourceDefinition();
182         if (optDef.isPresent()) {
183             YangSourceDefinition def = optDef.get();
184             sb.append(NEW_LINE);
185
186             if (def instanceof Single) {
187                 DocumentedNode node = ((Single) def).getNode();
188                 sb.append("<p>\n")
189                         .append("This class represents the following YANG schema fragment defined in module <b>")
190                         .append(def.getModule().argument()).append("</b>\n")
191                         .append("<pre>\n");
192                 appendYangSnippet(sb, def.getModule(), ((EffectiveStatement<?, ?>) node).getDeclared());
193                 sb.append("</pre>");
194
195                 if (node instanceof SchemaNode) {
196                     sb.append("The schema path to identify an instance is\n")
197                             .append("<i>")
198                             .append(formatSchemaPath(def.getModule().argument(), ((SchemaNode) node).getPath()
199                                     .getPathFromRoot()))
200                             .append("</i>\n");
201
202                     if (hasBuilderClass(type)) {
203                         final String builderName = new StringBuilder()
204                                 .append(((GeneratedTypeForBuilder) type).getPackageNameForBuilder())
205                                 .append(".").append(type.getName()).append("Builder").toString();
206
207                         sb.append("\n<p>To create instances of this class use {@link ").append(builderName)
208                                 .append("}.\n")
209                                 .append("@see ").append(builderName).append('\n');
210                         if (node instanceof ListSchemaNode) {
211                             final StringBuilder linkToKeyClass = new StringBuilder();
212
213                             final String[] namespace = Iterables.toArray(
214                                     BSDOT_SPLITTER.split(type.getFullyQualifiedName()), String.class);
215                             final String className = namespace[namespace.length - 1];
216
217                             linkToKeyClass.append(BindingGeneratorUtil.packageNameForSubGeneratedType(
218                                     ((GeneratedTypeForBuilder) type).getBasePackageName(), (SchemaNode) node,
219                                     BindingNamespaceType.Key)).append('.').append(className).append("Key");
220
221                             List<QName> keyDef = ((ListSchemaNode) node).getKeyDefinition();
222                             if (keyDef != null && !keyDef.isEmpty()) {
223                                 sb.append("@see ").append(linkToKeyClass);
224                             }
225                             sb.append('\n');
226                         }
227                     }
228                 }
229             } else if (def instanceof Multiple) {
230                 sb.append("<pre>\n");
231                 for (SchemaNode node : ((Multiple) def).getNodes()) {
232                     appendYangSnippet(sb, def.getModule(), ((EffectiveStatement<?, ?>) node).getDeclared());
233                 }
234                 sb.append("</pre>\n");
235             }
236         }
237     }
238
239     private static void appendYangSnippet(StringBuilder sb, ModuleEffectiveStatement module,
240                                           DeclaredStatement<?> stmt) {
241         for (String str : YANG_FORMATTER.toYangTextSnippet(module, stmt)) {
242             sb.append(encodeAngleBrackets(encodeJavadocSymbols(str)));
243         }
244     }
245
246     public static boolean hasBuilderClass(final GeneratedType type) {
247         return type instanceof GeneratedTypeForBuilder;
248     }
249
250     public static String formatSchemaPath(final String moduleName, final Iterable<QName> schemaPath) {
251         final StringBuilder sb = new StringBuilder();
252         sb.append(moduleName);
253
254         QName currentElement = Iterables.getFirst(schemaPath, null);
255         for (final QName pathElement : schemaPath) {
256             sb.append('/');
257             if (!currentElement.getNamespace().equals(pathElement.getNamespace())) {
258                 currentElement = pathElement;
259                 sb.append(pathElement);
260             } else {
261                 sb.append(pathElement.getLocalName());
262             }
263         }
264         return sb.toString();
265     }
266
267     /**
268      * Returns properties names in formatted string.
269      *
270      * @param properties list of given properties
271      * @return properties names in formatted string
272      */
273     //FIXME: this needs further clarification in future patch
274     public static String valueForBits(final List<GeneratedProperty> properties) {
275         return String.join(",", Lists.transform(properties, TextTemplateUtil::fieldName));
276     }
277
278     /**
279      * Returns parameter name, based on given Type.
280      *
281      * @param returnType given type
282      * @param paramName  parameter name
283      * @return parameter name, based on given Type
284      */
285     public static String paramValue(final Type returnType, final String paramName) {
286         return returnType instanceof ConcreteType ? paramName : paramName + ".getValue()";
287     }
288
289     /**
290      * Template method which generates JAVA comments. InterfaceTemplate
291      *
292      * @param comment comment string with the comment for whole JAVA class
293      * @return string with comment in JAVA format
294      */
295     public static String asJavadoc(final String comment) {
296         return comment == null ? "" : wrapToDocumentation(formatToParagraph(comment.trim(), 0));
297     }
298
299     /**
300      * Returns related Javadoc.
301      *
302      * @param methodSignature method signature
303      * @return related Javadoc
304      */
305     public static String getJavaDocForInterface(final MethodSignature methodSignature) {
306         if (methodSignature.getReturnType() == Types.VOID) {
307             return "";
308         }
309         final StringBuilder javaDoc = new StringBuilder();
310         javaDoc.append("@return ")
311                 .append(asCode(methodSignature.getReturnType().getFullyQualifiedName()))
312                 .append(" ")
313                 .append(asCode(propertyNameFromGetter(methodSignature)))
314                 .append(", or ")
315                 .append(asCode("null"))
316                 .append(" if not present");
317         return formatDataForJavaDoc(methodSignature, javaDoc.toString());
318     }
319
320     private static String asCode(final String text) {
321         return "<code>" + text + "</code>";
322     }
323
324     /**
325      * Encodes angle brackets in yang statement description.
326      *
327      * @param description description of a yang statement which is used to generate javadoc comments
328      * @return string with encoded angle brackets
329      */
330     public static String encodeAngleBrackets(String description) {
331         if (description != null) {
332             description = LT_MATCHER.replaceFrom(description, "&lt;");
333             description = GT_MATCHER.replaceFrom(description, "&gt;");
334         }
335         return description;
336     }
337
338     /**
339      * Returns collection of properties as formatted String.
340      *
341      * @param properties list of given properties
342      * @return generated properties as formatted String
343      */
344     public static String propsAsArgs(final Iterable<GeneratedProperty> properties) {
345         return String.join(",", Iterables.transform(properties, prop -> "\"" + prop.getName() + "\""));
346     }
347
348     /**
349      * Returns properties as formatted String.
350      *
351      * @param properties  list of given properties
352      * @param booleanName Java Boolean type name
353      * @return Properties as formatted String
354      */
355     public static String propsAsList(final Iterable<GeneratedProperty> properties, final String booleanName) {
356         return String.join(",", Iterables.transform(properties,
357             prop -> "properties.get(i++).equals(defaultValue) ? " + booleanName + ".TRUE : null"));
358     }
359
360     /**
361      * Extracts available restrictions from given type.
362      *
363      * @param currentType given type
364      * @return restrictions from given type
365      */
366     public static Restrictions getRestrictions(final Type currentType) {
367         Restrictions restrictions = null;
368         if (currentType instanceof ConcreteType) {
369             restrictions = ((ConcreteType) currentType).getRestrictions();
370         } else if (currentType instanceof GeneratedTransferObject) {
371             restrictions = ((GeneratedTransferObject) currentType).getRestrictions();
372         }
373         return restrictions;
374     }
375
376     /**
377      * sets fieldname according to property for return type.
378      * method(type fieldname)
379      *
380      * @param property type from getter
381      * @return underscored string form
382      */
383     public static String fieldName(final GeneratedProperty property) {
384         final String name = Preconditions.checkNotNull(property.getName());
385         return UNDERSCORE.concat(name);
386     }
387
388     /**
389      * Template method which generates sequence of the names of the class attributes.
390      *
391      * @param parameters group of generated property instances which are transformed to the sequence of parameter names
392      * @return string with the list of the parameter names
393      */
394     public static String asArguments(final List<GeneratedProperty> parameters) {
395         return String.join(", ", Lists.transform(parameters, TextTemplateUtil::fieldName));
396     }
397
398     /**
399      * Helper method for building getter.
400      *
401      * @param field property name
402      * @return getter for property
403      */
404     public static String getterMethodName(final GeneratedProperty field) {
405         final Type type = Preconditions.checkNotNull(field.getReturnType());
406         final String name = Preconditions.checkNotNull(field.getName());
407         final String prefix = Types.BOOLEAN.equals(type) ? "is" : "get";
408         return prefix.concat(toFirstUpper(name));
409     }
410
411     /**
412      * Returns built setter method body from input parameters.
413      *
414      * @param field          generated property
415      * @param typeName       type name
416      * @param returnTypeName return type name
417      * @return built setter method body
418      */
419     public static String setterMethod(final GeneratedProperty field, final String typeName, final String
420             returnTypeName) {
421         final StringBuilder stringBuilder = new StringBuilder();
422         stringBuilder.append("public ")
423                 .append(typeName)
424                 .append(" set")
425                 .append(toFirstUpper(field.getName()))
426                 .append('(')
427                 .append(returnTypeName)
428                 .append(" value) {\n    this.")
429                 .append(fieldName(field))
430                 .append(" = value;\n    return this;\n}\n");
431         return stringBuilder.toString();
432     }
433
434     /**
435      * Returns simple name of underlying class.
436      *
437      * @return Simple name of underlying class
438      */
439     public static String getSimpleNameForBuilder() {
440         return Builder.class.getSimpleName();
441     }
442
443     /**
444      * Makes start of getter name uppercase.
445      *
446      * @param str getter name without prefix
447      * @return getter name starting in uppercase
448      */
449     public static String toFirstUpper(final String str) {
450         return str != null && str.length() != 0 ? Character.isUpperCase(str.charAt(0)) ? str : str.length() == 1
451                 ? str.toUpperCase() : str.substring(0, 1).toUpperCase() + str.substring(1) : str;
452     }
453
454     /**
455      * Cuts prefix from getter name.
456      *
457      * @param getter getter name
458      * @return getter name without prefix
459      */
460     public static String propertyNameFromGetter(final MethodSignature getter) {
461         final String name = Preconditions.checkNotNull(getter.getName());
462         int prefix;
463         if (name.startsWith("is")) {
464             prefix = 2;
465         } else if (name.startsWith("get")) {
466             prefix = 3;
467         } else {
468             prefix = 0;
469         }
470         return toFirstLower(name.substring(prefix));
471     }
472
473     /**
474      * Returns list of properties as formatted String.
475      *
476      * @param properties input list of generated properties
477      * @return formatted property list as String
478      */
479     public static String getPropertyList(final List<GeneratedProperty> properties) {
480         return String.join(", ", Lists.transform(properties, prop -> "base." + getterMethodName(prop) + "()"));
481     }
482
483     /**
484      * util method for unionTemplateBuilderTemplate.
485      *
486      * @return string with clarification for javadoc
487      */
488     public static String getClarification() {
489         final StringBuilder clarification = new StringBuilder();
490         clarification.append("The purpose of generated class in src/main/java for Union types is to create new "
491                 + "instances of unions from a string representation.\n")
492                 .append("In some cases it is very difficult to automate it since there can be unions such as (uint32 "
493                         + "- uint16), or (string - uint32).\n")
494                 .append("\n")
495                 .append("The reason behind putting it under src/main/java is:\n")
496                 .append("This class is generated in form of a stub and needs to be finished by the user. This class "
497                         + "is generated only once to prevent\n")
498                 .append("loss of user code.\n")
499                 .append("\n");
500         return clarification.toString();
501     }
502
503     /**
504      * Returns source path as String.
505      *
506      * @param module                 module
507      * @param moduleFilePathResolver function module to module file path
508      * @return formatted String source path
509      */
510     public static String getSourcePath(final Module module, final Function<Module, Optional<String>>
511             moduleFilePathResolver) {
512         final java.util.Optional<String> moduleFilePath = moduleFilePathResolver.apply(module);
513         Preconditions.checkArgument(moduleFilePath.isPresent(), "Module file path for %s is not present", module);
514
515         return moduleFilePath.get();
516     }
517
518     /**
519      * Util method for unionTemplateBuilderTemplate.
520      *
521      * @param modifier enum representing Java access modifier
522      * @return needed access modifier
523      */
524     public static String getAccessModifier(final AccessModifier modifier) {
525         switch (modifier) {
526             case PUBLIC:
527                 return "public ";
528             case PROTECTED:
529                 return "protected ";
530             case PRIVATE:
531                 return "private ";
532             default:
533                 return "";
534         }
535     }
536
537     /**
538      * Return formatted description.
539      * @param text           Content of tag description
540      * @param nextLineIndent Number of spaces from left side default is 12
541      * @return formatted description
542      */
543     private static String formatToParagraph(final String text, final int nextLineIndent) {
544         if (Strings.isNullOrEmpty(text)) {
545             return "";
546         }
547         boolean isFirstElementOnNewLineEmptyChar = false;
548         final StringBuilder sb = new StringBuilder();
549         final StringBuilder lineBuilder = new StringBuilder();
550         final String lineIndent = Strings.repeat(" ", nextLineIndent);
551         final String textToFormat = NEWLINE_OR_TAB.removeFrom(encodeJavadocSymbols(text));
552         final String formattedText = MULTIPLE_SPACES_PATTERN.matcher(textToFormat).replaceAll(" ");
553         final StringTokenizer tokenizer = new StringTokenizer(formattedText, " ", true);
554
555         while (tokenizer.hasMoreElements()) {
556             final String nextElement = tokenizer.nextElement().toString();
557
558             if (lineBuilder.length() + nextElement.length() > 80) {
559                 // Trim trailing whitespace
560                 for (int i = lineBuilder.length() - 1; i >= 0 && lineBuilder.charAt(i) != ' '; --i) {
561                     lineBuilder.setLength(i);
562                 }
563                 // Trim leading whitespace
564                 while (lineBuilder.charAt(0) == ' ') {
565                     lineBuilder.deleteCharAt(0);
566                 }
567                 sb.append(lineBuilder).append('\n');
568                 lineBuilder.setLength(0);
569
570                 if (nextLineIndent > 0) {
571                     sb.append(lineIndent);
572                 }
573
574                 if (" ".equals(nextElement)) {
575                     isFirstElementOnNewLineEmptyChar = true;
576                 }
577             }
578             if (isFirstElementOnNewLineEmptyChar) {
579                 isFirstElementOnNewLineEmptyChar = false;
580             } else {
581                 lineBuilder.append(nextElement);
582             }
583         }
584         return sb.append(lineBuilder).append('\n').toString();
585     }
586
587     private static String encodeJavadocSymbols(final String description) {
588         return Strings.isNullOrEmpty(description) ? description
589                 : AMP_MATCHER.replaceFrom(TAIL_COMMENT_PATTERN.matcher(description).replaceAll("&#42;&#47;"), "&amp;");
590     }
591 }