e9c177c7023088f2bcb32adcc585ce78232e0f4b
[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 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;
50
51 public final class TextTemplateUtil {
52
53     public static final String DOT = ".";
54     public static final String PATTERN_CONSTANT_NAME = "PATTERN_CONSTANTS";
55
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(".");
65
66     private static final Pattern TAIL_COMMENT_PATTERN = Pattern.compile("*/", Pattern.LITERAL);
67     private static final Pattern MULTIPLE_SPACES_PATTERN = Pattern.compile(" +");
68
69     private TextTemplateUtil() {
70         throw new UnsupportedOperationException("Util class");
71     }
72
73     /**
74      * Makes start of getter name LowerCase
75      *
76      * @param s getter name without prefix
77      * @return getter name starting in LowerCase
78      */
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;
82     }
83
84     /**
85      * Wraps text as documentation, used in enum description
86      *
87      * @param text text for wrapping
88      * @return wrapped text
89      */
90     public static String wrapToDocumentation(final String text) {
91         if (text.isEmpty()) {
92             return "";
93         }
94         final StringBuilder sb = new StringBuilder(NEW_LINE);
95         sb.append("/**");
96         sb.append(NEW_LINE);
97         for (final String t : NL_SPLITTER.split(text)) {
98             if (!t.isEmpty()) {
99                 sb.append(" * ");
100                 sb.append(t);
101                 sb.append(NEW_LINE);
102             }
103         }
104         sb.append(" */");
105         sb.append(NEW_LINE);
106         return sb.toString();
107     }
108
109     /**
110      * Returns formatted Javadoc, based on type
111      * @param typeName given type name
112      * @return formatted Javadoc, based on type
113      */
114     public static String formatDataForJavaDocBuilder(final String typeName) {
115         final StringBuilder stringBuilder = new StringBuilder();
116         stringBuilder.append("Class that builds {@link ")
117                 .append(typeName)
118                 .append("} instances.")
119                 .append(NEW_LINE)
120                 .append("@see ")
121                 .append(typeName);
122         return stringBuilder.toString();
123     }
124
125     /**
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
130      */
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();
135     }
136
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();
141             sb.append(NEW_LINE);
142
143             if (def instanceof Single) {
144                 DocumentedNode node = ((Single) def).getNode();
145                 sb.append("<p>\n")
146                     .append("This class represents the following YANG schema fragment defined in module <b>")
147                     .append(def.getModule().getName()).append("</b>\n")
148                     .append("<pre>\n")
149                     .append(encodeAngleBrackets(encodeJavadocSymbols(
150                         YangSnippetCleaner.clean(generateYangSnippet(node, def.getModule())))))
151                     .append("</pre>");
152
153                 if (node instanceof SchemaNode) {
154                     sb.append("The schema path to identify an instance is\n")
155                         .append("<i>")
156                         .append(formatSchemaPath(def.getModule().getName(), ((SchemaNode) node).getPath().getPathFromRoot()))
157                         .append("</i>\n");
158
159                     if (hasBuilderClass(type)) {
160                         final String builderName = new StringBuilder()
161                             .append(((GeneratedTypeForBuilder) type).getPackageNameForBuilder())
162                             .append(".").append(type.getName()).append("Builder").toString();
163
164                         sb.append("\n<p>To create instances of this class use {@link ").append(builderName)
165                             .append("}.\n")
166                             .append("@see ").append(builderName).append('\n');
167                         if (node instanceof ListSchemaNode) {
168                             final StringBuilder linkToKeyClass = new StringBuilder();
169
170                             final String[] namespace = Iterables.toArray(
171                                 BSDOT_SPLITTER.split(type.getFullyQualifiedName()), String.class);
172                             final String className = namespace[namespace.length - 1];
173
174                             linkToKeyClass.append(BindingGeneratorUtil.packageNameForSubGeneratedType(
175                                 ((GeneratedTypeForBuilder) type).getBasePackageName(), (SchemaNode) node,
176                                 BindingNamespaceType.Key)).append('.').append(className).append("Key");
177
178                             List<QName> keyDef = ((ListSchemaNode) node).getKeyDefinition();
179                             if (keyDef != null && !keyDef.isEmpty()) {
180                                 sb.append("@see ").append(linkToKeyClass);
181                             }
182                             sb.append('\n');
183                         }
184                     }
185                 }
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");
191             }
192         }
193     }
194
195       /**
196      * Generate a YANG snippet for specified SchemaNode.
197      *
198      * @param node node for which to generate a snippet
199      * @return YANG snippet
200      */
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();
206         }
207
208         throw new IllegalArgumentException("Not supported.");
209     }
210
211     private static String generateYangSnippet(final Collection<? extends SchemaNode> nodes, final Module module) {
212         return yangTemplateForNodes.render(nodes, module).body();
213     }
214
215     public static boolean hasBuilderClass(final GeneratedType type) {
216         return type instanceof GeneratedTypeForBuilder;
217     }
218
219     public static String formatSchemaPath(final String moduleName, final Iterable<QName> schemaPath) {
220         final StringBuilder sb = new StringBuilder();
221         sb.append(moduleName);
222
223         QName currentElement = Iterables.getFirst(schemaPath, null);
224         for (final QName pathElement : schemaPath) {
225             sb.append('/');
226             if (!currentElement.getNamespace().equals(pathElement.getNamespace())) {
227                 currentElement = pathElement;
228                 sb.append(pathElement);
229             } else {
230                 sb.append(pathElement.getLocalName());
231             }
232         }
233         return sb.toString();
234     }
235
236     /**
237      * Returns properties names in formatted string
238      * @param properties list of given properties
239      * @return properties names in formatted string
240      */
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));
244     }
245
246     /**
247      * Returns formatted type description
248      * @param type given type
249      * @return formatted type description
250      */
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())
256                 .append(NEW_LINE)
257                 .append(NEW_LINE)
258                 .append(NEW_LINE);
259         }
260
261         appendSnippet(javaDoc, type);
262
263         return javaDoc.toString();
264     }
265
266     /**
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
271      */
272     public static String paramValue(final Type returnType, final String paramName) {
273         return returnType instanceof ConcreteType ? paramName : paramName + ".getValue()";
274     }
275
276     /**
277      * Template method which generates JAVA comments. InterfaceTemplate
278      *
279      * @param comment comment string with the comment for whole JAVA class
280      * @return string with comment in JAVA format
281      */
282     public static String asJavadoc(final String comment) {
283         return comment == null ? "" : wrapToDocumentation(formatToParagraph(comment.trim(), 0));
284     }
285
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))
290                     .append(NEW_LINE)
291                     .append(NEW_LINE)
292                     .append(NEW_LINE);
293         }
294         javaDoc.append(additionalComment);
295         return wrapToDocumentation(javaDoc.toString());
296     }
297
298     /**
299      * Returns related Javadoc
300      * @param methodSignature method signature
301      * @return related Javadoc
302      */
303     public static String getJavaDocForInterface(final MethodSignature methodSignature) {
304         if (methodSignature.getReturnType() == Types.VOID) {
305             return "";
306         }
307         final StringBuilder javaDoc = new StringBuilder();
308         javaDoc.append("@return ")
309                 .append(asCode(methodSignature.getReturnType().getFullyQualifiedName()))
310                 .append(" ")
311                 .append(asCode(propertyNameFromGetter(methodSignature)))
312                 .append(", or ")
313                 .append(asCode("null"))
314                 .append(" if not present");
315         return formatDataForJavaDoc(methodSignature, javaDoc.toString());
316     }
317
318     private static String asCode(final String text) {
319         return "<code>" + text + "</code>";
320     }
321
322     /**
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
326      */
327     public static String encodeAngleBrackets(String description) {
328         if (description != null) {
329             description = LT_MATCHER.replaceFrom(description, "&lt;");
330             description = GT_MATCHER.replaceFrom(description, "&gt;");
331         }
332         return description;
333     }
334
335     /**
336      * Returns collection of properties as formatted String
337      * @param properties list of given properties
338      * @return generated properties as formatted String
339      */
340     public static String propsAsArgs(final Iterable<GeneratedProperty> properties) {
341         return String.join(",", Iterables.transform(properties, prop -> "\"" + prop.getName() + "\""));
342     }
343
344     /**
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
349      */
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"));
353     }
354
355     /**
356      * Extracts available restrictions from given type
357      * @param currentType given type
358      * @return restrictions from given type
359      */
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();
366         }
367         return restrictions;
368     }
369
370     /**
371      * sets fieldname according to property for return type
372      * method(type fieldname)
373      *
374      * @param property type from getter
375      * @return underscored string form
376      */
377     public static String fieldName(final GeneratedProperty property) {
378         final String name = Preconditions.checkNotNull(property.getName());
379         return UNDERSCORE.concat(name);
380     }
381
382     /**
383      * Template method which generates sequence of the names of the class attributes.
384      *
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
387      */
388     public static String asArguments(final List<GeneratedProperty> parameters) {
389         return String.join(", ", Lists.transform(parameters, TextTemplateUtil::fieldName));
390     }
391
392     /**
393      * Helper method for building getter.
394      *
395      * @param field property name
396      * @return getter for property
397      */
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));
403     }
404
405     /**
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
411      */
412     public static String setterMethod(final GeneratedProperty field, final String typeName, final String returnTypeName) {
413         final StringBuilder stringBuilder = new StringBuilder();
414         stringBuilder.append("public ")
415                 .append(typeName)
416                 .append(" set")
417                 .append(toFirstUpper(field.getName()))
418                 .append('(')
419                 .append(returnTypeName)
420                 .append(" value) {\n    this.")
421                 .append(fieldName(field))
422                 .append(" = value;\n    return this;\n}\n");
423         return stringBuilder.toString();
424     }
425
426     /**
427      * Returns simple name of underlying class
428      * @return Simple name of underlying class
429      */
430     public static String getSimpleNameForBuilder() {
431         return Builder.class.getSimpleName();
432     }
433
434     /**
435      * Makes start of getter name uppercase
436      *
437      * @param s getter name without prefix
438      * @return getter name starting in uppercase
439      */
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;
443     }
444
445     /**
446      * Cuts prefix from getter name.
447      *
448      * @param getter getter name
449      * @return getter name without prefix
450      */
451     public static String propertyNameFromGetter(final MethodSignature getter) {
452         final String name = Preconditions.checkNotNull(getter.getName());
453         int prefix;
454         if (name.startsWith("is")) {
455             prefix = 2;
456         } else if (name.startsWith("get")) {
457             prefix = 3;
458         } else {
459             prefix = 0;
460         }
461         return toFirstLower(name.substring(prefix));
462     }
463
464     /**
465      * Returns list of properties as formatted String.
466      * @param properties input list of generated properties
467      * @return formatted property list as String
468      */
469     public static String getPropertyList(final List<GeneratedProperty> properties) {
470         return String.join(", ", Lists.transform(properties, prop -> "base." + getterMethodName(prop) + "()"));
471     }
472
473     /**
474      * util method for unionTemplateBuilderTemplate
475      * @return string with clarification for javadoc
476      */
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")
481                 .append("\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")
485                 .append("\n");
486         return clarification.toString();
487     }
488
489     /**
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
494      */
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);
498
499         return moduleFilePath.get();
500     }
501
502     /**
503      * Util method for unionTemplateBuilderTemplate.
504      * @param modifier enum representing Java access modifier
505      * @return needed access modifier
506      */
507     public static String getAccessModifier(final AccessModifier modifier) {
508         switch (modifier) {
509             case PUBLIC: return "public ";
510             case PROTECTED: return "protected ";
511             case PRIVATE: return "private ";
512             default: return "";
513         }
514     }
515
516     /**
517      * @param text Content of tag description
518      * @param nextLineIndent Number of spaces from left side default is 12
519      * @return formatted description
520      */
521     private static String formatToParagraph(final String text, final int nextLineIndent) {
522         if (Strings.isNullOrEmpty(text)) {
523             return "";
524         }
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);
532
533         while (tokenizer.hasMoreElements()) {
534             final String nextElement = tokenizer.nextElement().toString();
535
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);
540                 }
541                 // Trim leading whitespace
542                 while (lineBuilder.charAt(0) == ' ') {
543                     lineBuilder.deleteCharAt(0);
544                 }
545                 sb.append(lineBuilder).append('\n');
546                 lineBuilder.setLength(0);
547
548                 if (nextLineIndent > 0) {
549                     sb.append(lineIndent);
550                 }
551
552                 if (" ".equals(nextElement)) {
553                     isFirstElementOnNewLineEmptyChar = true;
554                 }
555             }
556             if (isFirstElementOnNewLineEmptyChar) {
557                 isFirstElementOnNewLineEmptyChar = false;
558             } else {
559                 lineBuilder.append(nextElement);
560             }
561         }
562         return sb.append(lineBuilder).append('\n').toString();
563     }
564
565     private static String encodeJavadocSymbols(final String description) {
566         return Strings.isNullOrEmpty(description) ? description
567                 : AMP_MATCHER.replaceFrom(TAIL_COMMENT_PATTERN.matcher(description).replaceAll("&#42;&#47;"), "&amp;");
568     }
569 }