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.exolab.castor.mapping.loader;
37  
38  import java.lang.reflect.Constructor;
39  import java.lang.reflect.Modifier;
40  import java.io.Serializable;
41  import java.math.BigDecimal;
42  import java.math.BigInteger;
43  import java.util.Vector;
44  
45  import org.castor.core.util.Messages;
46  import org.exolab.castor.types.Duration;
47  
48  /**
49   * Type information. Can be used to map between short type names (such as 'int') and actual Java
50   * types (java.lang.Integer), to determine whether a type is simple (i.e. maps to a single XML
51   * attribute, SQL column, etc), as well as to create a new instance of a type.
52   * 
53   * @author <a href="arkin@intalio.com">Assaf Arkin</a>
54   * @version $Revision$ $Date: 2006-04-25 15:08:23 -0600 (Tue, 25 Apr 2006) $
55   */
56  public class Types {
57  
58    /**
59     * List of all the simple types supported by Castor.
60     */
61    private static TypeInfo[] typeInfos;
62  
63    static {
64      typeInfos = new TypeInfo[] {
65          // shortName primitive
66          // javaType immutable defValue
67          new TypeInfo("other", null, java.lang.Object.class, false, null),
68          new TypeInfo("string", null, java.lang.String.class, true, null),
69          new TypeInfo("integer", java.lang.Integer.TYPE, java.lang.Integer.class, true,
70              Integer.valueOf(0)),
71          new TypeInfo("int", java.lang.Integer.TYPE, java.lang.Integer.TYPE, true,
72              Integer.valueOf(0)),
73          new TypeInfo("long", java.lang.Long.TYPE, java.lang.Long.class, true, Long.valueOf(0L)),
74          new TypeInfo("big-integer", null, java.math.BigInteger.class, true, BigInteger.valueOf(0)),
75          new TypeInfo("boolean", java.lang.Boolean.TYPE, java.lang.Boolean.class, true,
76              Boolean.FALSE),
77          new TypeInfo("double", java.lang.Double.TYPE, java.lang.Double.class, true,
78              Double.valueOf(0.0)),
79          new TypeInfo("float", java.lang.Float.TYPE, java.lang.Float.class, true,
80              Float.valueOf(0.0f)),
81          new TypeInfo("big-decimal", null, java.math.BigDecimal.class, true, BigDecimal.ZERO),
82          new TypeInfo("byte", java.lang.Byte.TYPE, java.lang.Byte.class, true,
83              Byte.valueOf((byte) 0)),
84          new TypeInfo("date", null, java.util.Date.class, true, null),
85          new TypeInfo("timestamp", null, java.sql.Timestamp.class, true, null),
86          new TypeInfo("sqldate", null, java.sql.Date.class, true, null),
87          new TypeInfo("sqltime", null, java.sql.Time.class, true, null),
88          new TypeInfo("short", java.lang.Short.TYPE, java.lang.Short.class, true,
89              Short.valueOf((short) 0)),
90          new TypeInfo("char", java.lang.Character.TYPE, java.lang.Character.class, true,
91              Character.valueOf((char) 0)),
92          new TypeInfo("bytes", null, byte[].class, false, null),
93          new TypeInfo("chars", null, char[].class, false, null),
94          new TypeInfo("strings", null, String[].class, false, null),
95          new TypeInfo("locale", null, java.util.Locale.class, true, null),
96          new TypeInfo("stream", null, java.io.InputStream.class, true, null),
97          new TypeInfo("clob", null, getClobClass(), true, null),
98          new TypeInfo("serializable", null, java.io.Serializable.class, false, null),
99  
100         /*
101          * Mapping for the java array of primitive type so they use the same naming encoding as
102          * array of object.
103          */
104         new TypeInfo("[Lbyte;", null, byte[].class, false, null),
105         new TypeInfo("[Lchar;", null, char[].class, false, null),
106         new TypeInfo("[Ldouble;", null, double[].class, false, null),
107         new TypeInfo("[Lfloat;", null, float[].class, false, null),
108         new TypeInfo("[Lint;", null, int[].class, false, null),
109         new TypeInfo("[Llong;", null, long[].class, false, null),
110         new TypeInfo("[Lshort;", null, int[].class, false, null),
111         new TypeInfo("[Lboolean;", null, int[].class, false, null),
112 
113         /*
114          * new TypeInfo( XML, "xml", org.w3c.dom.Document.class, org.w3c.dom.Element.class ), new
115          * TypeInfo( Serialized, "ser", java.io.Serializable.class, null )
116          */
117         new TypeInfo("duration", null, org.exolab.castor.types.Duration.class, false,
118             new Duration(0)),
119         new TypeInfo("xml-date", null, org.exolab.castor.types.Date.class, false,
120             new org.exolab.castor.types.Date(0)),
121         new TypeInfo("xml-time", null, org.exolab.castor.types.Time.class, false,
122             new org.exolab.castor.types.Time(0))
123 
124     };
125   }
126 
127   /**
128    * Returns the class name based on the supplied type name. The type name can be a short name (e.g.
129    * int, byte) or any other Java class (e.g. myapp.Product). If a short type name is used, the
130    * primitive type might be returned. If a Java class name is used, the class will be loaded and
131    * returned through the supplied class loader.
132    * 
133    * @param loader The class loader to use, may be null
134    * @param typeName The type name
135    * @return The type class
136    * @throws ClassNotFoundException The specified class could not be found
137    */
138   public static Class<?> typeFromName(ClassLoader loader, String typeName)
139       throws ClassNotFoundException {
140     for (TypeInfo typeInfo : typeInfos) {
141       if (typeName.equals(typeInfo.getShortName()))
142         return (typeInfo.getPrimitive() != null ? typeInfo.getPrimitive() : typeInfo.getJavaType());
143     }
144     if (loader != null) {
145       Class<?> aClass = Class.forName(typeName, false, loader);
146       return aClass;
147     }
148     return Class.forName(typeName);
149   }
150 
151   /**
152    * Returns the default value for this Java type (e.g. 0 for integer, empty string) or null if no
153    * default value is known. The default value only applies to primitive types (that is,
154    * <tt>Integer.TYPE</tt> but not <tt>java.lang.Integer</tt>).
155    * 
156    * @param type The Java type
157    * @return The default value or null
158    */
159   public static Object getDefault(Class<?> type) {
160     for (TypeInfo typeInfo : typeInfos) {
161       if (typeInfo.getPrimitive() == type || typeInfo.getJavaType() == type)
162         return typeInfo.getDefaultValue();
163     }
164     return null;
165   }
166 
167   /**
168    * Maps from a primitive Java type to a Java class. Returns the same class if <tt>type</tt> is not
169    * a primitive. The following conversion applies:
170    * 
171    * <pre>
172    * From            To
173    * --------------  ---------------
174    * Boolean.TYPE    Boolean.class
175    * Byte.TYPE       Byte.class
176    * Character.TYPE  Character.class
177    * Short.TYPE      Short.class
178    * Integer.TYPE    Integer.class
179    * Long.TYPE       Long.class
180    * Float.TYPE      Float.class
181    * Double.TYPE     Double.class
182    * </pre>
183    * 
184    * @param type The Java type (primitive or not)
185    * @return A comparable non-primitive Java type
186    */
187   public static Class<?> typeFromPrimitive(Class<?> type) {
188     // / Fix for arrays
189     if ((type != null) && (type.isArray()) && !type.getComponentType().isPrimitive()) {
190       return typeFromPrimitive(type.getComponentType());
191     }
192     // / end fix
193     for (TypeInfo typeInfo : typeInfos) {
194       if (typeInfo.getPrimitive() == type) {
195         return typeInfo.getJavaType();
196       }
197     }
198     return type;
199   }
200 
201   /**
202    * Returns true if the Java type is represented as a simple type. A simple can be described with a
203    * single XML attribute value, a single SQL column, a single LDAP attribute value, etc. The
204    * following types are considered simple:
205    * <ul>
206    * <li>All primitive types
207    * <li>String
208    * <li>Date
209    * <li>java.sql.Date
210    * <li>java.sql.Time
211    * <li>Timestamp
212    * <li>byte/char arrays
213    * <li>BigDecimal
214    * </ul>
215    * 
216    * @param type The Java type
217    * @return True if a simple type
218    */
219   public static boolean isSimpleType(Class<?> type) {
220     for (TypeInfo typeInfo : typeInfos) {
221       if (typeInfo.getJavaType() == type || typeInfo.getPrimitive() == type)
222         return true;
223     }
224     return false;
225   }
226 
227   /**
228    * Returns true if the Java type is represented as a primitive type. Wrapper like
229    * java.lang.Integer are considered as primitive.
230    * 
231    * @param type The Java type
232    * @return True if a primitive type
233    */
234   public static boolean isPrimitiveType(Class<?> type) {
235     for (TypeInfo typeInfo : typeInfos) {
236       if (typeInfo.getPrimitive() == type
237           || (typeInfo.getJavaType() == type && typeInfo.getPrimitive() != null)) {
238         return true;
239       }
240     }
241     return false;
242   }
243 
244   private static final Vector<Class<?>> ENUMS = new Vector<>();
245 
246   public static void addEnumType(Class<?> type) {
247     ENUMS.add(type);
248   }
249 
250   public static boolean isEnumType(Class<?> type) {
251     return ENUMS.contains(type);
252   }
253 
254   private static final Vector<Class<?>> CONVERTIBLE = new Vector<>();
255 
256   public static void addConvertibleType(Class<?> type) {
257     CONVERTIBLE.add(type);
258   }
259 
260   public static boolean isConvertibleType(Class<?> type) {
261     return CONVERTIBLE.contains(type);
262   }
263 
264   /**
265    * Constructs a new object from the given class. Does not generate any checked exceptions, since
266    * object creation has been proven to work when creating descriptor from mapping.
267    * 
268    * @param type The class type of the object instance to be constructed.
269    * @return An instance of the class type specified.
270    * @throws IllegalStateException The Java object cannot be constructed
271    */
272   public static Object newInstance(Class<?> type) throws IllegalStateException {
273     try {
274       return type.newInstance();
275     } catch (IllegalAccessException except) {
276       // This should never happen unless byte code changed all of a sudden
277       throw new IllegalStateException(
278           Messages.format("mapping.schemaNotConstructable", type.getName(), except.getMessage()));
279     } catch (InstantiationException except) {
280       // This should never happen unless byte code changed all of a sudden
281       throw new IllegalStateException(
282           Messages.format("mapping.schemaNotConstructable", type.getName(), except.getMessage()));
283     }
284   }
285 
286   /**
287    * Constructs a new object from the given class. Does not generate any checked exceptions, since
288    * object creation has been proven to work when creating descriptor from mapping.
289    * 
290    * @param type The class type of the object instance to be constructed.
291    * @param args Arguments to be supplied to constructor call.
292    * @return An instance of the class type specified.
293    * @throws IllegalStateException The Java object cannot be constructed
294    */
295   public static Object newInstance(Class<?> type, Object args[]) throws IllegalStateException {
296 
297     if ((args == null) || (args.length == 0))
298       return newInstance(type);
299 
300     try {
301       Constructor<?> cons = findConstructor(type, args);
302       return cons.newInstance(args);
303     } catch (NoSuchMethodException except) {
304       throw new IllegalStateException(
305           Messages.format("mapping.constructorNotFound", type.getName(), except.getMessage()));
306     } catch (java.lang.reflect.InvocationTargetException except) {
307       // This should never happen unless byte code changed all of a sudden
308       throw new IllegalStateException(
309           Messages.format("mapping.schemaNotConstructable", type.getName(), except.getMessage()));
310     } catch (IllegalAccessException except) {
311       // This should never happen unless byte code changed all of a sudden
312       throw new IllegalStateException(
313           Messages.format("mapping.schemaNotConstructable", type.getName(), except.getMessage()));
314     } catch (InstantiationException except) {
315       // This should never happen unless byte code changed all of a sudden
316       throw new IllegalStateException(
317           Messages.format("mapping.schemaNotConstructable", type.getName(), except.getMessage()));
318     }
319   }
320 
321   /**
322    * Returns true if the objects of this class are constructable. The class must be publicly
323    * available and have a default public constructor.
324    * 
325    * @param type The Java type
326    * @return True if constructable
327    */
328   public static boolean isConstructable(Class<?> type) {
329     return isConstructable(type, false);
330   }
331 
332   /**
333    * Returns true if the objects of this class are constructable. The class must be publicly
334    * available and have a default public constructor.
335    * 
336    * @param allowAbstractOrInterface True to indicate that abstract classes of interfaces are
337    *        allowed.
338    * @param type The Java type
339    * @return True if constructable
340    */
341   public static boolean isConstructable(Class<?> type, boolean allowAbstractOrInterface) {
342     try {
343       if ((type.getModifiers() & Modifier.PUBLIC) == 0) {
344         return false;
345       }
346       if ((!allowAbstractOrInterface)
347           && ((type.getModifiers() & (Modifier.ABSTRACT | Modifier.INTERFACE)) != 0)) {
348         return false;
349       }
350       if ((type.getConstructor(new Class[0]).getModifiers() & Modifier.PUBLIC) != 0) {
351         return true;
352       }
353     } catch (NoSuchMethodException except) {
354       // TODO [WG]: no code available
355     } catch (SecurityException except) {
356       // TODO [WG]: no code available
357     }
358     return false;
359   }
360 
361   /**
362    * Returns true if the Java type implements the {@link Serializable} interface.
363    * 
364    * @param type The Java type
365    * @return True if declared as serializable
366    */
367   public static boolean isSerializable(Class<?> type) {
368     return (Serializable.class.isAssignableFrom(type));
369   }
370 
371   /**
372    * Returns true if the Java type is immutable. Immutable objects are not copied.
373    * 
374    * @param type The Java type
375    * @return True if immutable type
376    */
377   public static boolean isImmutable(Class<?> type) {
378     for (TypeInfo typeInfo : typeInfos) {
379       if (typeInfo.getJavaType() == type || typeInfo.getPrimitive() == type)
380         return typeInfo.immutable;
381     }
382     return false;
383   }
384 
385   /**
386    * Returns true if the Java type implements the {@link Cloneable} interface.
387    * 
388    * @param type The Java type
389    * @return True if declared as cloneable
390    */
391   public static boolean isCloneable(Class<?> type) {
392     return (Cloneable.class.isAssignableFrom(type));
393   }
394 
395   /**
396    * Returns the matching constructor for the given arguments
397    * 
398    * @param type The class type of the object instance to be constructed.
399    * @param args Arguments to be supplied to constructor call.
400    * @return the matching constructor for the given arguments
401    * @throws NoSuchMethodException If no constructor with the given number/types of arguments
402    *         exists.
403    */
404   private static Constructor<?> findConstructor(Class<?> type, Object[] args)
405       throws NoSuchMethodException {
406 
407     Constructor<?>[] constructors = type.getConstructors();
408     Constructor<?> cons = null;
409     int rank = 0;
410 
411     for (int c = 0; c < constructors.length; c++) {
412       Class<?>[] paramTypes = constructors[c].getParameterTypes();
413       if (paramTypes.length != args.length)
414         continue;
415 
416       int tmpRank = 0;
417       boolean matches = true;
418       for (int p = 0; p < paramTypes.length; p++) {
419         if (args[p] == null) {
420           if (paramTypes[p].isPrimitive()) {
421             matches = false;
422             break;
423           }
424           // null matches any non primitive object
425           continue;
426         }
427 
428         // -- check direct parameter match
429         if (paramTypes[p] == args[p].getClass()) {
430           ++tmpRank;
431           continue;
432         }
433 
434         // -- check for inheritance
435         if (paramTypes[p].isAssignableFrom(args[p].getClass())) {
436           // -- good keep going, but don't increment rank
437           continue;
438         }
439         // -- check for primitive match
440         if (paramTypes[p].isPrimitive()) {
441           Class<?> pType = typeFromPrimitive(paramTypes[p]);
442           if (pType.isAssignableFrom(args[p].getClass())) {
443             // -- good keep going, but don't increment rank
444             continue;
445           }
446         }
447         matches = false;
448         break;
449       }
450 
451       if (matches) {
452         if (tmpRank == paramTypes.length)
453           return constructors[c];
454         if ((cons == null) || (tmpRank > rank)) {
455           cons = constructors[c];
456           rank = tmpRank;
457         }
458       }
459     }
460 
461     if (cons == null)
462       throw new NoSuchMethodException();
463 
464     return cons;
465   } // -- findConstructor
466 
467   /**
468    * Information about a specific Java type.
469    */
470   static class TypeInfo {
471     /** The short type name (e.g. <tt>integer</tt>). */
472     private final String shortName;
473 
474     /** The primitive Java type, if exists (e.g. <tt>Integer.TYPE</tt>). */
475     private final Class<?> primitive;
476 
477     /** The Java type (e.g. <tt>java.lang.Integer</tt>). */
478     private final Class<?> javaType;
479 
480     /** True if the type is immutable. */
481     private final boolean immutable;
482 
483     /** The default value for the type, if known. */
484     private final Object defaultValue;
485 
486     TypeInfo(final String shortName, final Class<?> primitive, final Class<?> javaType,
487         final boolean immutable, final Object defaultValue) {
488       this.shortName = shortName;
489       this.primitive = primitive;
490       this.javaType = javaType;
491       this.immutable = immutable;
492       this.defaultValue = defaultValue;
493     }
494 
495     Class<?> getJavaType() {
496       return javaType;
497     }
498 
499     String getShortName() {
500       return shortName;
501     }
502 
503     Class<?> getPrimitive() {
504       return primitive;
505     }
506 
507     boolean isImmutable() {
508       return immutable;
509     }
510 
511     Object getDefaultValue() {
512       return defaultValue;
513     }
514   }
515 
516 
517   /**
518    * A hack for JDK 1.1 Compatibility
519    * 
520    * @return A Class instance for CLOB types.
521    */
522   private static final Class<?> getClobClass() {
523     Class<?> type = null;
524     try {
525       type = Class.forName("java.sql.Clob");
526     } catch (ClassNotFoundException cnfe) {
527       // / If this is thrown we are probably in JDK 1.1, so
528       // / that's ok, otherwise there is some nasty ClassLoader problem :-)
529     }
530     return type;
531   }
532 
533 }