001: /*
002: * Copyright 2006 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package com.sun.xml.internal.bind.v2;
027:
028: import java.lang.reflect.Constructor;
029: import java.lang.reflect.InvocationTargetException;
030: import java.lang.reflect.Method;
031: import java.lang.reflect.Modifier;
032: import java.util.ArrayList;
033: import java.util.HashSet;
034: import java.util.LinkedList;
035: import java.util.Map;
036: import java.util.Stack;
037: import java.util.TreeSet;
038: import java.util.WeakHashMap;
039: import java.util.logging.Level;
040: import java.util.logging.Logger;
041:
042: import com.sun.xml.internal.bind.Util;
043:
044: /**
045: * Creates new instances of classes.
046: *
047: * <p>
048: * This code handles the case where the class is not public or the constructor is
049: * not public.
050: *
051: * @since 2.0
052: * @author Kohsuke Kawaguchi
053: */
054: public final class ClassFactory {
055: private static final Class[] emptyClass = new Class[0];
056: private static final Object[] emptyObject = new Object[0];
057:
058: private static final Logger logger = Util.getClassLogger();
059:
060: /**
061: * Cache from a class to its default constructor.
062: *
063: * To avoid synchronization among threads, we use {@link ThreadLocal}.
064: */
065: private static final ThreadLocal<Map<Class, Constructor>> tls = new ThreadLocal<Map<Class, Constructor>>() {
066: public Map<Class, Constructor> initialValue() {
067: return new WeakHashMap<Class, Constructor>();
068: }
069: };
070:
071: /**
072: * Creates a new instance of the class but throw exceptions without catching it.
073: */
074: public static <T> T create0(final Class<T> clazz)
075: throws IllegalAccessException, InvocationTargetException,
076: InstantiationException {
077: Map<Class, Constructor> m = tls.get();
078: Constructor<T> cons = m.get(clazz);
079: if (cons == null) {
080: try {
081: cons = clazz.getDeclaredConstructor(emptyClass);
082: } catch (NoSuchMethodException e) {
083: logger.log(Level.INFO,
084: "No default constructor found on " + clazz, e);
085: NoSuchMethodError exp;
086: if (clazz.getDeclaringClass() != null
087: && !Modifier.isStatic(clazz.getModifiers())) {
088: exp = new NoSuchMethodError(
089: Messages.NO_DEFAULT_CONSTRUCTOR_IN_INNER_CLASS
090: .format(clazz.getName()));
091: } else {
092: exp = new NoSuchMethodError(e.getMessage());
093: }
094: exp.initCause(e);
095: throw exp;
096: }
097:
098: int classMod = clazz.getModifiers();
099:
100: if (!Modifier.isPublic(classMod)
101: || !Modifier.isPublic(cons.getModifiers())) {
102: // attempt to make it work even if the constructor is not accessible
103: try {
104: cons.setAccessible(true);
105: } catch (SecurityException e) {
106: // but if we don't have a permission to do so, work gracefully.
107: logger.log(Level.FINE,
108: "Unable to make the constructor of "
109: + clazz + " accessible", e);
110: throw e;
111: }
112: }
113:
114: m.put(clazz, cons);
115: }
116:
117: return cons.newInstance(emptyObject);
118: }
119:
120: /**
121: * The same as {@link #create0} but with an error handling to make
122: * the instanciation error fatal.
123: */
124: public static <T> T create(Class<T> clazz) {
125: try {
126: return create0(clazz);
127: } catch (InstantiationException e) {
128: logger.log(Level.INFO,
129: "failed to create a new instance of " + clazz, e);
130: throw new InstantiationError(e.toString());
131: } catch (IllegalAccessException e) {
132: logger.log(Level.INFO,
133: "failed to create a new instance of " + clazz, e);
134: throw new IllegalAccessError(e.toString());
135: } catch (InvocationTargetException e) {
136: Throwable target = e.getTargetException();
137:
138: // most likely an error on the user's code.
139: // just let it through for the ease of debugging
140: if (target instanceof RuntimeException)
141: throw (RuntimeException) target;
142:
143: // error. just forward it for the ease of debugging
144: if (target instanceof Error)
145: throw (Error) target;
146:
147: // a checked exception.
148: // not sure how we should report this error,
149: // but for now we just forward it by wrapping it into a runtime exception
150: throw new IllegalStateException(target);
151: }
152: }
153:
154: /**
155: * Call a method in the factory class to get the object.
156: */
157: public static Object create(final Method method) {
158: Object cons = null;
159: Throwable errorMsg = null;
160: try {
161: cons = method.invoke(null, emptyObject);
162: } catch (InvocationTargetException ive) {
163: Throwable target = ive.getTargetException();
164:
165: if (target instanceof RuntimeException)
166: throw (RuntimeException) target;
167:
168: if (target instanceof Error)
169: throw (Error) target;
170:
171: throw new IllegalStateException(target);
172: } catch (IllegalAccessException e) {
173: logger.log(Level.INFO,
174: "failed to create a new instance of "
175: + method.getReturnType().getName(), e);
176: throw new IllegalAccessError(e.toString());
177: } catch (IllegalArgumentException iae) {
178: logger.log(Level.INFO,
179: "failed to create a new instance of "
180: + method.getReturnType().getName(), iae);
181: errorMsg = iae;
182: } catch (NullPointerException npe) {
183: logger.log(Level.INFO,
184: "failed to create a new instance of "
185: + method.getReturnType().getName(), npe);
186: errorMsg = npe;
187: } catch (ExceptionInInitializerError eie) {
188: logger.log(Level.INFO,
189: "failed to create a new instance of "
190: + method.getReturnType().getName(), eie);
191: errorMsg = eie;
192: }
193: if (errorMsg != null) {
194: NoSuchMethodError exp;
195: exp = new NoSuchMethodError(errorMsg.getMessage());
196: exp.initCause(errorMsg);
197: throw exp;
198: }
199: return cons;
200: }
201:
202: /**
203: * Infers the instanciable implementation class that can be assigned to the given field type.
204: *
205: * @return null
206: * if inference fails.
207: */
208: public static <T> Class<? extends T> inferImplClass(
209: Class<T> fieldType, Class[] knownImplClasses) {
210: if (!fieldType.isInterface())
211: // instanciable class?
212: // TODO: check if it has a default constructor
213: return fieldType;
214:
215: for (Class<?> impl : knownImplClasses) {
216: if (fieldType.isAssignableFrom(impl))
217: return impl.asSubclass(fieldType);
218: }
219:
220: // if we can't find an implementation class,
221: // let's just hope that we will never need to create a new object,
222: // and returns null
223: return null;
224: }
225:
226: public static final Class[] COLLECTION_IMPL_CLASSES = new Class[] {
227: ArrayList.class, LinkedList.class, HashSet.class,
228: TreeSet.class, Stack.class, };
229: }
|