ClassFactory.java revision 573:ba8cbdef7c26
1/*
2 * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.xml.internal.bind.v2;
27
28import java.lang.reflect.Constructor;
29import java.lang.reflect.InvocationTargetException;
30import java.lang.reflect.Method;
31import java.lang.reflect.Modifier;
32import java.lang.ref.WeakReference;
33import java.security.AccessController;
34import java.security.PrivilegedAction;
35import java.util.Map;
36import java.util.WeakHashMap;
37import java.util.logging.Level;
38import java.util.logging.Logger;
39
40import com.sun.xml.internal.bind.Util;
41
42/**
43 * Creates new instances of classes.
44 *
45 * <p>
46 * This code handles the case where the class is not public or the constructor is
47 * not public.
48 *
49 * @since 2.0
50 * @author Kohsuke Kawaguchi
51 */
52public final class ClassFactory {
53    private static final Class[] emptyClass = new Class[0];
54    private static final Object[] emptyObject = new Object[0];
55
56    private static final Logger logger = Util.getClassLogger();
57
58    /**
59     * Cache from a class to its default constructor.
60     *
61     * To avoid synchronization among threads, we use {@link ThreadLocal}.
62     */
63    private static final ThreadLocal<Map<Class, WeakReference<Constructor>>> tls = new ThreadLocal<Map<Class,WeakReference<Constructor>>>() {
64        @Override
65        public Map<Class,WeakReference<Constructor>> initialValue() {
66            return new WeakHashMap<Class,WeakReference<Constructor>>();
67        }
68    };
69
70    public static void cleanCache() {
71        if (tls != null) {
72            try {
73                tls.remove();
74            } catch (Exception e) {
75                logger.log(Level.WARNING, "Unable to clean Thread Local cache of classes used in Unmarshaller: {0}", e.getLocalizedMessage());
76            }
77        }
78    }
79
80    /**
81     * Creates a new instance of the class but throw exceptions without catching it.
82     */
83    public static <T> T create0( final Class<T> clazz ) throws IllegalAccessException, InvocationTargetException, InstantiationException {
84        Map<Class,WeakReference<Constructor>> m = tls.get();
85        Constructor<T> cons = null;
86        WeakReference<Constructor> consRef = m.get(clazz);
87        if(consRef!=null)
88            cons = consRef.get();
89        if(cons==null) {
90            cons = AccessController.doPrivileged(new PrivilegedAction<Constructor<T>>() {
91                @Override
92                public Constructor<T> run() {
93                    try {
94                        return clazz.getDeclaredConstructor(emptyClass);
95                    } catch (NoSuchMethodException e) {
96                        logger.log(Level.INFO,"No default constructor found on "+clazz,e);
97                        NoSuchMethodError exp;
98                        if(clazz.getDeclaringClass()!=null && !Modifier.isStatic(clazz.getModifiers())) {
99                            exp = new NoSuchMethodError(Messages.NO_DEFAULT_CONSTRUCTOR_IN_INNER_CLASS
100                                                                .format(clazz.getName()));
101                        } else {
102                            exp = new NoSuchMethodError(e.getMessage());
103                        }
104                        exp.initCause(e);
105                        throw exp;
106                    }
107                }
108            });
109
110            int classMod = clazz.getModifiers();
111
112            if(!Modifier.isPublic(classMod) || !Modifier.isPublic(cons.getModifiers())) {
113                // attempt to make it work even if the constructor is not accessible
114                try {
115                    cons.setAccessible(true);
116                } catch(SecurityException e) {
117                    // but if we don't have a permission to do so, work gracefully.
118                    logger.log(Level.FINE,"Unable to make the constructor of "+clazz+" accessible",e);
119                    throw e;
120                }
121            }
122
123            m.put(clazz,new WeakReference<Constructor>(cons));
124        }
125
126        return cons.newInstance(emptyObject);
127    }
128
129    /**
130     * The same as {@link #create0} but with an error handling to make
131     * the instantiation error fatal.
132     */
133    public static <T> T create( Class<T> clazz ) {
134        try {
135            return create0(clazz);
136        } catch (InstantiationException e) {
137            logger.log(Level.INFO,"failed to create a new instance of "+clazz,e);
138            throw new InstantiationError(e.toString());
139        } catch (IllegalAccessException e) {
140            logger.log(Level.INFO,"failed to create a new instance of "+clazz,e);
141            throw new IllegalAccessError(e.toString());
142        } catch (InvocationTargetException e) {
143            Throwable target = e.getTargetException();
144
145            // most likely an error on the user's code.
146            // just let it through for the ease of debugging
147            if(target instanceof RuntimeException)
148                throw (RuntimeException)target;
149
150            // error. just forward it for the ease of debugging
151            if(target instanceof Error)
152                throw (Error)target;
153
154            // a checked exception.
155            // not sure how we should report this error,
156            // but for now we just forward it by wrapping it into a runtime exception
157            throw new IllegalStateException(target);
158        }
159    }
160
161    /**
162     *  Call a method in the factory class to get the object.
163     */
164    public static Object create(Method method) {
165        Throwable errorMsg;
166        try {
167            return method.invoke(null, emptyObject);
168        } catch (InvocationTargetException ive) {
169            Throwable target = ive.getTargetException();
170
171            if(target instanceof RuntimeException)
172                throw (RuntimeException)target;
173
174            if(target instanceof Error)
175                throw (Error)target;
176
177            throw new IllegalStateException(target);
178        } catch (IllegalAccessException e) {
179            logger.log(Level.INFO,"failed to create a new instance of "+method.getReturnType().getName(),e);
180            throw new IllegalAccessError(e.toString());
181        } catch (IllegalArgumentException iae){
182            logger.log(Level.INFO,"failed to create a new instance of "+method.getReturnType().getName(),iae);
183            errorMsg = iae;
184        } catch (NullPointerException npe){
185            logger.log(Level.INFO,"failed to create a new instance of "+method.getReturnType().getName(),npe);
186            errorMsg = npe;
187        } catch (ExceptionInInitializerError eie){
188            logger.log(Level.INFO,"failed to create a new instance of "+method.getReturnType().getName(),eie);
189            errorMsg = eie;
190        }
191
192        NoSuchMethodError exp;
193        exp = new NoSuchMethodError(errorMsg.getMessage());
194        exp.initCause(errorMsg);
195        throw exp;
196    }
197
198    /**
199     * Infers the instanciable implementation class that can be assigned to the given field type.
200     *
201     * @return null
202     *      if inference fails.
203     */
204    public static <T> Class<? extends T> inferImplClass(Class<T> fieldType, Class[] knownImplClasses) {
205        if(!fieldType.isInterface())
206            return fieldType;
207
208        for( Class<?> impl : knownImplClasses ) {
209            if(fieldType.isAssignableFrom(impl))
210                return impl.asSubclass(fieldType);
211        }
212
213        // if we can't find an implementation class,
214        // let's just hope that we will never need to create a new object,
215        // and returns null
216        return null;
217    }
218}
219