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