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