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$
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$
65 */
66 @Component("javaNamingImpl")
67 public class JavaNamingImpl implements JavaNaming {
68 /** Logger of this class. */
69 private static final Log LOG = LogFactory.getLog(JavaNamingImpl.class);
70
71 /**
72 * The property name to use in the castor.properties file to specify the
73 * value of the <code>upperCaseAfterUnderscore</code> variable.
74 */
75 public static final String UPPER_CASE_AFTER_UNDERSCORE_PROPERTY
76 = "org.exolab.castor.xml.JavaNaming.upperCaseAfterUnderscore";
77
78 /**
79 * Used for backward compatibility, if you wish to be backward compatible
80 * with 0.9.3.9 and earlier set this boolean to true.
81 */
82 public static boolean _upperCaseAfterUnderscore = false;
83
84 /** the map of substition words for all keywords. */
85 private static final Hashtable<String, String> SUBST = keywordMap();
86
87 private InternalContext context;
88
89 /** all known Java keywords. */
90 private static final String[] KEYWORDS = {"abstract", "boolean", "break", "byte", "case",
91 "catch", "char", "class", "const", "continue", "default", "do", "double", "else",
92 "extends", "false", "final", "finally", "float", "for", "goto", "if", "implements",
93 "import", "instanceof", "int", "interface", "long", "native", "new", "null", "package",
94 "private", "protected", "public", "return", "short", "static", "super", "switch",
95 "synchronized", "this", "throw", "throws", "transient", "true", "try", "void",
96 "volatile", "while"}; // -- KEYWORDS
97
98 /**
99 * private constructor.
100 */
101 public JavaNamingImpl() {
102 super();
103 }
104
105 public JavaNamingImpl(InternalContext context) {
106 super();
107 this.context = context;
108 }
109
110 /**
111 * Returns true if the given String is a Java keyword which will cause a
112 * problem when used as a variable name.
113 * @param name the name to check
114 * @return true if it is a keyword
115 * @see org.castor.xml.JavaNaming#isKeyword(java.lang.String)
116 */
117 public final boolean isKeyword(final String name) {
118 if (name == null) {
119 return false;
120 }
121 for (int i = 0; i < KEYWORDS.length; i++) {
122 if (KEYWORDS[i].equals(name)) {
123 return true;
124 }
125 }
126 return false;
127 } // -- isKeyword
128
129 /**
130 * Returns true if the given String matches the production of a valid Java
131 * identifier.
132 *
133 * @param string
134 * The String to check the production of.
135 * @return true if the given String matches the production of a valid Java
136 * name, otherwise false.
137 * @see org.castor.xml.JavaNaming#isValidJavaIdentifier(java.lang.String)
138 */
139 public final boolean isValidJavaIdentifier(final String string) {
140 if ((string == null) || (string.length() == 0)) {
141 return false;
142 }
143
144 for (int i = 0; i < string.length(); i++) {
145 char ch = string.charAt(i);
146
147 // -- digit
148 if (ch == '_') {
149 continue;
150 }
151 if (ch == '$') {
152 continue;
153 }
154
155 if ((ch >= 'A') && (ch <= 'Z')) {
156 continue;
157 }
158 if ((ch >= 'a') && (ch <= 'z')) {
159 continue;
160 }
161 if ((ch >= '0') && (ch <= '9')) {
162 if (i == 0) {
163 return false;
164 }
165 continue;
166 }
167
168 return false;
169 }
170 if (isKeyword(string)) {
171 return false;
172 }
173 return true;
174 } // -- isValidJavaIdentifier
175
176 /**
177 * Cuts away a leading namespace prefix (if there is one in place).
178 * @param name the XML name to convert to a Java name
179 * @return a name which follows Java naming conventions
180 * @see org.castor.xml.JavaNaming#toJavaClassName(java.lang.String)
181 */
182 public final String toJavaClassName(final String name) {
183
184 if ((name == null) || (name.length() <= 0)) {
185 // handle error
186 return name; // -- for now just return name
187 }
188 // Remove namespace prefix (Andrew Fawcett, temporary until namespace
189 // changes go in)
190 int colon = name.indexOf(':');
191 if (colon != -1) {
192 return toJavaName(name.substring(colon + 1), true);
193 }
194 return toJavaName(name, true);
195
196 } // -- toJavaClassName
197
198 /**
199 * Appends a leading '_' and converts the given name to a java name.
200 * @param name the XML name to convert
201 * @return a Java member name starting with a leading _
202 * @see org.castor.xml.JavaNaming#toJavaMemberName(java.lang.String)
203 */
204 public final String toJavaMemberName(final String name) {
205 return toJavaMemberName(name, true);
206 } // -- toJavaMemberName
207
208 /**
209 * Appends a leading '_' and converts the given name to a java name.
210 * @param name the XML name to convert
211 * @param useKeywordSubstitutions set to true to turn on keyword substitution
212 * @return a Java member name starting with a leading _
213 * @see org.castor.xml.JavaNaming#toJavaMemberName(java.lang.String,boolean)
214 */
215 public final String toJavaMemberName(final String name, final boolean useKeywordSubstitutions) {
216
217 if (name == null) {
218 return null;
219 }
220
221 String memberName = toJavaName(name, false);
222
223 if (isKeyword(memberName) && useKeywordSubstitutions) {
224 String mappedName = (String) SUBST.get(memberName);
225 if (mappedName != null) {
226 memberName = mappedName;
227 } else {
228 memberName = FIELD_UNDERSCORE_PREFIX + memberName;
229 }
230 }
231 return memberName;
232 } // -- toJavaMemberName
233
234 /**
235 * Checks if the given pacckage name is valid or not. Empty pacakge names
236 * are considered valid!
237 *
238 * @param packageName
239 * name of package as String with periods
240 * @return true if package name is valid
241 * @see org.castor.xml.JavaNaming#isValidPackageName(java.lang.String)
242 */
243 public final boolean isValidPackageName(final String packageName) {
244 if ((packageName == null) || (packageName.length() < 1)) {
245 return true;
246 }
247 if (".".equals(packageName)) {
248 return false;
249 }
250 if (packageName.startsWith(".") || (packageName.endsWith("."))) {
251 return false;
252 }
253 boolean valid = true;
254 String[] packageNameParts = packageName.split("\\.");
255 for (int i = 0; i < packageNameParts.length; i++) {
256 String packageNamePart = packageNameParts[i];
257 valid &= isValidJavaIdentifier(packageNamePart);
258 }
259 return valid;
260 }
261
262 /**
263 * Converts the given Package name to it's corresponding Path. The path will
264 * be a relative path.
265 * @param packageName the package name to convert
266 * @return a String containing the resulting patch
267 * @see org.castor.xml.JavaNaming#packageToPath(java.lang.String)
268 */
269 public final String packageToPath(final String packageName) {
270 if (packageName == null) {
271 return packageName;
272 }
273 if (!isValidPackageName(packageName)) {
274 String message = "Package name: " + packageName + " is not valid";
275 LOG.warn(message);
276 throw new IllegalArgumentException(message);
277 }
278 return packageName.replace('.', File.separatorChar);
279 } // -- packageToPath
280
281 /**
282 * To initialize the keyword map.
283 * @return an initialized keyword map
284 */
285 private static Hashtable<String, String> keywordMap() {
286 Hashtable<String, String> ht = new Hashtable<String, String>();
287 ht.put("class", "clazz");
288 return ht;
289 } // -- keywordMap
290
291 /**
292 * Converts the given xml name to a Java name.
293 *
294 * @param name
295 * the name to convert to a Java Name
296 * @param upperFirst
297 * a flag to indicate whether or not the the first character
298 * should be converted to uppercase.
299 * @return the resulting Java name
300 */
301 private String toJavaName(final String name, final boolean upperFirst) {
302
303 int size = name.length();
304 char[] ncChars = name.toCharArray();
305 int next = 0;
306
307 boolean uppercase = upperFirst;
308
309 // -- initialize lowercase, this is either (!uppercase) or
310 // -- false depending on if the first two characters
311 // -- are uppercase (unless override is specified by means of property)
312 boolean lowercase = (!uppercase);
313 if ((size > 1) && lowercase) {
314 if (Character.isUpperCase(ncChars[0]) && Character.isUpperCase(ncChars[1])) {
315 if (context != null && context.getBooleanProperty(XMLProperties.MEMBER_NAME_CAPITALISATION_STRICT)) {
316 lowercase = true;
317 } else {
318 lowercase = false;
319 }
320 }
321 }
322
323 for (int i = 0; i < size; i++) {
324 char ch = ncChars[i];
325
326 switch (ch) {
327 case '.':
328 case ' ':
329 ncChars[next++] = '_';
330 break;
331 case ':':
332 case '-':
333 uppercase = true;
334 break;
335 case '_':
336 // -- backward compatibility with 0.9.3.9
337 if (_upperCaseAfterUnderscore) {
338 uppercase = true;
339 ncChars[next] = ch;
340 ++next;
341 break;
342 }
343 // -- for backward compatibility with 0.9.3
344 /*
345 * if (replaceUnderscore) { uppercase = true; break; }
346 */
347 // --> do not break here for anything greater
348 // --> than 0.9.3.9
349 default:
350 if (uppercase) {
351 ncChars[next] = Character.toUpperCase(ch);
352 uppercase = false;
353 } else if (lowercase) {
354 ncChars[next] = Character.toLowerCase(ch);
355 lowercase = false;
356 } else {
357 ncChars[next] = ch;
358 }
359 ++next;
360 break;
361 }
362 }
363 return new String(ncChars, 0, next);
364 } // -- toJavaName
365
366 /**
367 * Qualifies the given <code>fileName</code> with the given
368 * <code>packageName</code> and returns the resulting file path.<br>
369 * If <code>packageName</code> is <code>null</code> or a zero-length
370 * String, this method will return <code>fileName</code>.<br>
371 *
372 * @param fileName
373 * The file name to be qualified.
374 * @param packageName
375 * The package name to be used for qualifying.
376 * @return The qualified file path.
377 * @see org.castor.xml.JavaNaming#getQualifiedFileName(java.lang.String,java.lang.String)
378 */
379 public final String getQualifiedFileName(final String fileName, final String packageName) {
380 if ((packageName == null) || (packageName.length() == 0)) {
381 return fileName;
382 }
383 StringBuffer result = new StringBuffer();
384 result.append(packageToPath(packageName));
385 result.append('/');
386 result.append(fileName);
387 return result.toString();
388 } // -- getQualifiedFileName
389
390 /**
391 * Gets the package name of the given class name.
392 *
393 * @param className
394 * The class name to retrieve the package name from.
395 * @return The package name or the empty String if <code>className</code>
396 * is <code>null</code> or does not contain a package.
397 * @see org.castor.xml.JavaNaming#getPackageName(java.lang.String)
398 */
399 public final String getPackageName(final String className) {
400 if ((className == null) || (className.length() < 1)) {
401 return className;
402 }
403
404 int idx = className.lastIndexOf('.');
405 if (idx >= 0) {
406 return className.substring(0, idx);
407 }
408 return "";
409 } // -- getPackageName
410
411 /**
412 * Extracts the filed name part from the methods name. Mostly it cuts
413 * away the method prefix.
414 * @param method the Method to process
415 * @return the extracted field name
416 * @see org.castor.xml.JavaNaming#extractFieldNameFromMethod(java.lang.reflect.Method)
417 */
418 public final String extractFieldNameFromMethod(final Method method) {
419 if (method == null) {
420 return null;
421 }
422 String fieldName = null;
423 if (isSetMethod(method)) {
424 fieldName = method.getName().substring(METHOD_PREFIX_SET.length());
425 } else if (isCreateMethod(method)) {
426 fieldName = method.getName().substring(METHOD_PREFIX_CREATE.length());
427 } else if (isGetMethod(method)) {
428 fieldName = method.getName().substring(METHOD_PREFIX_GET.length());
429 } else if (isIsMethod(method)) {
430 fieldName = method.getName().substring(METHOD_PREFIX_IS.length());
431 } else if (isAddMethod(method)) {
432 fieldName = method.getName().substring(METHOD_PREFIX_ADD.length());
433 }
434 return toJavaMemberName(fieldName);
435 } //FIELD_UNDERSCORE_PREFIX
436
437 /**
438 * Extracts the field name part from the Field. Mostly it cuts away
439 * prefixes like '_'.
440 *
441 * @param field the Field to process
442 * @return The extracted field name.
443 * @see org.castor.xml.JavaNaming#extractFieldNameFromField(java.lang.reflect.Field)
444 */
445 public final String extractFieldNameFromField(Field field) {
446 if (field == null) {
447 return null;
448 }
449 String fieldName = field.getName();
450 if (fieldName.charAt(0) == FIELD_UNDERSCORE_PREFIX) {
451 fieldName = fieldName.substring(1);
452 }
453 return fieldName;
454 }
455
456 /**
457 * Checks if the given method is a set method.
458 * @param method the Method to check
459 * @return true if it is a set method
460 * @see org.castor.xml.JavaNaming#isSetMethod(java.lang.reflect.Method)
461 */
462 public final boolean isSetMethod(final Method method) {
463 if (method == null) {
464 return false;
465 }
466 if (!method.getName().startsWith(METHOD_PREFIX_SET)) {
467 return false;
468 }
469 if (method.getParameterTypes().length != 1) {
470 return false;
471 }
472 if ((method.getReturnType() != void.class) && (method.getReturnType() != Void.class)) {
473 return false;
474 }
475 return true;
476 }
477
478 /**
479 * Checks if the given method is a create method.
480 * @param method the Method to check
481 * @return true if it is a create method
482 * @see org.castor.xml.JavaNaming#isCreateMethod(java.lang.reflect.Method)
483 */
484 public final boolean isCreateMethod(final Method method) {
485 if (method == null) {
486 return false;
487 }
488 if (!method.getName().startsWith(METHOD_PREFIX_CREATE)) {
489 return false;
490 }
491 if (method.getParameterTypes().length != 0) {
492 return false;
493 }
494 if (method.getReturnType() == null) {
495 return false;
496 }
497 return true;
498 }
499
500 /**
501 * Checks if the given method is a get method.
502 * @param method the Method to check
503 * @return true if it is a get method
504 * @see org.castor.xml.JavaNaming#isGetMethod(java.lang.reflect.Method)
505 */
506 public final boolean isGetMethod(final Method method) {
507 if (method == null) {
508 return false;
509 }
510 if (!method.getName().startsWith(METHOD_PREFIX_GET)) {
511 return false;
512 }
513 if (method.getParameterTypes().length != 0) {
514 return false;
515 }
516 if (method.getReturnType() == null) {
517 return false;
518 }
519 return true;
520 }
521
522 /**
523 * Checks if the given method is a 'is' method.
524 * @param method the Method to check
525 * @return true if it is a 'is' method
526 * @see org.castor.xml.JavaNaming#isIsMethod(java.lang.reflect.Method)
527 */
528 public final boolean isIsMethod(final Method method) {
529 if (method == null) {
530 return false;
531 }
532 if (!method.getName().startsWith(METHOD_PREFIX_IS)) {
533 return false;
534 }
535 if (method.getParameterTypes().length != 0) {
536 return false;
537 }
538 if ((method.getReturnType().isPrimitive()) && (method.getReturnType() != Boolean.TYPE)) {
539 return false;
540 }
541 if ((!method.getReturnType().isPrimitive()) && (method.getReturnType() != Boolean.class)) {
542 return false;
543 }
544 return true;
545 }
546
547 /**
548 * Checks if the given method is an add method.
549 * @param method the Method to check
550 * @return true if it is an add method
551 * @see org.castor.xml.JavaNaming#isAddMethod(java.lang.reflect.Method)
552 */
553 public final boolean isAddMethod(final Method method) {
554 if (method == null) {
555 return false;
556 }
557 if (!method.getName().startsWith(METHOD_PREFIX_ADD)) {
558 return false;
559 }
560 if (method.getParameterTypes().length != 1) {
561 return false;
562 }
563 if ((method.getReturnType() != void.class) && (method.getReturnType() != Void.class)) {
564 return false;
565 }
566 return true;
567 }
568
569 /**
570 * Generates the name of an add method for the given field name.
571 * @param fieldName the field name to generate a method name for
572 * @return the generated add method name
573 */
574 public final String getAddMethodNameForField(final String fieldName) {
575 return METHOD_PREFIX_ADD + toJavaClassName(fieldName);
576 }
577
578 /**
579 * Generates the name of a set method for the given field name.
580 * @param fieldName the field name to generate a method name for
581 * @return the generated set method name
582 */
583 public final String getCreateMethodNameForField(final String fieldName) {
584 return METHOD_PREFIX_CREATE + toJavaClassName(fieldName);
585 }
586
587 /**
588 * Generates the name of a get method for the given field name.
589 * @param fieldName the field name to generate a method name for
590 * @return the generated get method name
591 */
592 public final String getGetMethodNameForField(final String fieldName) {
593 return METHOD_PREFIX_GET + toJavaClassName(fieldName);
594 }
595
596 /**
597 * Generates the name of an is method for the given field name.
598 * @param fieldName the field name to generate a method name for
599 * @return the generated is method name
600 */
601 public final String getIsMethodNameForField(final String fieldName) {
602 return METHOD_PREFIX_IS + toJavaClassName(fieldName);
603 }
604
605 /**
606 * Generates the name of a create method for the given field name.
607 * @param fieldName the field name to generate a method name for
608 * @return the generated create method name
609 */
610 public final String getSetMethodNameForField(final String fieldName) {
611 return METHOD_PREFIX_SET + toJavaClassName(fieldName);
612 }
613
614 /**
615 * Gets the class name without package part.
616 *
617 * @param clazz The class to retrieve the name from
618 * @return the class name without package part or null
619 * {@inheritDoc}
620 * @see org.castor.xml.JavaNaming#getClassName(java.lang.Class)
621 */
622 public String getClassName(Class clazz) {
623 if (clazz == null) {
624 return null;
625 }
626 String name = clazz.getName();
627 int idx = name.lastIndexOf('.');
628 if (idx >= 0) {
629 name = name.substring(idx+1);
630 }
631 return name;
632 }
633 } // -- JavaNaming