Bug 1411-4: MDSAL Binding2 Generator Impl
[mdsal.git] / binding2 / mdsal-binding2-generator-impl / src / main / java / org / opendaylight / mdsal / binding2 / generator / impl / util / YangTextTemplate.java
1 /*
2  * Copyright (c) 2016 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 package org.opendaylight.mdsal.binding2.generator.impl.util;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11
12 import com.google.common.annotations.Beta;
13 import com.google.common.base.CharMatcher;
14 import com.google.common.base.Preconditions;
15 import com.google.common.base.Splitter;
16 import com.google.common.base.Strings;
17 import com.google.common.collect.Iterables;
18 import java.util.Map;
19 import java.util.StringTokenizer;
20 import java.util.regex.Pattern;
21 import org.opendaylight.mdsal.binding2.generator.util.Types;
22 import org.opendaylight.mdsal.binding2.model.api.GeneratedType;
23 import org.opendaylight.mdsal.binding2.model.api.ParameterizedType;
24 import org.opendaylight.mdsal.binding2.model.api.Type;
25 import org.opendaylight.mdsal.binding2.model.api.WildcardType;
26 import org.opendaylight.yangtools.yang.common.QName;
27
28 /**
29  * Util class
30  */
31 @Beta
32 public final class YangTextTemplate {
33     private static final CharMatcher NEWLINE_OR_TAB = CharMatcher.anyOf("\n\t");
34     private static final String DOT = ".";
35     private static final String COMMA = ",";
36     private static final char NEW_LINE = '\n';
37     private static final CharMatcher NL_MATCHER = CharMatcher.is(NEW_LINE);
38     private static final CharMatcher AMP_MATCHER = CharMatcher.is('&');
39     private static final Splitter NL_SPLITTER = Splitter.on(NL_MATCHER);
40     private static final CharMatcher TAB_MATCHER = CharMatcher.is('\t');
41     private static final Pattern SPACES_PATTERN = Pattern.compile(" +");
42
43     private YangTextTemplate() {
44         throw new UnsupportedOperationException("Util class");
45     }
46
47     public static String formatSchemaPath(final String moduleName, final Iterable<QName> schemaPath) {
48         final StringBuilder sb = new StringBuilder();
49         sb.append(moduleName);
50
51         QName currentElement = Iterables.getFirst(schemaPath, null);
52         for (QName pathElement : schemaPath) {
53             sb.append('/');
54             if (!currentElement.getNamespace().equals(pathElement.getNamespace())) {
55                 currentElement = pathElement;
56                 sb.append(pathElement);
57             } else {
58                 sb.append(pathElement.getLocalName());
59             }
60         }
61         return sb.toString();
62     }
63
64     /**
65      * Used in #yangtemplateformodule.scala.txt for formating revision description
66      *
67      * @param text Content of tag description
68      * @param nextLineIndent Number of spaces from left side default is 12
69      * @return formatted description
70      */
71     public static String formatToParagraph(final String text, final int nextLineIndent) {
72         if (Strings.isNullOrEmpty(text)) {
73             return "";
74         }
75         boolean isFirstElementOnNewLineEmptyChar = false;
76         final StringBuilder sb = new StringBuilder();
77         final StringBuilder lineBuilder = new StringBuilder();
78         final String lineIndent = Strings.repeat(" ", nextLineIndent);
79         final String textToFormat = NEWLINE_OR_TAB.removeFrom(text);
80         final String formattedText = textToFormat.replaceAll(" +", " ");
81         final StringTokenizer tokenizer = new StringTokenizer(formattedText, " ", true);
82
83         while (tokenizer.hasMoreElements()) {
84             final String nextElement = tokenizer.nextElement().toString();
85
86             if (lineBuilder.length() + nextElement.length() > 80) {
87                 // Trim trailing whitespace
88                 for (int i = lineBuilder.length() - 1; i >= 0 && lineBuilder.charAt(i) != ' '; --i) {
89                     lineBuilder.setLength(i);
90                 }
91                 // Trim leading whitespace
92                 while (lineBuilder.charAt(0) == ' ') {
93                     lineBuilder.deleteCharAt(0);
94                 }
95                 sb.append(lineBuilder).append('\n');
96                 lineBuilder.setLength(0);
97
98                 if (nextLineIndent > 0) {
99                     sb.append(lineIndent);
100                 }
101
102                 if (" ".equals(nextElement)) {
103                     isFirstElementOnNewLineEmptyChar = true;
104                 }
105             }
106             if (isFirstElementOnNewLineEmptyChar) {
107                 isFirstElementOnNewLineEmptyChar = false;
108             } else {
109                 lineBuilder.append(nextElement);
110             }
111         }
112         return sb.append(lineBuilder).append('\n').toString();
113     }
114
115     /**
116      * Used in all yangtemplates for formating augmentation target
117      *
118      * @param schemaPath path to augmented node
119      * @return path in string format
120      */
121     public static String formatToAugmentPath(final Iterable<QName> schemaPath) {
122         final StringBuilder sb = new StringBuilder();
123         for (QName pathElement : schemaPath) {
124             sb.append("\\(")
125             .append(pathElement.getNamespace())
126             .append(')')
127             .append(pathElement.getLocalName());
128         }
129         return sb.toString();
130     }
131
132     /**
133      * Evaluates if it is necessary to add the package name for type to the map of imports for parentGenType
134      * If it is so the package name is saved to the map imports.
135      *
136      * @param parentGenType generated type for which is the map of necessary imports build
137      * @param type JAVA type for which is the necessary of the package import evaluated
138      * @param imports map of the imports for parentGenType
139      */
140     public static void putTypeIntoImports(final GeneratedType parentGenType, final Type type,
141         final Map<String, String> imports) {
142         checkArgument(parentGenType != null, "Parent Generated Type parameter MUST be specified and cannot be NULL!");
143         checkArgument(type != null, "Type parameter MUST be specified and cannot be NULL!");
144         checkArgument(parentGenType.getPackageName() != null,
145                 "Parent Generated Type cannot have Package Name referenced as NULL!");
146
147         final String typeName = Preconditions.checkNotNull(type.getName());
148         final String typePackageName = Preconditions.checkNotNull(type.getPackageName());
149         final String parentTypeName = Preconditions.checkNotNull(parentGenType.getName());
150         if (typeName.equals(parentTypeName) || typePackageName.startsWith("java.lang") || typePackageName.isEmpty()) {
151             return;
152         }
153         if (!imports.containsKey(typeName)) {
154             imports.put(typeName, typePackageName);
155         }
156         if (type instanceof ParameterizedType) {
157             final ParameterizedType paramType = (ParameterizedType) type;
158             final Type[] params = paramType.getActualTypeArguments();
159             if (params != null) {
160                 for (Type param : params) {
161                     putTypeIntoImports(parentGenType, param, imports);
162                 }
163             }
164         }
165     }
166
167     /**
168      * Builds the string which contains either the full path to the type (package name with type) or only type name
169      * if the package is among imports.
170      *
171      * @param parentGenType generated type which contains type
172      * @param type JAVA type for which is the string with type info generated
173      * @param imports map of necessary imports for parentGenType
174      * @return string with type name for type in the full format or in the short format
175      */
176     public static String getExplicitType(final GeneratedType parentGenType, final Type type,
177         final Map<String, String> imports) {
178         checkArgument(type != null, "Type parameter MUST be specified and cannot be NULL!");
179         checkArgument(imports != null, "Imports Map cannot be NULL!");
180
181         final String typePackageName = Preconditions.checkNotNull(type.getPackageName());
182         final String typeName = Preconditions.checkNotNull(type.getName());
183         final String importedPackageName = imports.get(typeName);
184         final StringBuilder builder;
185         if (typePackageName.equals(importedPackageName)) {
186             builder = new StringBuilder(typeName);
187             addActualTypeParameters(builder, type, parentGenType, imports);
188             if (builder.toString().equals("Void")) {
189                 return "void";
190             }
191         } else {
192             builder = new StringBuilder();
193             if (!typePackageName.isEmpty()) {
194                 builder.append(typePackageName + DOT + typeName);
195             } else {
196                 builder.append(type.getName());
197             }
198             if (type.equals(Types.voidType())) {
199                 return "void";
200             }
201             addActualTypeParameters(builder, type, parentGenType, imports);
202         }
203         return builder.toString();
204     }
205
206     /**
207      * Adds actual type parameters from type to builder if type is ParametrizedType.
208      *
209      * @param builder string builder which contains type name
210      * @param type JAVA Type for which is the string with type info generated
211      * @param parentGenType generated type which contains type
212      * @param imports map of necessary imports for parentGenType
213      * @return adds actual type parameters to builder
214      */
215     private static StringBuilder addActualTypeParameters(final StringBuilder builder, final Type type,
216         final GeneratedType parentGenType, final Map<String, String> imports) {
217         if (type instanceof ParameterizedType) {
218             final ParameterizedType pType = (ParameterizedType) type;
219             final Type[] pTypes = pType.getActualTypeArguments();
220             builder.append("<");
221             builder.append(getParameters(parentGenType, pTypes, imports));
222             builder.append(">");
223         }
224         return builder;
225     }
226
227     /**
228      * Generates the string with all actual type parameters from
229      *
230      * @param parentGenType generated type for which is the JAVA code generated
231      * @param pTypes array of Type instances = actual type parameters
232      * @param availableImports map of imports for parentGenType
233      * @return string with all actual type parameters from pTypes
234      */
235     private static String getParameters(final GeneratedType parentGenType, final Type[] pTypes,
236         final Map<String, String> availableImports) {
237         if (pTypes == null || pTypes.length == 0) {
238             return "?";
239         }
240         final StringBuilder builder = new StringBuilder();
241         for (int i = 0; i < pTypes.length; i++) {
242             final Type t = pTypes[i];
243
244             String separator = COMMA;
245             if (i == (pTypes.length - 1)) {
246                 separator = "";
247             }
248
249             String wildcardParam = "";
250             if (t.equals(Types.voidType())) {
251                 builder.append("java.lang.Void")
252                 .append(separator);
253                 continue;
254             } else {
255
256                 if (t instanceof WildcardType) {
257                     wildcardParam = "? extends ";
258                 }
259
260                 builder.append(wildcardParam)
261                 .append(getExplicitType(parentGenType, t, availableImports) + separator);
262             }
263         }
264         return builder.toString();
265     }
266
267     /**
268      * Wraps text as documentation
269      *
270      * @param text text for wrapping
271      * @return wrapped text
272      */
273     public static String wrapToDocumentation(String text) {
274         if (text.isEmpty()) {
275             return "";
276         }
277         final StringBuilder sb = new StringBuilder("/**");
278         sb.append(NEW_LINE);
279         Iterable<String> lineSplitText = NL_SPLITTER.split(text);
280         for (final String t : lineSplitText) {
281             sb.append(" *");
282             if (t.isEmpty()) {
283                 sb.append(" ");
284                 sb.append(t);
285             }
286             sb.append(NEW_LINE);
287         }
288         sb.append(" */");
289         return sb.toString();
290     }
291
292     public static String encodeJavadocSymbols(String description) {
293         if (description == null || description.isEmpty()) {
294             return description;
295         }
296         String ret = description.replace("*/", "&#42;&#47;");
297         return AMP_MATCHER.replaceFrom(ret, "&amp;");
298     }
299 }