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