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