AbstractClassGenerator.java revision 674060f01e9090cd21b3c5656cc3204912ad17a6
1/*
2 * Copyright 2003,2004 The Apache Software Foundation
3 *
4 *  Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 *  Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package org.mockito.cglib.core;
17
18import java.io.*;
19import java.util.*;
20import java.lang.ref.*;
21
22import org.mockito.asm.ClassReader;
23import org.mockito.asm.ClassVisitor;
24import org.mockito.asm.ClassWriter;
25import org.mockito.asm.Type;
26
27/**
28 * Abstract class for all code-generating CGLIB utilities.
29 * In addition to caching generated classes for performance, it provides hooks for
30 * customizing the <code>ClassLoader</code>, name of the generated class, and transformations
31 * applied before generation.
32 */
33abstract public class AbstractClassGenerator
34implements ClassGenerator
35{
36    private static final Object NAME_KEY = new Object();
37    private static final ThreadLocal CURRENT = new ThreadLocal();
38
39    private GeneratorStrategy strategy = DefaultGeneratorStrategy.INSTANCE;
40    private NamingPolicy namingPolicy = DefaultNamingPolicy.INSTANCE;
41    private Source source;
42    private ClassLoader classLoader;
43    private String namePrefix;
44    private Object key;
45    private boolean useCache = true;
46    private String className;
47    private boolean attemptLoad;
48
49    protected static class Source {
50        String name;
51        Map cache = new WeakHashMap();
52        public Source(String name) {
53            this.name = name;
54        }
55    }
56
57    protected AbstractClassGenerator(Source source) {
58        this.source = source;
59    }
60
61    protected void setNamePrefix(String namePrefix) {
62        this.namePrefix = namePrefix;
63    }
64
65    final protected String getClassName() {
66        if (className == null)
67            className = getClassName(getClassLoader());
68        return className;
69    }
70
71    private String getClassName(final ClassLoader loader) {
72        final Set nameCache = getClassNameCache(loader);
73        return namingPolicy.getClassName(namePrefix, source.name, key, new Predicate() {
74            public boolean evaluate(Object arg) {
75                return nameCache.contains(arg);
76            }
77        });
78    }
79
80    private Set getClassNameCache(ClassLoader loader) {
81        return (Set)((Map)source.cache.get(loader)).get(NAME_KEY);
82    }
83
84    /**
85     * Set the <code>ClassLoader</code> in which the class will be generated.
86     * Concrete subclasses of <code>AbstractClassGenerator</code> (such as <code>Enhancer</code>)
87     * will try to choose an appropriate default if this is unset.
88     * <p>
89     * Classes are cached per-<code>ClassLoader</code> using a <code>WeakHashMap</code>, to allow
90     * the generated classes to be removed when the associated loader is garbage collected.
91     * @param classLoader the loader to generate the new class with, or null to use the default
92     */
93    public void setClassLoader(ClassLoader classLoader) {
94        this.classLoader = classLoader;
95    }
96
97    /**
98     * Override the default naming policy.
99     * @see DefaultNamingPolicy
100     * @param namingPolicy the custom policy, or null to use the default
101     */
102    public void setNamingPolicy(NamingPolicy namingPolicy) {
103        if (namingPolicy == null)
104            namingPolicy = DefaultNamingPolicy.INSTANCE;
105        this.namingPolicy = namingPolicy;
106    }
107
108    /**
109     * @see #setNamingPolicy
110     */
111    public NamingPolicy getNamingPolicy() {
112        return namingPolicy;
113    }
114
115    /**
116     * Whether use and update the static cache of generated classes
117     * for a class with the same properties. Default is <code>true</code>.
118     */
119    public void setUseCache(boolean useCache) {
120        this.useCache = useCache;
121    }
122
123    /**
124     * @see #setUseCache
125     */
126    public boolean getUseCache() {
127        return useCache;
128    }
129
130    /**
131     * If set, CGLIB will attempt to load classes from the specified
132     * <code>ClassLoader</code> before generating them. Because generated
133     * class names are not guaranteed to be unique, the default is <code>false</code>.
134     */
135    public void setAttemptLoad(boolean attemptLoad) {
136        this.attemptLoad = attemptLoad;
137    }
138
139    public boolean getAttemptLoad() {
140        return attemptLoad;
141    }
142
143    /**
144     * Set the strategy to use to create the bytecode from this generator.
145     * By default an instance of {@see DefaultGeneratorStrategy} is used.
146     */
147    public void setStrategy(GeneratorStrategy strategy) {
148        if (strategy == null)
149            strategy = DefaultGeneratorStrategy.INSTANCE;
150        this.strategy = strategy;
151    }
152
153    /**
154     * @see #setStrategy
155     */
156    public GeneratorStrategy getStrategy() {
157        return strategy;
158    }
159
160    /**
161     * Used internally by CGLIB. Returns the <code>AbstractClassGenerator</code>
162     * that is being used to generate a class in the current thread.
163     */
164    public static AbstractClassGenerator getCurrent() {
165        return (AbstractClassGenerator)CURRENT.get();
166    }
167
168    public ClassLoader getClassLoader() {
169        ClassLoader t = classLoader;
170        if (t == null) {
171            t = getDefaultClassLoader();
172        }
173        if (t == null) {
174            t = getClass().getClassLoader();
175        }
176        if (t == null) {
177            t = Thread.currentThread().getContextClassLoader();
178        }
179        if (t == null) {
180            throw new IllegalStateException("Cannot determine classloader");
181        }
182        return t;
183    }
184
185    abstract protected ClassLoader getDefaultClassLoader();
186
187    protected Object create(Object key) {
188        try {
189        	Class gen = null;
190
191            synchronized (source) {
192                ClassLoader loader = getClassLoader();
193                Map cache2 = null;
194                cache2 = (Map)source.cache.get(loader);
195                if (cache2 == null) {
196                    cache2 = new HashMap();
197                    cache2.put(NAME_KEY, new HashSet());
198                    source.cache.put(loader, cache2);
199                } else if (useCache) {
200                    Reference ref = (Reference)cache2.get(key);
201                    gen = (Class) (( ref == null ) ? null : ref.get());
202                }
203                if (gen == null) {
204                    Object save = CURRENT.get();
205                    CURRENT.set(this);
206                    try {
207                        this.key = key;
208
209                        if (attemptLoad) {
210                            try {
211                                gen = loader.loadClass(getClassName());
212                            } catch (ClassNotFoundException e) {
213                                // ignore
214                            }
215                        }
216                        if (gen == null) {
217                            byte[] b = strategy.generate(this);
218                            String className = ClassNameReader.getClassName(new ClassReader(b));
219                            getClassNameCache(loader).add(className);
220                            gen = ReflectUtils.defineClass(className, b, loader);
221                        }
222
223                        if (useCache) {
224                            cache2.put(key, new WeakReference(gen));
225                        }
226                        return firstInstance(gen);
227                    } finally {
228                        CURRENT.set(save);
229                    }
230                }
231            }
232            return firstInstance(gen);
233        } catch (RuntimeException e) {
234            throw e;
235        } catch (Error e) {
236            throw e;
237        } catch (Exception e) {
238            throw new CodeGenerationException(e);
239        }
240    }
241
242    abstract protected Object firstInstance(Class type) throws Exception;
243    abstract protected Object nextInstance(Object instance) throws Exception;
244}
245