View Javadoc
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