ClassFactory.java revision 585:f4dce4ccb00f
1246149Ssjg/*
2246149Ssjg * Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved.
3246149Ssjg * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4246149Ssjg *
5246149Ssjg * This code is free software; you can redistribute it and/or modify it
6246149Ssjg * under the terms of the GNU General Public License version 2 only, as
7246149Ssjg * published by the Free Software Foundation.  Oracle designates this
8246149Ssjg * particular file as subject to the "Classpath" exception as provided
9246149Ssjg * by Oracle in the LICENSE file that accompanied this code.
10246149Ssjg *
11246149Ssjg * This code is distributed in the hope that it will be useful, but WITHOUT
12246149Ssjg * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13246149Ssjg * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14246149Ssjg * version 2 for more details (a copy is included in the LICENSE file that
15246149Ssjg * accompanied this code).
16246149Ssjg *
17246149Ssjg * You should have received a copy of the GNU General Public License version
18246149Ssjg * 2 along with this work; if not, write to the Free Software Foundation,
19246149Ssjg * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20246149Ssjg *
21246149Ssjg * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22246149Ssjg * or visit www.oracle.com if you need additional information or have any
23246149Ssjg * questions.
24246149Ssjg */
25246149Ssjg
26246149Ssjgpackage com.sun.xml.internal.bind.v2;
27246149Ssjg
28246149Ssjgimport java.lang.reflect.Constructor;
29246149Ssjgimport java.lang.reflect.InvocationTargetException;
30246149Ssjgimport java.lang.reflect.Method;
31246149Ssjgimport java.lang.reflect.Modifier;
32246149Ssjgimport java.lang.ref.WeakReference;
33246149Ssjgimport java.security.AccessController;
34246149Ssjgimport java.security.PrivilegedAction;
35246149Ssjgimport java.util.Map;
36246149Ssjgimport java.util.WeakHashMap;
37246149Ssjgimport java.util.logging.Level;
38246149Ssjgimport java.util.logging.Logger;
39246149Ssjg
40246149Ssjgimport com.sun.xml.internal.bind.Util;
41246149Ssjg
42246149Ssjg/**
43246149Ssjg * Creates new instances of classes.
44246149Ssjg *
45246149Ssjg * <p>
46246149Ssjg * This code handles the case where the class is not public or the constructor is
47246149Ssjg * not public.
48246149Ssjg *
49246149Ssjg * @since 2.0
50246149Ssjg * @author Kohsuke Kawaguchi
51246149Ssjg */
52246149Ssjgpublic 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