654767e27bbd2546f2049438085b69adc30ab5af
[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.List;
18 import java.util.StringTokenizer;
19 import java.util.regex.Pattern;
20 import org.opendaylight.mdsal.binding.javav2.generator.util.Types;
21 import org.opendaylight.mdsal.binding.javav2.model.api.AccessModifier;
22 import org.opendaylight.mdsal.binding.javav2.model.api.ConcreteType;
23 import org.opendaylight.mdsal.binding.javav2.model.api.GeneratedProperty;
24 import org.opendaylight.mdsal.binding.javav2.model.api.GeneratedTransferObject;
25 import org.opendaylight.mdsal.binding.javav2.model.api.GeneratedType;
26 import org.opendaylight.mdsal.binding.javav2.model.api.MethodSignature;
27 import org.opendaylight.mdsal.binding.javav2.model.api.Restrictions;
28 import org.opendaylight.mdsal.binding.javav2.model.api.Type;
29 import org.opendaylight.mdsal.binding.javav2.model.api.TypeMember;
30 import org.opendaylight.yangtools.concepts.Builder;
31 import org.opendaylight.yangtools.yang.model.api.Module;
32
33 public final class TextTemplateUtil {
34
35     public static final String DOT = ".";
36     public static final String PATTERN_CONSTANT_NAME = "PATTERN_CONSTANTS";
37
38     private static final char NEW_LINE = '\n';
39     private static final String UNDERSCORE = "_";
40     private static final CharMatcher NEWLINE_OR_TAB = CharMatcher.anyOf("\n\t");
41     private static final CharMatcher NL_MATCHER = CharMatcher.is(NEW_LINE);
42     private static final CharMatcher AMP_MATCHER = CharMatcher.is('&');
43     private static final CharMatcher GT_MATCHER = CharMatcher.is('>');
44     private static final CharMatcher LT_MATCHER = CharMatcher.is('<');
45     private static final Splitter NL_SPLITTER = Splitter.on(NL_MATCHER);
46
47     private static final Pattern TAIL_COMMENT_PATTERN = Pattern.compile("*/", Pattern.LITERAL);
48     private static final Pattern MULTIPLE_SPACES_PATTERN = Pattern.compile(" +");
49
50     private TextTemplateUtil() {
51         throw new UnsupportedOperationException("Util class");
52     }
53
54     /**
55      * Makes start of getter name LowerCase
56      *
57      * @param s getter name without prefix
58      * @return getter name starting in LowerCase
59      */
60     public static String toFirstLower(final String s) {
61         return s != null && s.length() != 0 ? Character.isLowerCase(s.charAt(0)) ? s : s.length() == 1 ?
62                 s.toLowerCase() : s.substring(0, 1).toLowerCase() + s.substring(1) : s;
63     }
64
65     /**
66      * Wraps text as documentation, used in enum description
67      *
68      * @param text text for wrapping
69      * @return wrapped text
70      */
71     public static String wrapToDocumentation(final String text) {
72         if (text.isEmpty()) {
73             return "";
74         }
75         final StringBuilder sb = new StringBuilder(NEW_LINE);
76         sb.append("/**");
77         sb.append(NEW_LINE);
78         for (final String t : NL_SPLITTER.split(text)) {
79             if (!t.isEmpty()) {
80                 sb.append(" * ");
81                 sb.append(t);
82                 sb.append(NEW_LINE);
83             }
84         }
85         sb.append(" */");
86         sb.append(NEW_LINE);
87         return sb.toString();
88     }
89
90     /**
91      * Returns formatted Javadoc, based on type
92      * @param typeName
93      * @return formatted Javadoc, based on type
94      */
95     public static String formatDataForJavaDocBuilder(final String typeName) {
96         final StringBuilder stringBuilder = new StringBuilder();
97         stringBuilder.append("Class that builds {@link ")
98                 .append(typeName)
99                 .append("} instances.")
100                 .append(NEW_LINE)
101                 .append("@see ")
102                 .append(typeName);
103         return stringBuilder.toString();
104     }
105
106     /**
107      * Returns formatted Javadoc with possible additional comment, based on type
108      * @param type
109      * @param additionalComment
110      * @return formatted Javadoc with possible additional comment, based on type
111      */
112     public static String formatDataForJavaDoc(final GeneratedType type, final String additionalComment) {
113         final StringBuilder javaDoc = new StringBuilder();
114         if (type.getDescription().isPresent()) {
115             javaDoc.append(type.getDescription())
116                     .append(NEW_LINE)
117                     .append(NEW_LINE)
118                     .append(NEW_LINE);
119         }
120         javaDoc.append(additionalComment);
121         return javaDoc.toString();
122     }
123
124     /**
125      * Returns properties names in formatted string
126      * @param properties
127      * @return properties names in formatted string
128      */
129     //FIXME: this needs further clarification in future patch
130     public static String valueForBits(final List<GeneratedProperty> properties) {
131         return String.join(",", Lists.transform(properties, TextTemplateUtil::fieldName));
132     }
133
134     /**
135      * Returns formatted type description
136      * @param type
137      * @return formatted type description
138      */
139     public static String formatDataForJavaDoc(final GeneratedType type) {
140         return type.getDescription().map(TextTemplateUtil::encodeJavadocSymbols).orElse("");
141     }
142
143     /**
144      * Returns parameter name, based on given Type
145      * @param returnType
146      * @param paramName
147      * @return parameter name, based on given Type
148      */
149     public static String paramValue(final Type returnType, final String paramName) {
150         return returnType instanceof ConcreteType ? paramName : paramName + ".getValue()";
151     }
152
153     /**
154      * Template method which generates JAVA comments. InterfaceTemplate
155      *
156      * @param comment comment string with the comment for whole JAVA class
157      * @return string with comment in JAVA format
158      */
159     public static String asJavadoc(final String comment) {
160         return comment == null ? "" : wrapToDocumentation(formatToParagraph(comment.trim(), 0));
161     }
162
163     private static String formatDataForJavaDoc(final TypeMember type, final String additionalComment) {
164         final StringBuilder javaDoc = new StringBuilder();
165         if (type.getComment() != null && !type.getComment().isEmpty()) {
166             javaDoc.append(formatToParagraph(type.getComment(), 0))
167                     .append(NEW_LINE)
168                     .append(NEW_LINE)
169                     .append(NEW_LINE);
170         }
171         javaDoc.append(additionalComment);
172         return wrapToDocumentation(javaDoc.toString());
173     }
174
175     /**
176      * Returns related Javadoc
177      * @param methodSignature
178      * @return related Javadoc
179      */
180     public static String getJavaDocForInterface(final MethodSignature methodSignature) {
181         if (methodSignature.getReturnType() == Types.VOID) {
182             return "";
183         }
184         final StringBuilder javaDoc = new StringBuilder();
185         javaDoc.append("@return ")
186                 .append(asCode(methodSignature.getReturnType().getFullyQualifiedName()))
187                 .append(" ")
188                 .append(asCode(propertyNameFromGetter(methodSignature)))
189                 .append(", or ")
190                 .append(asCode("null"))
191                 .append(" if not present");
192         return formatDataForJavaDoc(methodSignature, javaDoc.toString());
193     }
194
195     private static String asCode(final String text) {
196         return "<code>" + text + "</code>";
197     }
198
199     /**
200      * Encodes angle brackets in yang statement description
201      * @param description description of a yang statement which is used to generate javadoc comments
202      * @return string with encoded angle brackets
203      */
204     public static String encodeAngleBrackets(String description) {
205         if (description != null) {
206             description = LT_MATCHER.replaceFrom(description, "&lt;");
207             description = GT_MATCHER.replaceFrom(description, "&gt;");
208         }
209         return description;
210     }
211
212     /**
213      * Returns collection of properties as formatted String
214      * @param properties
215      * @return generated properties as formatted String
216      */
217     public static String propsAsArgs(final Iterable<GeneratedProperty> properties) {
218         return String.join(",", Iterables.transform(properties, prop -> "\"" + prop.getName() + "\""));
219     }
220
221     /**
222      * Returns properties as formatted String
223      * @param properties
224      * @param booleanName
225      * @return Properties as formatted String
226      */
227     public static String propsAsList(final Iterable<GeneratedProperty> properties, final String booleanName) {
228         return String.join(",", Iterables.transform(properties,
229             prop -> "properties.get(i++).equals(defaultValue) ? " + booleanName + ".TRUE : null"));
230     }
231
232     /**
233      * Extracts available restrictions from given type
234      * @param currentType
235      * @return restrictions from given type
236      */
237     public static Restrictions getRestrictions(final Type currentType) {
238         Restrictions restrictions = null;
239         if (currentType instanceof ConcreteType) {
240             restrictions = ((ConcreteType) currentType).getRestrictions();
241         } else if (currentType instanceof GeneratedTransferObject) {
242             restrictions = ((GeneratedTransferObject) currentType).getRestrictions();
243         }
244         return restrictions;
245     }
246
247     /**
248      * sets fieldname according to property for return type
249      * method(type fieldname)
250      *
251      * @param property type from getter
252      */
253     public static String fieldName(final GeneratedProperty property) {
254         final String name = Preconditions.checkNotNull(property.getName());
255         return UNDERSCORE.concat(name);
256     }
257
258     /**
259      * Template method which generates sequence of the names of the class attributes
260      *
261      * @param parameters group of generated property instances which are transformed to the sequence of parameter names
262      * @return string with the list of the parameter names
263      */
264     public static String asArguments(final List<GeneratedProperty> parameters) {
265         return String.join(", ", Lists.transform(parameters, TextTemplateUtil::fieldName));
266     }
267
268     /**
269      * Helper method for building getter
270      *
271      * @param field property name
272      * @return getter for propery
273      */
274     public static String getterMethodName(final GeneratedProperty field) {
275         final Type type = Preconditions.checkNotNull(field.getReturnType());
276         final String name = Preconditions.checkNotNull(field.getName());
277         final String prefix = Types.BOOLEAN.equals(type) ? "is" : "get";
278         return prefix.concat(toFirstUpper(name));
279     }
280
281     /**
282      * Returns built setter method body
283      * @param field
284      * @param typeName
285      * @param returnTypeName
286      * @return built setter method body
287      */
288     public static String setterMethod(final GeneratedProperty field, final String typeName, final String returnTypeName) {
289         final StringBuilder stringBuilder = new StringBuilder();
290         stringBuilder.append("public ")
291                 .append(typeName)
292                 .append(" set")
293                 .append(toFirstUpper(field.getName()))
294                 .append('(')
295                 .append(returnTypeName)
296                 .append(" value) {\n    this.")
297                 .append(fieldName(field))
298                 .append(" = value;\n    return this;\n}\n");
299         return stringBuilder.toString();
300     }
301
302     /**
303      * Returns simple name of underlying class
304      * @return Simple name of underlying class
305      */
306     public static String getSimpleNameForBuilder() {
307         return Builder.class.getSimpleName();
308     }
309
310     /**
311      * Makes start of getter name uppercase
312      *
313      * @param s getter name without prefix
314      * @return getter name starting in uppercase
315      */
316     public static String toFirstUpper(final String s) {
317         return s != null && s.length() != 0 ? Character.isUpperCase(s.charAt(0)) ? s : s.length() == 1 ?
318                 s.toUpperCase() : s.substring(0, 1).toUpperCase() + s.substring(1) : s;
319     }
320
321     /**
322      * Cuts prefix from getter name
323      *
324      * @param getter getter name
325      * @return getter name without prefix
326      */
327     public static String propertyNameFromGetter(final MethodSignature getter) {
328         final String name = Preconditions.checkNotNull(getter.getName());
329         int prefix;
330         if (name.startsWith("is")) {
331             prefix = 2;
332         } else if (name.startsWith("get")) {
333             prefix = 3;
334         } else {
335             prefix = 0;
336         }
337         return toFirstLower(name.substring(prefix));
338     }
339
340     /**
341      * Returns list of properties as formatted String
342      * @param properties
343      * @return formatted property list as String
344      */
345     public static String getPropertyList(final List<GeneratedProperty> properties) {
346         return String.join(", ", Lists.transform(properties, prop -> "base." + getterMethodName(prop) + "()"));
347     }
348
349     /**
350      * util method for unionTemplateBuilderTemplate
351      * @return string with clarification for javadoc
352      */
353     public static String getClarification() {
354         final StringBuilder clarification = new StringBuilder();
355         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")
356                 .append("In some cases it is very difficult to automate it since there can be unions such as (uint32 - uint16), or (string - uint32).\n")
357                 .append("\n")
358                 .append("The reason behind putting it under src/main/java is:\n")
359                 .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")
360                 .append("loss of user code.\n")
361                 .append("\n");
362         return clarification.toString();
363     }
364
365     /**
366      * Returns source path as String
367      * @param module
368      * @return formatted String source path
369      */
370     public static String getSourcePath(final Module module) {
371         return "/".concat(module.getModuleSourcePath().replace(java.io.File.separatorChar, '/'));
372     }
373
374     /**
375      * util method for unionTemplateBuilderTemplate
376      * @return needed access modifier
377      */
378     public static String getAccessModifier(final AccessModifier modifier) {
379         switch (modifier) {
380             case PUBLIC: return "public ";
381             case PROTECTED: return "protected ";
382             case PRIVATE: return "private ";
383             default: return "";
384         }
385     }
386
387     /**
388      * @param text Content of tag description
389      * @param nextLineIndent Number of spaces from left side default is 12
390      * @return formatted description
391      */
392     private static String formatToParagraph(final String text, final int nextLineIndent) {
393         if (Strings.isNullOrEmpty(text)) {
394             return "";
395         }
396         boolean isFirstElementOnNewLineEmptyChar = false;
397         final StringBuilder sb = new StringBuilder();
398         final StringBuilder lineBuilder = new StringBuilder();
399         final String lineIndent = Strings.repeat(" ", nextLineIndent);
400         final String textToFormat = NEWLINE_OR_TAB.removeFrom(encodeJavadocSymbols(text));
401         final String formattedText = MULTIPLE_SPACES_PATTERN.matcher(textToFormat).replaceAll(" ");
402         final StringTokenizer tokenizer = new StringTokenizer(formattedText, " ", true);
403
404         while (tokenizer.hasMoreElements()) {
405             final String nextElement = tokenizer.nextElement().toString();
406
407             if (lineBuilder.length() + nextElement.length() > 80) {
408                 // Trim trailing whitespace
409                 for (int i = lineBuilder.length() - 1; i >= 0 && lineBuilder.charAt(i) != ' '; --i) {
410                     lineBuilder.setLength(i);
411                 }
412                 // Trim leading whitespace
413                 while (lineBuilder.charAt(0) == ' ') {
414                     lineBuilder.deleteCharAt(0);
415                 }
416                 sb.append(lineBuilder).append('\n');
417                 lineBuilder.setLength(0);
418
419                 if (nextLineIndent > 0) {
420                     sb.append(lineIndent);
421                 }
422
423                 if (" ".equals(nextElement)) {
424                     isFirstElementOnNewLineEmptyChar = true;
425                 }
426             }
427             if (isFirstElementOnNewLineEmptyChar) {
428                 isFirstElementOnNewLineEmptyChar = false;
429             } else {
430                 lineBuilder.append(nextElement);
431             }
432         }
433         return sb.append(lineBuilder).append('\n').toString();
434     }
435
436     private static String encodeJavadocSymbols(final String description) {
437         return Strings.isNullOrEmpty(description) ? description
438                 : AMP_MATCHER.replaceFrom(TAIL_COMMENT_PATTERN.matcher(description).replaceAll("&#42;&#47;"), "&amp;");
439     }
440 }