Bug 2332 - Binding Java API Generator -> doesn't handle
[mdsal.git] / binding2 / mdsal-binding2-generator-util / src / main / java / org / opendaylight / mdsal / binding / javav2 / generator / util / NonJavaCharsConverter.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 package org.opendaylight.mdsal.binding.javav2.generator.util;
9
10 import com.google.common.annotations.Beta;
11 import java.util.List;
12 import org.opendaylight.mdsal.binding.javav2.model.api.Enumeration;
13 import org.opendaylight.mdsal.binding.javav2.model.api.Enumeration.Pair;
14
15 /**
16  * This util class converts every non-java char in identifier to java char by its unicode name
17  * (<a href="http://docs.oracle.com/javase/specs/jls/se8/html/jls-3.html#jls-3.8">JAVA SE
18  * SPEFICIATIONS - Identifiers</a>). There are special types of mapping non-java chars to original
19  * identifiers according to specific {@linkplain JavaIdentifier java type}:
20  * <ul>
21  * <li>class, enum, interface</li>
22  * <li>
23  * <ul>
24  * <li>without special separator</li>
25  * <li>the first character of identifier, any other first character of identifier part mapped by
26  * non-Java char name from unicode and char in identifier behind non-java char name are converting
27  * to upper case</li>
28  * <li>examples:</li>
29  * <li>
30  * <ul>
31  * <li>example* - ExampleAsterisk</li>
32  * <li>example*example - ExampleAserisksExample</li>
33  * <li>\example - ReverseSolidusExample</li>
34  * <li>1example - DigitOneExample</li>
35  * <li>example1 - Example1</li>
36  * </ul>
37  * </li>
38  * </ul>
39  * </li>
40  * <li>enum value, constant</li>
41  * <li>
42  * <ul>
43  * <li>used underscore as special separator</li>
44  * <li>converted identifier to upper case</li>
45  * <li>examples:</li>
46  * <li>
47  * <ul>
48  * <li>example* - EXAMPLE_ASTERISK</li>
49  * <li>example*example - EXAMPLE_ASTERISK_EXAMPLE</li>
50  * <li>\example - REVERSE_SOLIDUS_EXAMPLE</li>
51  * <li>1example - DIGIT_ONE_EXAMPLE</li>
52  * <li>example1 - EXAMPLE1</li>
53  * </ul>
54  * </li>
55  * </ul>
56  * </li>
57  * <li>method, variable
58  * <li>
59  * <li>
60  * <ul>
61  * <li>without special separator</li>
62  * <li>the first character of identifier is converting to lower case</li>
63  * <li>any other first character of identifier part mapped by non-Java char name from unicode and
64  * char in identifier behind non-java char name are converting to upper case
65  * <li>examples:</li>
66  * <li>
67  * <ul>
68  * <li>example* - exampleAsterisk</li>
69  * <li>example*example - exampleAserisksExample</li>
70  * <li>\example - reverseSolidusExample</li>
71  * <li>1example - digitOneExample</li>
72  * <li>example1 - example1</li>
73  * </ul>
74  * </li>
75  * </ul>
76  * </li>
77  * </ul>
78  *
79  */
80 @Beta
81 public final class NonJavaCharsConverter {
82
83     private final static int FIRST_CHAR = 0;
84     private final static int FIRST_INDEX = 1;
85
86     private NonJavaCharsConverter() {
87         throw new UnsupportedOperationException("Util class");
88     }
89
90     /**
91      * <p>
92      * According to <a href="https://tools.ietf.org/html/rfc7950#section-9.6.4">YANG RFC 7950</a>,
93      * all assigned names in an enumeration MUST be unique. Created names are contained in the list
94      * of {@link Enumeration.Pair}. This method adds actual index with underscore behind name of new
95      * enum value only if this name already exists in one of the list of {@link Enumeration.Pair}.
96      * Then, the name will be converted to java chars according to {@link JavaIdentifier#ENUM_VALUE}
97      * and returned.
98      * </p>
99      * Example:
100      *
101      * <pre>
102      * type enumeration {
103      *     enum foo;
104      *     enum Foo;
105      * }
106      * </pre>
107      *
108      * YANG enum values will be mapped to 'FOO' and 'FOO_1' Java enum values.
109      *
110      * @param name
111      *            - name of new enum value
112      * @param values
113      *            - list of all actual enum values
114      * @return converted and fixed name of new enum value
115      */
116     public static String convertIdentifierEnumValue(final String name, final List<Pair> values) {
117         return convertIdentifierEnumValue(name, name, values, FIRST_INDEX);
118     }
119
120     private static String convertIdentifierEnumValue(final String name, final String origName, final List<Pair> values,
121             final int rank) {
122         String newName = name;
123         for (final Pair pair : values) {
124             if (pair.getName().toLowerCase().equals(name.toLowerCase())
125                     || pair.getMappedName().toLowerCase().equals(name.toLowerCase())) {
126                 int actualRank = rank;
127                 final StringBuilder actualNameBuilder = new StringBuilder(origName).append('_').append(actualRank);
128                 newName = convertIdentifierEnumValue(actualNameBuilder.toString(), origName, values,
129                         ++actualRank);
130             }
131         }
132         return convertIdentifier(newName, JavaIdentifier.ENUM_VALUE);
133     }
134
135     /**
136      * Find and convert non Java chars in identifiers of generated transfer objects, initially
137      * derived from corresponding YANG.
138      *
139      * <a>http://docs.oracle.com/javase/specs/jls/se8/html/jls-3.html#jls-3.8</a>
140      *
141      * @param identifier
142      *            - name of identifier
143      * @param javaIdentifier
144      *            - java type of identifier
145      * @return - java acceptable identifier
146      */
147     public static String convertIdentifier(final String identifier, final JavaIdentifier javaIdentifier) {
148         final StringBuilder sb = new StringBuilder();
149
150         // check and convert first char in identifier if there is non-java char
151         final char firstChar = identifier.charAt(FIRST_CHAR);
152         if (!Character.isJavaIdentifierStart(firstChar)) {
153             // converting first char of identifier
154             sb.append(convertFirst(firstChar, existNext(identifier, FIRST_CHAR)));
155         } else {
156             sb.append(firstChar);
157         }
158         // check and convert other chars in identifier, if there is non-java char
159         for (int i = 1; i < identifier.length(); i++) {
160             final char actualChar = identifier.charAt(i);
161             if (!Character.isJavaIdentifierPart(actualChar)) {
162                 // prepare actual string of sb for checking if underscore exist on position of the
163                 // last char
164                 final String partialConvertedIdentifier = sb.toString();
165                 sb.append(convert(actualChar, existNext(identifier, i),
166                         partialConvertedIdentifier.charAt(partialConvertedIdentifier.length() - 1)));
167             } else {
168                 sb.append(actualChar);
169             }
170         }
171         // apply camel case in appropriate way
172         return fixCasesByJavaType(sb.toString().replace("__", "_").toLowerCase(), javaIdentifier);
173     }
174
175     /**
176      * Fix cases of converted identifiers by Java type
177      *
178      * @param string
179      *            - converted identifier
180      * @param javaIdentifier
181      *            - java type of identifier
182      * @return converted identifier with right cases according to java type
183      */
184     private static String fixCasesByJavaType(final String convertedIdentifier, final JavaIdentifier javaIdentifier) {
185         switch (javaIdentifier) {
186             case CLASS:
187             case ENUM:
188             case INTERFACE:
189                 return capitalize(fixCases(convertedIdentifier));
190             case ENUM_VALUE:
191             case CONSTANT:
192                 return convertedIdentifier.toUpperCase();
193             case METHOD:
194             case VARIABLE:
195                 return fixCases(convertedIdentifier);
196             default:
197                 throw new IllegalArgumentException("Unknown java type of identifier : " + javaIdentifier.toString());
198         }
199     }
200
201     /**
202      * Delete unnecessary chars in converted identifier and apply camel case in appropriate way.
203      *
204      * @param convertedIdentifier
205      *            - original converted identifier
206      * @return resolved identifier
207      */
208     private static String fixCases(final String convertedIdentifier) {
209         final StringBuilder sb = new StringBuilder();
210         if (convertedIdentifier.contains("_")) {
211             boolean isFirst = true;
212             for (final String part : convertedIdentifier.split("_")) {
213                 if (isFirst) {
214                     isFirst = false;
215                     sb.append(part);
216                 } else {
217                     sb.append(capitalize(part));
218                 }
219             }
220         } else {
221             sb.append(convertedIdentifier);
222         }
223         return sb.toString();
224     }
225
226     /**
227      * Check if there exist next char in identifier behind actual char position
228      *
229      * @param identifier
230      *            - original identifier
231      * @param actual
232      *            - actual char position
233      * @return true if there is another char, false otherwise
234      */
235     private static boolean existNext(final String identifier, final int actual) {
236       return (identifier.length() - 1) < (actual + 1) ? false : true;
237     }
238
239     /**
240      * Converting first char of identifier. This happen only if this char is
241      * non-java char
242      *
243      * @param c
244      *            - first char
245      * @param existNext
246      *            - existing of next char behind actual char
247      * @return converted char
248      */
249     private static String convertFirst(final char c, final boolean existNext) {
250         String name = Character.getName(c);
251         name = existNext ? (name + "_") : name;
252         return name.contains(" ") ? name.replaceAll(" ", "_") : name;
253     }
254
255     /**
256      * Converting any char in java identifier, This happen only if this char is
257      * non-java char
258      *
259      * @param c
260      *            - actual char
261      * @param existNext
262      *            - existing of next char behind actual char
263      * @param partialLastChar
264      *            - last char of partial converted identifier
265      * @return converted char
266      */
267     private static String convert(final char c, final boolean existNext, final char partialLastChar) {
268         return partialLastChar == '_' ? convertFirst(c, existNext) : "_" + convertFirst(c, existNext);
269     }
270
271     /**
272      * Capitalize input string
273      *
274      * @param identifier
275      *            - string to be capitalized
276      */
277     private static String capitalize(final String identifier) {
278         return identifier.substring(FIRST_CHAR, FIRST_CHAR + 1).toUpperCase() + identifier.substring(1);
279     }
280 }