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