001: /**
002: * Redistribution and use of this software and associated documentation
003: * ("Software"), with or without modification, are permitted provided
004: * that the following conditions are met:
005: *
006: * 1. Redistributions of source code must retain copyright
007: * statements and notices. Redistributions must also contain a
008: * copy of this document.
009: *
010: * 2. Redistributions in binary form must reproduce the
011: * above copyright notice, this list of conditions and the
012: * following disclaimer in the documentation and/or other
013: * materials provided with the distribution.
014: *
015: * 3. The name "Exolab" must not be used to endorse or promote
016: * products derived from this Software without prior written
017: * permission of Intalio, Inc. For written permission,
018: * please contact info@exolab.org.
019: *
020: * 4. Products derived from this Software may not be called "Exolab"
021: * nor may "Exolab" appear in their names without prior written
022: * permission of Intalio, Inc. Exolab is a registered
023: * trademark of Intalio, Inc.
024: *
025: * 5. Due credit should be given to the Exolab Project
026: * (http://www.exolab.org/).
027: *
028: * THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS
029: * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
030: * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
031: * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
032: * INTALIO, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
033: * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
034: * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
035: * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
036: * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
037: * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
038: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
039: * OF THE POSSIBILITY OF SUCH DAMAGE.
040: *
041: * Copyright 1999-2003 (C) Intalio, Inc. All Rights Reserved.
042: *
043: * $Id: Types.java 7034 2007-06-09 14:23:25Z rjoachim $
044: */package org.exolab.castor.mapping.loader;
045:
046: import java.lang.reflect.Constructor;
047: import java.lang.reflect.Modifier;
048: import java.io.Serializable;
049: import java.math.BigDecimal;
050: import java.math.BigInteger;
051: import java.util.Vector;
052:
053: import org.castor.util.Messages;
054: import org.exolab.castor.types.Duration;
055:
056: /**
057: * Type information. Can be used to map between short type names (such
058: * as 'int') and actual Java types (java.lang.Integer), to determine
059: * whether a type is simple (i.e. maps to a single XML attribute, SQL
060: * column, etc), as well as to create a new instance of a type.
061: *
062: * @author <a href="arkin@intalio.com">Assaf Arkin</a>
063: * @version $Revision: 7034 $ $Date: 2006-04-25 15:08:23 -0600 (Tue, 25 Apr 2006) $
064: */
065: public class Types {
066: /**
067: * Returns the class name based on the supplied type name. The type name
068: * can be a short name (e.g. int, byte) or any other Java class (e.g.
069: * myapp.Product). If a short type name is used, the primitive type might
070: * be returned. If a Java class name is used, the class will be loaded and
071: * returned through the supplied class loader.
072: *
073: * @param loader The class loader to use, may be null
074: * @param typeName The type name
075: * @return The type class
076: * @throws ClassNotFoundException The specified class could not be found
077: */
078: public static Class typeFromName(ClassLoader loader, String typeName)
079: throws ClassNotFoundException {
080: for (int i = 0; i < _typeInfos.length; ++i) {
081: if (typeName.equals(_typeInfos[i].shortName))
082: return (_typeInfos[i].primitive != null ? _typeInfos[i].primitive
083: : _typeInfos[i].javaType);
084: }
085: if (loader != null) {
086: Class aClass = loader.loadClass(typeName);
087: // _log.debug ("Loaded class " + aClass);
088: return aClass;
089: }
090: return Class.forName(typeName);
091: }
092:
093: /**
094: * Returns the default value for this Java type (e.g. 0 for integer, empty
095: * string) or null if no default value is known. The default value only
096: * applies to primitive types (that is, <tt>Integer.TYPE</tt> but not
097: * <tt>java.lang.Integer</tt>).
098: *
099: * @param type The Java type
100: * @return The default value or null
101: */
102: public static Object getDefault(Class type) {
103: for (int i = 0; i < _typeInfos.length; ++i) {
104: if (_typeInfos[i].primitive == type
105: || _typeInfos[i].javaType == type)
106: return _typeInfos[i].defValue;
107: }
108: return null;
109: }
110:
111: /**
112: * Maps from a primitive Java type to a Java class. Returns the same class
113: * if <tt>type</tt> is not a primitive. The following conversion applies:
114: * <pre>
115: * From To
116: * -------------- ---------------
117: * Boolean.TYPE Boolean.class
118: * Byte.TYPE Byte.class
119: * Character.TYPE Character.class
120: * Short.TYPE Short.class
121: * Integer.TYPE Integer.class
122: * Long.TYPE Long.class
123: * Float.TYPE Float.class
124: * Double.TYPE Double.class
125: * </pre>
126: *
127: * @param type The Java type (primitive or not)
128: * @return A comparable non-primitive Java type
129: */
130: public static Class typeFromPrimitive(Class type) {
131: /// Fix for arrays
132: if ((type != null) && (type.isArray())
133: && !type.getComponentType().isPrimitive()) {
134: return typeFromPrimitive(type.getComponentType());
135: }
136: /// end fix
137: for (int i = 0; i < _typeInfos.length; ++i) {
138: if (_typeInfos[i].primitive == type)
139: return _typeInfos[i].javaType;
140: }
141: return type;
142: }
143:
144: /**
145: * Returns true if the Java type is represented as a simple type.
146: * A simple can be described with a single XML attribute value,
147: * a single SQL column, a single LDAP attribute value, etc.
148: * The following types are considered simple:
149: * <ul>
150: * <li>All primitive types
151: * <li>String
152: * <li>Date
153: * <li>java.sql.Date
154: * <li>java.sql.Time
155: * <li>Timestamp
156: * <li>byte/char arrays
157: * <li>BigDecimal
158: * </ul>
159: *
160: * @param type The Java type
161: * @return True if a simple type
162: */
163: public static boolean isSimpleType(Class type) {
164: for (int i = 0; i < _typeInfos.length; ++i) {
165: if (_typeInfos[i].javaType == type
166: || _typeInfos[i].primitive == type)
167: return true;
168: }
169: return false;
170: }
171:
172: /**
173: * Returns true if the Java type is represented as a primitive type. Wrapper
174: * like java.lang.Integer are considered as primitive.
175: *
176: * @param type The Java type
177: * @return True if a primitive type
178: */
179: public static boolean isPrimitiveType(Class type) {
180: for (int i = 0; i < _typeInfos.length; ++i) {
181: if (_typeInfos[i].primitive == type
182: || (_typeInfos[i].javaType == type && _typeInfos[i].primitive != null))
183: return true;
184: }
185: return false;
186: }
187:
188: private static final Vector _enums = new Vector();
189:
190: public static void addEnumType(Class type) {
191: _enums.add(type);
192: }
193:
194: public static boolean isEnumType(Class type) {
195: return _enums.contains(type);
196: }
197:
198: /**
199: * Constructs a new object from the given class. Does not generate any
200: * checked exceptions, since object creation has been proven to work
201: * when creating descriptor from mapping.
202: *
203: * @param type The class type of the object instance to be constructed.
204: * @return An instance of the class type specified.
205: * @throws IllegalStateException The Java object cannot be constructed
206: */
207: public static Object newInstance(Class type)
208: throws IllegalStateException {
209: try {
210: return type.newInstance();
211: } catch (IllegalAccessException except) {
212: // This should never happen unless bytecode changed all of a sudden
213: throw new IllegalStateException(Messages.format(
214: "mapping.schemaNotConstructable", type.getName(),
215: except.getMessage()));
216: } catch (InstantiationException except) {
217: // This should never happen unless bytecode changed all of a sudden
218: throw new IllegalStateException(Messages.format(
219: "mapping.schemaNotConstructable", type.getName(),
220: except.getMessage()));
221: }
222: }
223:
224: /**
225: * Constructs a new object from the given class. Does not generate any
226: * checked exceptions, since object creation has been proven to work
227: * when creating descriptor from mapping.
228: *
229: * @param type The class type of the object instance to be constructed.
230: * @param args Arguments to be supplied to constructor call.
231: * @return An instance of the class type specified.
232: * @throws IllegalStateException The Java object cannot be constructed
233: */
234: public static Object newInstance(Class type, Object args[])
235: throws IllegalStateException {
236:
237: if ((args == null) || (args.length == 0))
238: return newInstance(type);
239:
240: try {
241: Constructor cons = findConstructor(type, args);
242: return cons.newInstance(args);
243: } catch (NoSuchMethodException except) {
244: throw new IllegalStateException(Messages.format(
245: "mapping.constructorNotFound", type.getName(),
246: except.getMessage()));
247: } catch (java.lang.reflect.InvocationTargetException except) {
248: // This should never happen unless bytecode changed all of a sudden
249: throw new IllegalStateException(Messages.format(
250: "mapping.schemaNotConstructable", type.getName(),
251: except.getMessage()));
252: } catch (IllegalAccessException except) {
253: // This should never happen unless bytecode changed all of a sudden
254: throw new IllegalStateException(Messages.format(
255: "mapping.schemaNotConstructable", type.getName(),
256: except.getMessage()));
257: } catch (InstantiationException except) {
258: // This should never happen unless bytecode changed all of a sudden
259: throw new IllegalStateException(Messages.format(
260: "mapping.schemaNotConstructable", type.getName(),
261: except.getMessage()));
262: }
263: }
264:
265: /**
266: * Returns true if the objects of this class are constructable.
267: * The class must be publicly available and have a default public
268: * constructor.
269: *
270: * @param type The Java type
271: * @return True if constructable
272: */
273: public static boolean isConstructable(Class type) {
274: return isConstructable(type, false);
275: }
276:
277: /**
278: * Returns true if the objects of this class are constructable.
279: * The class must be publicly available and have a default public
280: * constructor.
281: *
282: * @param allowAbstractOrInterface True to indicate that abstract classes of interfaces are allowed.
283: * @param type The Java type
284: * @return True if constructable
285: */
286: public static boolean isConstructable(Class type,
287: boolean allowAbstractOrInterface) {
288: try {
289: if ((type.getModifiers() & Modifier.PUBLIC) == 0)
290: return false;
291: if ((!allowAbstractOrInterface)
292: && ((type.getModifiers() & (Modifier.ABSTRACT | Modifier.INTERFACE)) != 0))
293: return false;
294: if ((type.getConstructor(new Class[0]).getModifiers() & Modifier.PUBLIC) != 0)
295: return true;
296: } catch (NoSuchMethodException except) {
297: // TODO [WG]: no code available
298: } catch (SecurityException except) {
299: // TODO [WG]: no code available
300: }
301: return false;
302: }
303:
304: /**
305: * Returns true if the Java type implements the {@link Serializable}
306: * interface.
307: *
308: * @param type The Java type
309: * @return True if declared as serializable
310: */
311: public static boolean isSerializable(Class type) {
312: return (Serializable.class.isAssignableFrom(type));
313: }
314:
315: /**
316: * Returns true if the Java type is immutable. Immutable objects are
317: * not copied.
318: *
319: * @param type The Java type
320: * @return True if immutable type
321: */
322: public static boolean isImmutable(Class type) {
323: for (int i = 0; i < _typeInfos.length; ++i) {
324: if (_typeInfos[i].javaType == type
325: || _typeInfos[i].primitive == type)
326: return _typeInfos[i].immutable;
327: }
328: return false;
329: }
330:
331: /**
332: * Returns true if the Java type implements the {@link Cloneable}
333: * interface.
334: *
335: * @param type The Java type
336: * @return True if declared as cloneable
337: */
338: public static boolean isCloneable(Class type) {
339: return (Cloneable.class.isAssignableFrom(type));
340: }
341:
342: /**
343: * Returns the matching constructor for the given arguments
344: *
345: * @param type The class type of the object instance to be constructed.
346: * @param args Arguments to be supplied to constructor call.
347: * @return the matching constructor for the given arguments
348: * @throws NoSuchMethodException If no constructor with the given number/types of arguments exists.
349: */
350: private static Constructor findConstructor(Class type, Object[] args)
351: throws NoSuchMethodException {
352:
353: Constructor[] constructors = type.getConstructors();
354: Constructor cons = null;
355: int rank = 0;
356:
357: for (int c = 0; c < constructors.length; c++) {
358: Class[] paramTypes = constructors[c].getParameterTypes();
359: if (paramTypes.length != args.length)
360: continue;
361:
362: int tmpRank = 0;
363: boolean matches = true;
364: for (int p = 0; p < paramTypes.length; p++) {
365: if (args[p] == null) {
366: if (paramTypes[p].isPrimitive()) {
367: matches = false;
368: break;
369: }
370: // null matches any non primitive object
371: continue;
372: }
373:
374: //-- check direct param match
375: if (paramTypes[p] == args[p].getClass()) {
376: ++tmpRank;
377: continue;
378: }
379:
380: //-- check for inheritence
381: if (paramTypes[p].isAssignableFrom(args[p].getClass())) {
382: //-- good keep going, but don't increment rank
383: continue;
384: }
385: //-- check for primitive match
386: if (paramTypes[p].isPrimitive()) {
387: Class pType = typeFromPrimitive(paramTypes[p]);
388: if (pType.isAssignableFrom(args[p].getClass())) {
389: //-- good keep going, but don't increment rank
390: continue;
391: }
392: }
393: matches = false;
394: break;
395: }
396:
397: if (matches) {
398: if (tmpRank == paramTypes.length)
399: return constructors[c];
400: if ((cons == null) || (tmpRank > rank)) {
401: cons = constructors[c];
402: rank = tmpRank;
403: }
404: }
405: }
406:
407: if (cons == null)
408: throw new NoSuchMethodException();
409:
410: return cons;
411: } //-- findConstructor
412:
413: /**
414: * Information about a specific Java type.
415: */
416: static class TypeInfo {
417:
418: /**
419: * The short type name (e.g. <tt>integer</tt>).
420: */
421: final String shortName;
422:
423: /**
424: * The primitive Java type, if exists (e.g. <tt>Integer.TYPE</tt>).
425: */
426: final Class primitive;
427:
428: /**
429: * The Java type (e.g. <tt>java.lang.Integer</tt>).
430: */
431: final Class javaType;
432:
433: /**
434: * True if the type is immutable.
435: */
436: final boolean immutable;
437:
438: /**
439: * The default value for the type, if known.
440: */
441: final Object defValue;
442:
443: TypeInfo(String shortName, Class primitive, Class javaType,
444: boolean immutable, Object defValue) {
445: this .shortName = shortName;
446: this .primitive = primitive;
447: this .javaType = javaType;
448: this .immutable = immutable;
449: this .defValue = defValue;
450: }
451:
452: }
453:
454: /**
455: * List of all the simple types supported by Castor.
456: */
457: static TypeInfo[] _typeInfos = new TypeInfo[] {
458: // shortName primitive
459: // javaType immutable defValue
460: new TypeInfo("other", null, java.lang.Object.class, false,
461: null),
462: new TypeInfo("string", null, java.lang.String.class, true,
463: null),
464: new TypeInfo("integer", java.lang.Integer.TYPE,
465: java.lang.Integer.class, true, new Integer(0)),
466: new TypeInfo("int", java.lang.Integer.TYPE,
467: java.lang.Integer.TYPE, true, new Integer(0)),
468: new TypeInfo("long", java.lang.Long.TYPE,
469: java.lang.Long.class, true, new Long(0L)),
470: new TypeInfo("big-integer", null,
471: java.math.BigInteger.class, true, BigInteger
472: .valueOf(0)),
473: new TypeInfo("boolean", java.lang.Boolean.TYPE,
474: java.lang.Boolean.class, true, Boolean.FALSE),
475: new TypeInfo("double", java.lang.Double.TYPE,
476: java.lang.Double.class, true, new Double(0.0)),
477: new TypeInfo("float", java.lang.Float.TYPE,
478: java.lang.Float.class, true, new Float(0.0f)),
479: new TypeInfo("big-decimal", null,
480: java.math.BigDecimal.class, true, new BigDecimal(
481: 0.0)),
482: new TypeInfo("byte", java.lang.Byte.TYPE,
483: java.lang.Byte.class, true, new Byte((byte) 0)),
484: new TypeInfo("date", null, java.util.Date.class, true, null),
485: new TypeInfo("timestamp", null, java.sql.Timestamp.class,
486: true, null),
487: new TypeInfo("sqldate", null, java.sql.Date.class, true,
488: null),
489: new TypeInfo("sqltime", null, java.sql.Time.class, true,
490: null),
491: new TypeInfo("short", java.lang.Short.TYPE,
492: java.lang.Short.class, true, new Short((short) 0)),
493: new TypeInfo("char", java.lang.Character.TYPE,
494: java.lang.Character.class, true, new Character(
495: (char) 0)),
496: new TypeInfo("bytes", null, byte[].class, false, null),
497: new TypeInfo("chars", null, char[].class, false, null),
498: new TypeInfo("strings", null, String[].class, false, null),
499: new TypeInfo("locale", null, java.util.Locale.class, true,
500: null),
501: new TypeInfo("stream", null, java.io.InputStream.class,
502: true, null),
503: new TypeInfo("clob", null, getClobClass(), true, null),
504: new TypeInfo("serializable", null,
505: java.io.Serializable.class, false, null),
506:
507: /* Mapping for the java array of primitive type so they use the same
508: * naming encoding as array of object.
509: */
510: new TypeInfo("[Lbyte;", null, byte[].class, false, null),
511: new TypeInfo("[Lchar;", null, char[].class, false, null),
512: new TypeInfo("[Ldouble;", null, double[].class, false, null),
513: new TypeInfo("[Lfloat;", null, float[].class, false, null),
514: new TypeInfo("[Lint;", null, int[].class, false, null),
515: new TypeInfo("[Llong;", null, long[].class, false, null),
516: new TypeInfo("[Lshort;", null, int[].class, false, null),
517: new TypeInfo("[Lboolean;", null, int[].class, false, null),
518:
519: /*
520: new TypeInfo( XML, "xml", org.w3c.dom.Document.class, org.w3c.dom.Element.class ),
521: new TypeInfo( Serialized, "ser", java.io.Serializable.class, null )
522: */
523: new TypeInfo("duration", null,
524: org.exolab.castor.types.Duration.class, false,
525: new Duration(0)),
526: new TypeInfo("xml-date", null,
527: org.exolab.castor.types.Date.class, false,
528: new org.exolab.castor.types.Date(0)),
529: new TypeInfo("xml-time", null,
530: org.exolab.castor.types.Time.class, false,
531: new org.exolab.castor.types.Time(0))
532:
533: };
534:
535: /**
536: * A hack for JDK 1.1 Compatibility
537: * @return A Class instance for CLOB types.
538: */
539: private static final Class getClobClass() {
540: Class type = null;
541: try {
542: type = Class.forName("java.sql.Clob");
543: } catch (ClassNotFoundException cnfe) {
544: /// If this is thrown we are probably in JDK 1.1, so
545: /// that's ok, otherwise there is some nasty ClassLoader problem :-)
546: }
547: return type;
548: } //-- getClobClass
549:
550: }
|