1/*
2 * Copyright (c) 1997, 2011, 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            if (System.getSecurityManager() == null) {
91                cons = tryGetDeclaredConstructor(clazz);
92            } else {
93                cons = AccessController.doPrivileged(new PrivilegedAction<Constructor<T>>() {
94                    @Override
95                    public Constructor<T> run() {
96                        return tryGetDeclaredConstructor(clazz);
97                    }
98                });
99            }
100
101            int classMod = clazz.getModifiers();
102
103            if(!Modifier.isPublic(classMod) || !Modifier.isPublic(cons.getModifiers())) {
104                // attempt to make it work even if the constructor is not accessible
105                try {
106                    cons.setAccessible(true);
107                } catch(SecurityException e) {
108                    // but if we don't have a permission to do so, work gracefully.
109                    logger.log(Level.FINE,"Unable to make the constructor of "+clazz+" accessible",e);
110                    throw e;
111                }
112            }
113
114            m.put(clazz,new WeakReference<Constructor>(cons));
115        }
116        return cons.newInstance(emptyObject);
117    }
118
119    private static <T> Constructor<T> tryGetDeclaredConstructor(Class<T> clazz) {
120        try {
121            return clazz.getDeclaredConstructor((Class<T>[])emptyClass);
122        } catch (NoSuchMethodException e) {
123            logger.log(Level.INFO,"No default constructor found on "+clazz,e);
124            NoSuchMethodError exp;
125            if(clazz.getDeclaringClass()!=null && !Modifier.isStatic(clazz.getModifiers())) {
126                exp = new NoSuchMethodError(Messages.NO_DEFAULT_CONSTRUCTOR_IN_INNER_CLASS
127                                                    .format(clazz.getName()));
128            } else {
129                exp = new NoSuchMethodError(e.getMessage());
130            }
131            exp.initCause(e);
132            throw exp;
133        }
134    }
135
136    /**
137     * The same as {@link #create0} but with an error handling to make
138     * the instantiation error fatal.
139     */
140    public static <T> T create( Class<T> clazz ) {
141        try {
142            return create0(clazz);
143        } catch (InstantiationException e) {
144            logger.log(Level.INFO,"failed to create a new instance of "+clazz,e);
145            throw new InstantiationError(e.toString());
146        } catch (IllegalAccessException e) {
147            logger.log(Level.INFO,"failed to create a new instance of "+clazz,e);
148            throw new IllegalAccessError(e.toString());
149        } catch (InvocationTargetException e) {
150            Throwable target = e.getTargetException();
151
152            // most likely an error on the user's code.
153            // just let it through for the ease of debugging
154            if(target instanceof RuntimeException)
155                throw (RuntimeException)target;
156
157            // error. just forward it for the ease of debugging
158            if(target instanceof Error)
159                throw (Error)target;
160
161            // a checked exception.
162            // not sure how we should report this error,
163            // but for now we just forward it by wrapping it into a runtime exception
164            throw new IllegalStateException(target);
165        }
166    }
167
168    /**
169     *  Call a method in the factory class to get the object.
170     */
171    public static Object create(Method method) {
172        Throwable errorMsg;
173        try {
174            return method.invoke(null, emptyObject);
175        } catch (InvocationTargetException ive) {
176            Throwable target = ive.getTargetException();
177
178            if(target instanceof RuntimeException)
179                throw (RuntimeException)target;
180
181            if(target instanceof Error)
182                throw (Error)target;
183
184            throw new IllegalStateException(target);
185        } catch (IllegalAccessException e) {
186            logger.log(Level.INFO,"failed to create a new instance of "+method.getReturnType().getName(),e);
187            throw new IllegalAccessError(e.toString());
188        } catch (IllegalArgumentException iae){
189            logger.log(Level.INFO,"failed to create a new instance of "+method.getReturnType().getName(),iae);
190            errorMsg = iae;
191        } catch (NullPointerException npe){
192            logger.log(Level.INFO,"failed to create a new instance of "+method.getReturnType().getName(),npe);
193            errorMsg = npe;
194        } catch (ExceptionInInitializerError eie){
195            logger.log(Level.INFO,"failed to create a new instance of "+method.getReturnType().getName(),eie);
196            errorMsg = eie;
197        }
198
199        NoSuchMethodError exp;
200        exp = new NoSuchMethodError(errorMsg.getMessage());
201        exp.initCause(errorMsg);
202        throw exp;
203    }
204
205    /**
206     * Infers the instanciable implementation class that can be assigned to the given field type.
207     *
208     * @return null
209     *      if inference fails.
210     */
211    public static <T> Class<? extends T> inferImplClass(Class<T> fieldType, Class[] knownImplClasses) {
212        if(!fieldType.isInterface())
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