View Javadoc
1   /*
2    * Portions of this software was developed by employees of the National Institute
3    * of Standards and Technology (NIST), an agency of the Federal Government and is
4    * being made available as a public service. Pursuant to title 17 United States
5    * Code Section 105, works of NIST employees are not subject to copyright
6    * protection in the United States. This software may be subject to foreign
7    * copyright. Permission in the United States and in foreign countries, to the
8    * extent that NIST may hold copyright, to use, copy, modify, create derivative
9    * works, and distribute this software and its documentation without fee is hereby
10   * granted on a non-exclusive basis, provided that this notice and disclaimer
11   * of warranty appears in all copies.
12   *
13   * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
14   * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
15   * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
16   * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
17   * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
18   * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE.  IN NO EVENT
19   * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
20   * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
21   * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
22   * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
23   * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
24   * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
25   */
26  /*
27   * Copyright (c) 1997, 2022 Oracle and/or its affiliates. All rights reserved.
28   *
29   * This program and the accompanying materials are made available under the
30   * terms of the Eclipse Distribution License v. 1.0, which is available at
31   * http://www.eclipse.org/org/documents/edl-v10.php.
32   *
33   * SPDX-License-Identifier: BSD-3-Clause
34   */
35  
36  package gov.nist.secauto.metaschema.databind.codegen.impl;
37  
38  import java.util.ArrayList;
39  import java.util.List;
40  import java.util.Locale;
41  import java.util.StringTokenizer;
42  
43  import javax.lang.model.SourceVersion;
44  
45  /**
46   * Converts aribitrary strings into Java identifiers.
47   *
48   * @author <a href="mailto:kohsuke.kawaguchi@sun.com">Kohsuke KAWAGUCHI</a>
49   */
50  public interface NameConverter {
51    /**
52     * The name converter implemented by Code Model.
53     *
54     * This is the standard name conversion for JAXB.
55     */
56    NameConverter STANDARD = new Standard();
57  
58    /**
59     * converts a string into an identifier suitable for classes.
60     *
61     * In general, this operation should generate "NamesLikeThis".
62     *
63     * @param token
64     *          the string to convert
65     * @return the equivalent class name
66     */
67    String toClassName(String token);
68  
69    /**
70     * converts a string into an identifier suitable for interfaces.
71     *
72     * In general, this operation should generate "NamesLikeThis". But for example,
73     * it can prepend every interface with 'I'.
74     *
75     * @param token
76     *          the string to convert
77     * @return the equivalent interface name
78     */
79    String toInterfaceName(String token);
80  
81    /**
82     * converts a string into an identifier suitable for properties.
83     *
84     * In general, this operation should generate "NamesLikeThis", which will be
85     * used with known prefixes like "get" or "set".
86     *
87     * @param token
88     *          the string to convert
89     * @return the equivalent property name
90     */
91    String toPropertyName(String token);
92  
93    /**
94     * converts a string into an identifier suitable for constants.
95     *
96     * In the standard Java naming convention, this operation should generate
97     * "NAMES_LIKE_THIS".
98     *
99     * @param token
100    *          the string to convert
101    * @return the equivalent constant name
102    */
103   String toConstantName(String token);
104 
105   /**
106    * Converts a string into an identifier suitable for variables.
107    *
108    * In general it should generate "namesLikeThis".
109    *
110    * @param token
111    *          the string to convert
112    * @return the equivalent variable name
113    */
114   String toVariableName(String token);
115 
116   /**
117    * Converts a namespace URI into a package name. This method should expect
118    * strings like "http://foo.bar.zot/org", "urn:abc:def:ghi" "", or even "###"
119    * (basically anything) and expected to return a package name, liks
120    * "org.acme.foo".
121    *
122    * @param namespaceUri
123    *          the string to convert
124    * @return the equivalent package name
125    */
126   String toPackageName(String namespaceUri);
127 
128   class Standard
129       extends NameUtil
130       implements NameConverter {
131 
132     /**
133      * Default constructor.
134      */
135     public Standard() {
136       // do nothing
137     }
138 
139     @Override
140     public String toClassName(String token) {
141       return toMixedCaseName(toWordList(token), true);
142     }
143 
144     @Override
145     public String toVariableName(String token) {
146       return toMixedCaseName(toWordList(token), false);
147     }
148 
149     @Override
150     public String toInterfaceName(String token) {
151       return toClassName(token);
152     }
153 
154     @Override
155     public String toPropertyName(String token) {
156       String prop = toClassName(token);
157       // property name "Class" with collide with Object.getClass,
158       // so escape this.
159       if ("Class".equals(prop)) {
160         prop = "Clazz";
161       }
162       return prop;
163     }
164 
165     /**
166      * Computes a Java package name from a namespace URI, as specified in the spec.
167      *
168      * @return {@code null} if it fails to derive a package name.
169      */
170     @SuppressWarnings({
171         "PMD.CyclomaticComplexity", "PMD.NPathComplexity", // acceptable
172         "PMD.OnlyOneReturn", // readability
173         "PMD.UseStringBufferForStringAppends" // ok
174     })
175     @Override
176     public String toPackageName(String uri) {
177       String nsUri = uri;
178       // remove scheme and :, if present
179       // spec only requires us to remove 'http' and 'urn'...
180       int idx = nsUri.indexOf(':');
181       String scheme = "";
182       if (idx >= 0) {
183         scheme = nsUri.substring(0, idx);
184         if ("http".equalsIgnoreCase(scheme) || "urn".equalsIgnoreCase(scheme)) {
185           nsUri = nsUri.substring(idx + 1);
186         }
187       }
188 
189       // tokenize string
190       List<String> tokens = tokenize(nsUri, "/: ");
191       if (tokens.isEmpty()) {
192         return null;
193       }
194 
195       // remove trailing file type, if necessary
196       if (tokens.size() > 1) {
197         // for uri's like "www.foo.com" and "foo.com", there is no trailing
198         // file, so there's no need to look at the last '.' and substring
199         // otherwise, we loose the "com" (which would be wrong)
200         String lastToken = tokens.get(tokens.size() - 1);
201         idx = lastToken.lastIndexOf('.');
202         if (idx > 0) {
203           lastToken = lastToken.substring(0, idx);
204           tokens.set(tokens.size() - 1, lastToken);
205         }
206       }
207 
208       // tokenize domain name and reverse. Also remove :port if it exists
209       String domain = tokens.get(0);
210       idx = domain.indexOf(':');
211       if (idx >= 0) {
212         domain = domain.substring(0, idx);
213       }
214       List<String> rev = reverse(tokenize(domain, "urn".equals(scheme) ? ".-" : "."));
215       if ("www".equalsIgnoreCase(rev.get(rev.size() - 1))) {
216         // remove leading www
217         rev.remove(rev.size() - 1);
218       }
219 
220       // replace the domain name with tokenized items
221       tokens.addAll(1, rev);
222       tokens.remove(0);
223 
224       // iterate through the tokens and apply xml->java name algorithm
225       for (int i = 0; i < tokens.size(); i++) {
226 
227         // get the token and remove illegal chars
228         String token = tokens.get(i);
229         token = removeIllegalIdentifierChars(token);
230 
231         // this will check for reserved keywords
232         if (SourceVersion.isKeyword(token.toLowerCase(Locale.ENGLISH))) {
233           token = '_' + token;
234         }
235 
236         tokens.set(i, token.toLowerCase(Locale.ENGLISH));
237       }
238 
239       // concat all the pieces and return it
240       return combine(tokens, '.');
241     }
242 
243     private static String removeIllegalIdentifierChars(String token) {
244       StringBuilder newToken = new StringBuilder(token.length() + 1); // max expected length
245       for (int i = 0; i < token.length(); i++) {
246         char ch = token.charAt(i);
247         if (i == 0 && !Character.isJavaIdentifierStart(ch)) { // ch can't be used as FIRST char
248           newToken.append('_');
249         }
250         if (Character.isJavaIdentifierPart(ch)) {
251           newToken.append(ch); // ch is valid
252         } else {
253           newToken.append('_'); // ch can't be used
254         }
255       }
256       return newToken.toString();
257     }
258 
259     private static List<String> tokenize(String str, String sep) {
260       StringTokenizer tokens = new StringTokenizer(str, sep);
261       ArrayList<String> retval = new ArrayList<>();
262 
263       while (tokens.hasMoreTokens())
264         retval.add(tokens.nextToken());
265 
266       return retval;
267     }
268 
269     private static <T> List<T> reverse(List<T> list) {
270       List<T> rev = new ArrayList<>(list.size());
271 
272       for (int i = list.size() - 1; i >= 0; i--) {
273         rev.add(list.get(i));
274       }
275 
276       return rev;
277     }
278 
279     private static String combine(List<String> tokens, char sep) {
280       StringBuilder buf = new StringBuilder(tokens.get(0));
281 
282       for (int i = 1; i < tokens.size(); i++) {
283         buf.append(sep);
284         buf.append(tokens.get(i));
285       }
286 
287       return buf.toString();
288     }
289   }
290 }