217eff14d2b4a7a8773809eae6e6e38d089aad8e
[mdsal.git] / binding2 / mdsal-binding2-util / src / main / java / org / opendaylight / mdsal / binding / javav2 / util / BindingMapping.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.util;
10
11 import com.google.common.annotations.Beta;
12 import com.google.common.base.CharMatcher;
13 import com.google.common.base.Preconditions;
14 import com.google.common.base.Splitter;
15 import com.google.common.collect.ImmutableSet;
16 import com.google.common.collect.Interner;
17 import com.google.common.collect.Interners;
18 import java.text.SimpleDateFormat;
19 import java.util.Set;
20 import java.util.regex.Matcher;
21 import java.util.regex.Pattern;
22 import org.opendaylight.mdsal.binding.javav2.spec.runtime.BindingNamespaceType;
23 import org.opendaylight.yangtools.yang.model.api.Module;
24
25 /**
26  * Standard Util class that provides generated Java related functionality
27  */
28 @Beta
29 public final class BindingMapping {
30
31     public static final Set<String> JAVA_RESERVED_WORDS = ImmutableSet.of("abstract", "assert", "boolean", "break",
32             "byte", "case", "catch", "char", "class", "const", "continue", "default", "double", "do", "else", "enum",
33             "extends", "false", "final", "finally", "float", "for", "goto", "if", "implements", "import", "instanceof",
34             "int", "interface", "long", "native", "new", "null", "package", "private", "protected", "public", "return",
35             "short", "static", "strictfp", "super", "switch", "synchronized", "this", "throw", "throws", "transient",
36             "true", "try", "void", "volatile", "while");
37
38     public static final String QNAME_STATIC_FIELD_NAME = "QNAME";
39
40     /**
41      * Package prefix for Binding v2 generated Java code structures
42      */
43     public static final String PACKAGE_PREFIX = "org.opendaylight.mdsal.gen.javav2";
44
45     private static final Splitter DOT_SPLITTER = Splitter.on('.');
46     private static final Interner<String> PACKAGE_INTERNER = Interners.newWeakInterner();
47     private static final Splitter CAMEL_SPLITTER = Splitter.on(CharMatcher.anyOf(" _.-/").precomputed())
48             .omitEmptyStrings().trimResults();
49     private static final Pattern COLON_SLASH_SLASH = Pattern.compile("://", Pattern.LITERAL);
50     private static final String QUOTED_DOT = Matcher.quoteReplacement(".");
51     public static final String MODULE_INFO_CLASS_NAME = "$YangModuleInfoImpl";
52     public static final String MODEL_BINDING_PROVIDER_CLASS_NAME = "$YangModelBindingProvider";
53     public static final String PATTERN_CONSTANT_NAME = "PATTERN_CONSTANTS";
54     public static final String MEMBER_PATTERN_LIST = "patterns";
55
56     private static final ThreadLocal<SimpleDateFormat> PACKAGE_DATE_FORMAT = new ThreadLocal<SimpleDateFormat>() {
57
58         @Override
59         protected SimpleDateFormat initialValue() {
60             return new SimpleDateFormat("yyMMdd");
61         }
62
63         @Override
64         public void set(final SimpleDateFormat value) {
65             throw new UnsupportedOperationException();
66         }
67     };
68
69     private BindingMapping() {
70         throw new UnsupportedOperationException("Utility class");
71     }
72
73     public static String getRootPackageName(final Module module) {
74         Preconditions.checkArgument(module != null, "Module must not be null");
75         Preconditions.checkArgument(module.getRevision() != null, "Revision must not be null");
76         Preconditions.checkArgument(module.getNamespace() != null, "Namespace must not be null");
77
78         final StringBuilder packageNameBuilder = new StringBuilder();
79         packageNameBuilder.append(PACKAGE_PREFIX);
80         packageNameBuilder.append('.');
81
82         String namespace = module.getNamespace().toString();
83         namespace = COLON_SLASH_SLASH.matcher(namespace).replaceAll(QUOTED_DOT);
84
85         final char[] chars = namespace.toCharArray();
86         for (int i = 0; i < chars.length; ++i) {
87             switch (chars[i]) {
88                 case '/':
89                 case ':':
90                 case '-':
91                 case '@':
92                 case '$':
93                 case '#':
94                 case '\'':
95                 case '*':
96                 case '+':
97                 case ',':
98                 case ';':
99                 case '=':
100                     chars[i] = '.';
101                     break;
102                 default:
103                     // no-op, any other character is kept as it is
104             }
105         }
106
107         packageNameBuilder.append(chars);
108         if (chars[chars.length - 1] != '.') {
109             packageNameBuilder.append('.');
110         }
111
112         //TODO: per yangtools dev, semantic version not used yet
113 //        final SemVer semVer = module.getSemanticVersion();
114 //        if (semVer != null) {
115 //            packageNameBuilder.append(semVer.toString());
116 //        } else {
117 //            packageNameBuilder.append("rev");
118 //            packageNameBuilder.append(PACKAGE_DATE_FORMAT.get().format(module.getRevision()));
119 //        }
120
121         packageNameBuilder.append("rev");
122         packageNameBuilder.append(PACKAGE_DATE_FORMAT.get().format(module.getRevision()));
123
124         //seems to be duplicate call, because normalizing is run again on full packagename + localName
125         //return normalizePackageName(packageNameBuilder.toString(), null);
126         return packageNameBuilder.toString();
127     }
128
129     /**
130      * This method normalizes input package name to become valid package identifier
131      * and appends Binding v2 specific namespace type
132      *
133      * @param packageName package name
134      * @return normalized package name
135      */
136     public static String normalizePackageName(final String packageName) {
137         if (packageName == null) {
138             return null;
139         }
140
141         final StringBuilder builder = new StringBuilder();
142         boolean first = true;
143
144         for (String p : DOT_SPLITTER.split(packageName.toLowerCase())) {
145             if (first) {
146                 first = false;
147             } else {
148                 builder.append('.');
149             }
150
151             //TODO: incorporate use of PackageNameNormalizer (impl in progress)
152             //TODO: to not to worry about various characters in identifiers,
153             //TODO: relying on https://docs.oracle.com/javase/tutorial/java/package/namingpkgs.html
154
155             //FIXME: delete this custom check when naming convention patch above is merged
156             if (Character.isDigit(p.charAt(0)) || BindingMapping.JAVA_RESERVED_WORDS.contains(p)) {
157                 builder.append('_');
158             }
159
160             builder.append(p);
161         }
162
163         // Prevent duplication of input string
164         return PACKAGE_INTERNER.intern(builder.toString());
165     }
166
167     /**
168      * Prepares valid Java class name
169      * @param localName
170      * @return class name
171      */
172     public static String getClassName(final String localName) {
173         Preconditions.checkArgument(localName != null, "Name should not be null.");
174         return toFirstUpper(toCamelCase(localName));
175     }
176
177     private static String toCamelCase(final String rawString) {
178         Preconditions.checkArgument(rawString != null, "String should not be null");
179         Iterable<String> components = CAMEL_SPLITTER.split(rawString);
180         StringBuilder builder = new StringBuilder();
181         for (String comp : components) {
182             builder.append(toFirstUpper(comp));
183         }
184         return checkNumericPrefix(builder.toString());
185     }
186
187     private static String checkNumericPrefix(final String rawString) {
188         if (rawString == null || rawString.isEmpty()) {
189             return rawString;
190         }
191         char firstChar = rawString.charAt(0);
192         if (firstChar >= '0' && firstChar <= '9') {
193             return "_" + rawString;
194         } else {
195             return rawString;
196         }
197     }
198
199     /**
200      * Returns the {@link String} {@code s} with an
201      * {@link Character#isUpperCase(char) upper case} first character. This
202      * function is null-safe.
203      *
204      * @param s
205      *            the string that should get an upper case first character. May
206      *            be <code>null</code>.
207      * @return the {@link String} {@code s} with an upper case first character
208      *         or <code>null</code> if the input {@link String} {@code s} was
209      *         <code>null</code>.
210      */
211     public static String toFirstUpper(final String s) {
212         if (s == null || s.length() == 0) {
213             return s;
214         }
215         if (Character.isUpperCase(s.charAt(0))) {
216             return s;
217         }
218         if (s.length() == 1) {
219             return s.toUpperCase();
220         }
221         return s.substring(0, 1).toUpperCase() + s.substring(1);
222     }
223
224     /**
225      * Prepares Java property name for method getter code generation
226      * @param yangIdentifier given YANG element local name
227      * @return property name
228      */
229     public static String getPropertyName(final String yangIdentifier) {
230         final String potential = toFirstLower(toCamelCase(yangIdentifier));
231         if ("class".equals(potential)) {
232             return "xmlClass";
233         }
234         return potential;
235     }
236
237     /**
238      * Returns the {@link String} {@code s} with an
239      * {@link Character#isLowerCase(char) lower case} first character. This
240      * function is null-safe.
241      *
242      * @param s
243      *            the string that should get an lower case first character. May
244      *            be <code>null</code>.
245      * @return the {@link String} {@code s} with an lower case first character
246      *         or <code>null</code> if the input {@link String} {@code s} was
247      *         <code>null</code>.
248      */
249     private static String toFirstLower(final String s) {
250         if (s == null || s.length() == 0) {
251             return s;
252         }
253         if (Character.isLowerCase(s.charAt(0))) {
254             return s;
255         }
256         if (s.length() == 1) {
257             return s.toLowerCase();
258         }
259         return s.substring(0, 1).toLowerCase() + s.substring(1);
260     }
261
262     //TODO: further implementation of static util methods...
263
264 }