View Javadoc

1   /*
2    $Id: MetaClassRegistry.java,v 1.19 2005/02/24 22:41:32 blackdrag Exp $
3   
4    Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
5   
6    Redistribution and use of this software and associated documentation
7    ("Software"), with or without modification, are permitted provided
8    that the following conditions are met:
9   
10   1. Redistributions of source code must retain copyright
11      statements and notices.  Redistributions must also contain a
12      copy of this document.
13  
14   2. Redistributions in binary form must reproduce the
15      above copyright notice, this list of conditions and the
16      following disclaimer in the documentation and/or other
17      materials provided with the distribution.
18  
19   3. The name "groovy" must not be used to endorse or promote
20      products derived from this Software without prior written
21      permission of The Codehaus.  For written permission,
22      please contact info@codehaus.org.
23  
24   4. Products derived from this Software may not be called "groovy"
25      nor may "groovy" appear in their names without prior written
26      permission of The Codehaus. "groovy" is a registered
27      trademark of The Codehaus.
28  
29   5. Due credit should be given to The Codehaus -
30      http://groovy.codehaus.org/
31  
32   THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
33   ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
34   NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
35   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
36   THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
37   INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
38   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
39   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
40   HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
41   STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
42   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
43   OF THE POSSIBILITY OF SUCH DAMAGE.
44  
45   */
46  package groovy.lang;
47  
48  import org.codehaus.groovy.runtime.DefaultGroovyMethods;
49  import org.codehaus.groovy.runtime.DefaultGroovyStaticMethods;
50  
51  import java.beans.IntrospectionException;
52  import java.lang.reflect.Constructor;
53  import java.security.AccessController;
54  import java.security.PrivilegedAction;
55  import java.util.ArrayList;
56  import java.util.Collections;
57  import java.util.Iterator;
58  import java.util.List;
59  import java.util.Map;
60  import java.util.WeakHashMap;
61  
62  /***
63   * A registery of MetaClass instances which caches introspection &
64   * reflection information and allows methods to be dynamically added to
65   * existing classes at runtime
66   *
67   * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
68   * @version $Revision: 1.19 $
69   */
70  public class MetaClassRegistry {
71      private Map metaClasses = Collections.synchronizedMap(new WeakHashMap());
72      private boolean useAccessible;
73      private Map loaderMap = Collections.synchronizedMap(new WeakHashMap());
74      private GroovyClassLoader loader =
75              (GroovyClassLoader) AccessController.doPrivileged(new PrivilegedAction() {
76                  public Object run() {
77                      return new GroovyClassLoader(getClass().getClassLoader());
78                  }
79              });
80  
81      public static final int LOAD_DEFAULT = 0;
82      public static final int DONT_LOAD_DEFAULT = 1;
83      private static MetaClassRegistry instanceInclude;
84      private static MetaClassRegistry instanceExclude;
85  
86  
87      public MetaClassRegistry() {
88          this(true);
89      }
90  
91      public MetaClassRegistry(int loadDefault) {
92          if (loadDefault == LOAD_DEFAULT) {
93              this.useAccessible = true;
94              // lets register the default methods
95              lookup(DefaultGroovyMethods.class).registerInstanceMethods();
96              lookup(DefaultGroovyStaticMethods.class).registerStaticMethods();
97              checkInitialised();
98          }
99          else {
100             this.useAccessible = true;
101             // do nothing to avoid loading DefaultGroovyMethod
102         }
103     }
104 
105     /***
106      * @param useAccessible defines whether or not the {@link java.lang.reflect.AccessibleObject.setAccessible();}
107      *                      method will be called to enable access to all methods when using reflection
108      */
109     public MetaClassRegistry(boolean useAccessible) {
110         this.useAccessible = useAccessible;
111 
112         // lets register the default methods
113         lookup(DefaultGroovyMethods.class).registerInstanceMethods();
114         lookup(DefaultGroovyStaticMethods.class).registerStaticMethods();
115         checkInitialised();
116     }
117 
118     public MetaClass getMetaClass(Class theClass) {
119         synchronized (theClass) {
120             MetaClass answer = (MetaClass) metaClasses.get(theClass);
121             if (answer == null) {
122                 try {
123                     answer = new MetaClass(this, theClass);
124                     answer.checkInitialised();
125                 }
126                 catch (IntrospectionException e) {
127                     throw new GroovyRuntimeException("Could not introspect class: " + theClass.getName() + ". Reason: " + e,
128                             e);
129                 }
130                 metaClasses.put(theClass, answer);
131             }
132             return answer;
133         }
134     }
135 
136     public void removeMetaClass(Class theClass) {
137         metaClasses.remove(theClass);
138     }
139 
140 
141     /***
142      * Registers a new MetaClass in the registry to customize the type
143      *
144      * @param theClass
145      * @param theMetaClass
146      */
147     public void setMetaClass(Class theClass, MetaClass theMetaClass) {
148         metaClasses.put(theClass, theMetaClass);
149     }
150 
151     public boolean useAccessible() {
152         return useAccessible;
153     }
154 
155     /***
156      * A helper class to load meta class bytecode into the class loader
157      */
158     public Class loadClass(final String name, final byte[] bytecode) throws ClassNotFoundException {
159         return (Class) AccessController.doPrivileged(new PrivilegedAction() {
160             public Object run() {
161                 return getGroovyLoader(loader).defineClass(name, bytecode, getClass().getProtectionDomain());
162             }
163         });
164     }
165 
166     public Class loadClass(final ClassLoader loader, final String name, final byte[] bytecode) throws ClassNotFoundException {
167         return (Class) AccessController.doPrivileged(new PrivilegedAction() {
168             public Object run() {
169                 return getGroovyLoader(loader).defineClass(name, bytecode, getClass().getProtectionDomain());
170             }
171         });
172          }
173 
174     public Class loadClass(ClassLoader loader, String name) throws ClassNotFoundException {
175         return getGroovyLoader(loader).loadClass(name);
176     }
177 
178     public Class loadClass(String name) throws ClassNotFoundException {
179         return getGroovyLoader(loader).loadClass(name);
180     }
181 
182     private GroovyClassLoader getGroovyLoader(ClassLoader loader) {
183         if (loader instanceof GroovyClassLoader) {
184             return (GroovyClassLoader) loader;
185         }
186         
187         synchronized (loaderMap) {
188             GroovyClassLoader groovyLoader = (GroovyClassLoader) loaderMap.get(loader);
189             if (groovyLoader == null) {
190                 if (loader == null || loader == getClass().getClassLoader()) {
191                     groovyLoader = this.loader;
192                 }
193                 else {
194                     // We have to make sure a class loaded through groovy is always
195                     // able to find a class of the runtime. I assume the classloaders
196                     // are behaving normal and delegate unknown classes to the parent
197                     // classloader. So if no parent has the same classloader as the 
198                     // runtime we can't use the normal loader to create the 
199                     // GroovyClassLoader
200                     ClassLoader localLoader = getClass().getClassLoader();
201                     ClassLoader parent = loader;
202                     while (parent!=localLoader && parent!=null) parent=parent.getParent();
203                     if (parent==null) {
204                         groovyLoader = new GroovyClassLoader(localLoader);
205                     } else {
206                         groovyLoader = new GroovyClassLoader(loader);
207                     }
208                 }
209 
210                 loaderMap.put(loader, groovyLoader);
211             }
212 
213             return groovyLoader;
214         }
215     }
216 
217     /***
218      * Ensures that all the registered MetaClass instances are initalized
219      */
220     void checkInitialised() {
221         // lets copy all the classes in the repository right now 
222         // to avoid concurrent modification exception
223         List list = new ArrayList(metaClasses.values());
224         for (Iterator iter = list.iterator(); iter.hasNext();) {
225             MetaClass metaClass = (MetaClass) iter.next();
226             metaClass.checkInitialised();
227         }
228     }
229 
230     /***
231      * Used by MetaClass when registering new methods which avoids initializing the MetaClass instances on lookup
232      */
233     MetaClass lookup(Class theClass) {
234         MetaClass answer = (MetaClass) metaClasses.get(theClass);
235         if (answer == null) {
236             try {
237                 answer = new MetaClass(this, theClass);
238             }
239             catch (IntrospectionException e) {
240                 throw new GroovyRuntimeException("Could not introspect class: " + theClass.getName() + ". Reason: " + e,
241                         e);
242             }
243             metaClasses.put(theClass, answer);
244         }
245         return answer;
246     }
247 
248 
249     public MetaMethod getDefinedMethod(Class theClass, String methodName, Class[] args, boolean isStatic) {
250         MetaClass metaclass = this.getMetaClass(theClass);
251         if (metaclass == null) {
252             return null;
253         }
254         else {
255             if (isStatic) {
256                 return metaclass.retrieveStaticMethod(methodName, args);
257             }
258             else {
259                 return metaclass.retrieveMethod(methodName, args);
260             }
261         }
262     }
263 
264     public Constructor getDefinedConstructor(Class theClass, Class[] args) {
265         MetaClass metaclass = this.getMetaClass(theClass);
266         if (metaclass == null) {
267             return null;
268         }
269         else {
270             return metaclass.retrieveConstructor(args);
271         }
272     }
273 
274     /***
275      * Singleton of MetaClassRegistry. Shall we use threadlocal to store the instance?
276      *
277      * @param includeExtension
278      * @return
279      */
280     public static MetaClassRegistry getIntance(int includeExtension) {
281         if (includeExtension != DONT_LOAD_DEFAULT) {
282             if (instanceInclude == null) {
283                 instanceInclude = new MetaClassRegistry();
284             }
285             return instanceInclude;
286         }
287         else {
288             if (instanceExclude == null) {
289                 instanceExclude = new MetaClassRegistry(DONT_LOAD_DEFAULT);
290             }
291             return instanceExclude;
292         }
293     }
294 }