1 /**
2 * Redistribution and use of this software and associated documentation
3 * ("Software"), with or without modification, are permitted provided
4 * that the following conditions are met:
5 *
6 * 1. Redistributions of source code must retain copyright
7 * statements and notices. Redistributions must also contain a
8 * copy of this document.
9 *
10 * 2. Redistributions in binary form must reproduce the
11 * above copyright notice, this list of conditions and the
12 * following disclaimer in the documentation and/or other
13 * materials provided with the distribution.
14 *
15 * 3. The name "Exolab" must not be used to endorse or promote
16 * products derived from this Software without prior written
17 * permission of Intalio, Inc. For written permission,
18 * please contact info@exolab.org.
19 *
20 * 4. Products derived from this Software may not be called "Exolab"
21 * nor may "Exolab" appear in their names without prior written
22 * permission of Intalio, Inc. Exolab is a registered
23 * trademark of Intalio, Inc.
24 *
25 * 5. Due credit should be given to the Exolab Project
26 * (http://www.exolab.org/).
27 *
28 * THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS
29 * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
30 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
31 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
32 * INTALIO, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
33 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
34 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
35 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
36 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
37 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
38 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
39 * OF THE POSSIBILITY OF SUCH DAMAGE.
40 *
41 * Copyright 1999-2003 (C) Intalio, Inc. All Rights Reserved.
42 *
43 * $Id: JavaNamingImpl.java 9078 2011-11-03 20:50:10Z wguttmn $
44 */
45
46 package org.castor.xml;
47
48 import java.io.File;
49 import java.lang.reflect.Field;
50 import java.lang.reflect.Method;
51 import java.util.Hashtable;
52
53 import org.apache.commons.logging.Log;
54 import org.apache.commons.logging.LogFactory;
55 import org.springframework.stereotype.Component;
56
57 /**
58 * This class converts XML Names to proper Java names. As Java names are not
59 * completely defined this implementation is Castor specific.
60 * The first implementation was done by <a href="mailto:kvisco@intalio.com">Keith Visco</a>
61 * but had been changed radically since.
62 *
63 * @author <a href="mailto:jgrueneis_at_gmail_dot_com">Joachim Grueneis</a>
64 * @version $Id: JavaNamingImpl.java 9078 2011-11-03 20:50:10Z wguttmn $
65 */
66 @Component("javaNamingNG")
67 public class JavaNamingNGImpl implements JavaNaming {
68
69 /** Logger of this class. */
70 private static final Log LOG = LogFactory.getLog(JavaNamingNGImpl.class);
71
72 /**
73 * The property name to use in the castor.properties file to specify the
74 * value of the <code>upperCaseAfterUnderscore</code> variable.
75 */
76 public static final String UPPER_CASE_AFTER_UNDERSCORE_PROPERTY
77 = "org.exolab.castor.xml.JavaNaming.upperCaseAfterUnderscore";
78
79 /**
80 * Used for backward compatibility, if you wish to be backward compatible
81 * with 0.9.3.9 and earlier set this boolean to true.
82 */
83 public static boolean _upperCaseAfterUnderscore = false;
84
85 /** the map of substition words for all keywords. */
86 private static final Hashtable<String, String> SUBST = keywordMap();
87
88 private InternalContext context;
89
90 /** all known Java keywords. */
91 private static final String[] KEYWORDS = {"abstract", "boolean", "break", "byte", "case",
92 "catch", "char", "class", "const", "continue", "default", "do", "double", "else",
93 "extends", "false", "final", "finally", "float", "for", "goto", "if", "implements",
94 "import", "instanceof", "int", "interface", "long", "native", "new", "null", "package",
95 "private", "protected", "public", "return", "short", "static", "super", "switch",
96 "synchronized", "this", "throw", "throws", "transient", "true", "try", "void",
97 "volatile", "while"};
98
99 /**
100 * private constructor.
101 */
102 public JavaNamingNGImpl() {
103 super();
104 }
105
106 public JavaNamingNGImpl(InternalContext context) {
107 super();
108 this.context = context;
109 }
110
111 /**
112 * Returns true if the given String is a Java keyword which will cause a
113 * problem when used as a variable name.
114 * @param name the name to check
115 * @return true if it is a keyword
116 * @see org.castor.xml.JavaNaming#isKeyword(java.lang.String)
117 */
118 public final boolean isKeyword(final String name) {
119 if (name == null) {
120 return false;
121 }
122 for (int i = 0; i < KEYWORDS.length; i++) {
123 if (KEYWORDS[i].equals(name)) {
124 return true;
125 }
126 }
127 return false;
128 }
129
130 /**
131 * Returns true if the given String matches the production of a valid Java
132 * identifier.
133 *
134 * @param string
135 * The String to check the production of.
136 * @return true if the given String matches the production of a valid Java
137 * name, otherwise false.
138 * @see org.castor.xml.JavaNaming#isValidJavaIdentifier(java.lang.String)
139 */
140 public final boolean isValidJavaIdentifier(final String string) {
141 if ((string == null) || (string.length() == 0)) {
142 return false;
143 }
144
145 for (int i = 0; i < string.length(); i++) {
146 char ch = string.charAt(i);
147
148 // -- digit
149 if (ch == '_') {
150 continue;
151 }
152 if (ch == '$') {
153 continue;
154 }
155
156 if ((ch >= 'A') && (ch <= 'Z')) {
157 continue;
158 }
159 if ((ch >= 'a') && (ch <= 'z')) {
160 continue;
161 }
162 if ((ch >= '0') && (ch <= '9')) {
163 if (i == 0) {
164 return false;
165 }
166 continue;
167 }
168
169 return false;
170 }
171 if (isKeyword(string)) {
172 return false;
173 }
174 return true;
175 }
176
177 /**
178 * Cuts away a leading namespace prefix (if there is one in place).
179 * @param name the XML name to convert to a Java name
180 * @return a name which follows Java naming conventions
181 * @see org.castor.xml.JavaNaming#toJavaClassName(java.lang.String)
182 */
183 public final String toJavaClassName(final String name) {
184
185 if ((name == null) || (name.length() <= 0)) {
186 // handle error
187 return name; // -- for now just return name
188 }
189 // Remove namespace prefix (Andrew Fawcett, temporary until namespace
190 // changes go in)
191 int colon = name.indexOf(':');
192 if (colon != -1) {
193 return toJavaName(name.substring(colon + 1), true);
194 }
195 return toJavaName(name, true);
196
197 }
198
199 /**
200 * Converts the given name to a valid Java name.
201 * @param name the XML name to convert
202 * @return a valid Java member name
203 * @see org.castor.xml.JavaNaming#toJavaMemberName(java.lang.String)
204 */
205 public final String toJavaMemberName(final String name) {
206 return toJavaMemberName(name, true);
207 }
208
209 /**
210 * Converts the given name to a valid Java name.
211 * @param name the XML name to convert
212 * @param useKeywordSubstitutions set to true to turn on keyword substitution
213 * @return a valid Java member name
214 * @see org.castor.xml.JavaNaming#toJavaMemberName(java.lang.String,boolean)
215 */
216 public final String toJavaMemberName(final String name, final boolean useKeywordSubstitutions) {
217
218 if (name == null) {
219 return null;
220 }
221
222 String memberName = toJavaName(name, false);
223
224 if (isKeyword(memberName) && useKeywordSubstitutions) {
225 String mappedName = (String) SUBST.get(memberName);
226 if (mappedName != null) {
227 memberName = mappedName;
228 }
229 }
230 return memberName;
231 }
232
233 /**
234 * Checks if the given package name is valid or not. Empty package names
235 * are considered valid!
236 *
237 * @param packageName
238 * name of package as String with periods
239 * @return true if package name is valid
240 * @see org.castor.xml.JavaNaming#isValidPackageName(java.lang.String)
241 */
242 public final boolean isValidPackageName(final String packageName) {
243 if ((packageName == null) || (packageName.length() < 1)) {
244 return true;
245 }
246 if (".".equals(packageName)) {
247 return false;
248 }
249 if (packageName.startsWith(".") || (packageName.endsWith("."))) {
250 return false;
251 }
252 boolean valid = true;
253 String[] packageNameParts = packageName.split("\\.");
254 for (int i = 0; i < packageNameParts.length; i++) {
255 String packageNamePart = packageNameParts[i];
256 valid &= isValidJavaIdentifier(packageNamePart);
257 }
258 return valid;
259 }
260
261 /**
262 * Converts the given Package name to it's corresponding Path. The path will
263 * be a relative path.
264 * @param packageName the package name to convert
265 * @return a String containing the resulting patch
266 * @see org.castor.xml.JavaNaming#packageToPath(java.lang.String)
267 */
268 public final String packageToPath(final String packageName) {
269 if (packageName == null) {
270 return packageName;
271 }
272 if (!isValidPackageName(packageName)) {
273 String message = "Package name: " + packageName + " is not valid";
274 LOG.warn(message);
275 throw new IllegalArgumentException(message);
276 }
277 return packageName.replace('.', File.separatorChar);
278 }
279
280 /**
281 * To initialize the keyword map.
282 * @return an initialized keyword map
283 */
284 private static Hashtable<String, String> keywordMap() {
285 Hashtable<String, String> ht = new Hashtable<String, String>();
286 ht.put("class", "clazz");
287 return ht;
288 }
289
290 /**
291 * Converts the given xml name to a Java name.
292 *
293 * @param name
294 * the name to convert to a Java Name
295 * @param upperFirst
296 * a flag to indicate whether or not the the first character
297 * should be converted to uppercase.
298 * @return the resulting Java name
299 */
300 private String toJavaName(final String name, final boolean upperFirst) {
301
302 int size = name.length();
303 char[] ncChars = name.toCharArray();
304 int next = 0;
305
306 boolean uppercase = upperFirst;
307
308 // -- initialize lowercase, this is either (!uppercase) or
309 // -- false depending on if the first two characters
310 // -- are uppercase
311 boolean lowercase = (!uppercase);
312 if ((size > 1) && lowercase) {
313 if (Character.isUpperCase(ncChars[0]) && Character.isUpperCase(ncChars[1])) {
314 if (context != null && context.getBooleanProperty(XMLProperties.MEMBER_NAME_CAPITALISATION_STRICT)) {
315 lowercase = true;
316 } else {
317 lowercase = false;
318 }
319 }
320 }
321
322 for (int i = 0; i < size; i++) {
323 char ch = ncChars[i];
324
325 switch (ch) {
326 case '.':
327 case ' ':
328 ncChars[next++] = '_';
329 break;
330 case ':':
331 case '-':
332 uppercase = true;
333 break;
334 case '_':
335 // -- backward compatibility with 0.9.3.9
336 if (_upperCaseAfterUnderscore) {
337 uppercase = true;
338 ncChars[next] = ch;
339 ++next;
340 break;
341 }
342 // -- for backward compatibility with 0.9.3
343 /*
344 * if (replaceUnderscore) { uppercase = true; break; }
345 */
346 // --> do not break here for anything greater
347 // --> than 0.9.3.9
348 default:
349 if (uppercase) {
350 ncChars[next] = Character.toUpperCase(ch);
351 uppercase = false;
352 } else if (lowercase) {
353 ncChars[next] = Character.toLowerCase(ch);
354 lowercase = false;
355 } else {
356 ncChars[next] = ch;
357 }
358 ++next;
359 break;
360 }
361 }
362 return new String(ncChars, 0, next);
363 }
364
365 /**
366 * Qualifies the given <code>fileName</code> with the given
367 * <code>packageName</code> and returns the resulting file path.<br>
368 * If <code>packageName</code> is <code>null</code> or a zero-length
369 * String, this method will return <code>fileName</code>.<br>
370 *
371 * @param fileName
372 * The file name to be qualified.
373 * @param packageName
374 * The package name to be used for qualifying.
375 * @return The qualified file path.
376 * @see org.castor.xml.JavaNaming#getQualifiedFileName(java.lang.String,java.lang.String)
377 */
378 public final String getQualifiedFileName(final String fileName, final String packageName) {
379 if ((packageName == null) || (packageName.length() == 0)) {
380 return fileName;
381 }
382 StringBuffer result = new StringBuffer();
383 result.append(packageToPath(packageName));
384 result.append('/');
385 result.append(fileName);
386 return result.toString();
387 }
388
389 /**
390 * Gets the package name of the given class name.
391 *
392 * @param className
393 * The class name to retrieve the package name from.
394 * @return The package name or the empty String if <code>className</code>
395 * is <code>null</code> or does not contain a package.
396 * @see org.castor.xml.JavaNaming#getPackageName(java.lang.String)
397 */
398 public final String getPackageName(final String className) {
399 if ((className == null) || (className.length() < 1)) {
400 return className;
401 }
402
403 int idx = className.lastIndexOf('.');
404 if (idx >= 0) {
405 return className.substring(0, idx);
406 }
407 return "";
408 }
409
410 /**
411 * Extracts the filed name part from the methods name. Mostly it cuts
412 * away the method prefix.
413 * @param method the Method to process
414 * @return the extracted field name
415 * @see org.castor.xml.JavaNaming#extractFieldNameFromMethod(java.lang.reflect.Method)
416 */
417 public final String extractFieldNameFromMethod(final Method method) {
418 if (method == null) {
419 return null;
420 }
421 String fieldName = null;
422 if (isSetMethod(method)) {
423 fieldName = method.getName().substring(METHOD_PREFIX_SET.length());
424 } else if (isCreateMethod(method)) {
425 fieldName = method.getName().substring(METHOD_PREFIX_CREATE.length());
426 } else if (isGetMethod(method)) {
427 fieldName = method.getName().substring(METHOD_PREFIX_GET.length());
428 } else if (isIsMethod(method)) {
429 fieldName = method.getName().substring(METHOD_PREFIX_IS.length());
430 } else if (isAddMethod(method)) {
431 fieldName = method.getName().substring(METHOD_PREFIX_ADD.length());
432 }
433 return toJavaMemberName(fieldName);
434 }
435
436 /**
437 * Extracts the field name part from the Field. Mostly it cuts away
438 * prefixes like '_'.
439 *
440 * @param field the Field to process
441 * @return The extracted field name.
442 * @see org.castor.xml.JavaNaming#extractFieldNameFromField(java.lang.reflect.Field)
443 */
444 public final String extractFieldNameFromField(Field field) {
445 if (field == null) {
446 return null;
447 }
448 String fieldName = field.getName();
449 if (fieldName.charAt(0) == FIELD_UNDERSCORE_PREFIX) {
450 fieldName = fieldName.substring(1);
451 }
452 return fieldName;
453 }
454
455 /**
456 * Checks if the given method is a set method.
457 * @param method the Method to check
458 * @return true if it is a set method
459 * @see org.castor.xml.JavaNaming#isSetMethod(java.lang.reflect.Method)
460 */
461 public final boolean isSetMethod(final Method method) {
462 if (method == null) {
463 return false;
464 }
465 if (!method.getName().startsWith(METHOD_PREFIX_SET)) {
466 return false;
467 }
468 if (method.getParameterTypes().length != 1) {
469 return false;
470 }
471 if ((method.getReturnType() != void.class) && (method.getReturnType() != Void.class)) {
472 return false;
473 }
474 return true;
475 }
476
477 /**
478 * Checks if the given method is a create method.
479 * @param method the Method to check
480 * @return true if it is a create method
481 * @see org.castor.xml.JavaNaming#isCreateMethod(java.lang.reflect.Method)
482 */
483 public final boolean isCreateMethod(final Method method) {
484 if (method == null) {
485 return false;
486 }
487 if (!method.getName().startsWith(METHOD_PREFIX_CREATE)) {
488 return false;
489 }
490 if (method.getParameterTypes().length != 0) {
491 return false;
492 }
493 if (method.getReturnType() == null) {
494 return false;
495 }
496 return true;
497 }
498
499 /**
500 * Checks if the given method is a get method.
501 * @param method the Method to check
502 * @return true if it is a get method
503 * @see org.castor.xml.JavaNaming#isGetMethod(java.lang.reflect.Method)
504 */
505 public final boolean isGetMethod(final Method method) {
506 if (method == null) {
507 return false;
508 }
509 if (!method.getName().startsWith(METHOD_PREFIX_GET)) {
510 return false;
511 }
512 if (method.getParameterTypes().length != 0) {
513 return false;
514 }
515 if (method.getReturnType() == null) {
516 return false;
517 }
518 return true;
519 }
520
521 /**
522 * Checks if the given method is a 'is' method.
523 * @param method the Method to check
524 * @return true if it is a 'is' method
525 * @see org.castor.xml.JavaNaming#isIsMethod(java.lang.reflect.Method)
526 */
527 public final boolean isIsMethod(final Method method) {
528 if (method == null) {
529 return false;
530 }
531 if (!method.getName().startsWith(METHOD_PREFIX_IS)) {
532 return false;
533 }
534 if (method.getParameterTypes().length != 0) {
535 return false;
536 }
537 if ((method.getReturnType().isPrimitive()) && (method.getReturnType() != Boolean.TYPE)) {
538 return false;
539 }
540 if ((!method.getReturnType().isPrimitive()) && (method.getReturnType() != Boolean.class)) {
541 return false;
542 }
543 return true;
544 }
545
546 /**
547 * Checks if the given method is an add method.
548 * @param method the Method to check
549 * @return true if it is an add method
550 * @see org.castor.xml.JavaNaming#isAddMethod(java.lang.reflect.Method)
551 */
552 public final boolean isAddMethod(final Method method) {
553 if (method == null) {
554 return false;
555 }
556 if (!method.getName().startsWith(METHOD_PREFIX_ADD)) {
557 return false;
558 }
559 if (method.getParameterTypes().length != 1) {
560 return false;
561 }
562 if ((method.getReturnType() != void.class) && (method.getReturnType() != Void.class)) {
563 return false;
564 }
565 return true;
566 }
567
568 /**
569 * Generates the name of an add method for the given field name.
570 * @param fieldName the field name to generate a method name for
571 * @return the generated add method name
572 */
573 public final String getAddMethodNameForField(final String fieldName) {
574 return METHOD_PREFIX_ADD + toJavaClassName(fieldName);
575 }
576
577 /**
578 * Generates the name of a set method for the given field name.
579 * @param fieldName the field name to generate a method name for
580 * @return the generated set method name
581 */
582 public final String getCreateMethodNameForField(final String fieldName) {
583 return METHOD_PREFIX_CREATE + toJavaClassName(fieldName);
584 }
585
586 /**
587 * Generates the name of a get method for the given field name.
588 * @param fieldName the field name to generate a method name for
589 * @return the generated get method name
590 */
591 public final String getGetMethodNameForField(final String fieldName) {
592 return METHOD_PREFIX_GET + toJavaClassName(fieldName);
593 }
594
595 /**
596 * Generates the name of an is method for the given field name.
597 * @param fieldName the field name to generate a method name for
598 * @return the generated is method name
599 */
600 public final String getIsMethodNameForField(final String fieldName) {
601 return METHOD_PREFIX_IS + toJavaClassName(fieldName);
602 }
603
604 /**
605 * Generates the name of a create method for the given field name.
606 * @param fieldName the field name to generate a method name for
607 * @return the generated create method name
608 */
609 public final String getSetMethodNameForField(final String fieldName) {
610 return METHOD_PREFIX_SET + toJavaClassName(fieldName);
611 }
612
613 /**
614 * Gets the class name without package part.
615 *
616 * @param clazz The class to retrieve the name from
617 * @return the class name without package part or null
618 * {@inheritDoc}
619 * @see org.castor.xml.JavaNaming#getClassName(java.lang.Class)
620 */
621 public String getClassName(Class clazz) {
622 if (clazz == null) {
623 return null;
624 }
625 String name = clazz.getName();
626 int idx = name.lastIndexOf('.');
627 if (idx >= 0) {
628 name = name.substring(idx+1);
629 }
630 return name;
631 }
632 }