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: 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 }