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 }