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