001    /*
002     $Id: MetaClassRegistry.java,v 1.20 2005/04/13 14:15:26 jstrachan Exp $
003    
004     Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
005    
006     Redistribution and use of this software and associated documentation
007     ("Software"), with or without modification, are permitted provided
008     that the following conditions are met:
009    
010     1. Redistributions of source code must retain copyright
011        statements and notices.  Redistributions must also contain a
012        copy of this document.
013    
014     2. Redistributions in binary form must reproduce the
015        above copyright notice, this list of conditions and the
016        following disclaimer in the documentation and/or other
017        materials provided with the distribution.
018    
019     3. The name "groovy" must not be used to endorse or promote
020        products derived from this Software without prior written
021        permission of The Codehaus.  For written permission,
022        please contact info@codehaus.org.
023    
024     4. Products derived from this Software may not be called "groovy"
025        nor may "groovy" appear in their names without prior written
026        permission of The Codehaus. "groovy" is a registered
027        trademark of The Codehaus.
028    
029     5. Due credit should be given to The Codehaus -
030        http://groovy.codehaus.org/
031    
032     THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
033     ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
034     NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
035     FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
036     THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
037     INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
038     (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
039     SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
040     HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
041     STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
042     ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
043     OF THE POSSIBILITY OF SUCH DAMAGE.
044    
045     */
046    package groovy.lang;
047    
048    import org.codehaus.groovy.runtime.DefaultGroovyMethods;
049    import org.codehaus.groovy.runtime.DefaultGroovyStaticMethods;
050    
051    import java.beans.IntrospectionException;
052    import java.lang.reflect.Constructor;
053    import java.security.AccessController;
054    import java.security.PrivilegedAction;
055    import java.util.ArrayList;
056    import java.util.Collections;
057    import java.util.Iterator;
058    import java.util.List;
059    import java.util.Map;
060    import java.util.WeakHashMap;
061    
062    /**
063     * A registery of MetaClass instances which caches introspection &
064     * reflection information and allows methods to be dynamically added to
065     * existing classes at runtime
066     *
067     * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
068     * @version $Revision: 1.20 $
069     */
070    public class MetaClassRegistry {
071        private Map metaClasses = Collections.synchronizedMap(new WeakHashMap());
072        private boolean useAccessible;
073        private Map loaderMap = Collections.synchronizedMap(new WeakHashMap());
074        private GroovyClassLoader loader =
075                (GroovyClassLoader) AccessController.doPrivileged(new PrivilegedAction() {
076                    public Object run() {
077                        return new GroovyClassLoader(getClass().getClassLoader());
078                    }
079                });
080    
081        public static final int LOAD_DEFAULT = 0;
082        public static final int DONT_LOAD_DEFAULT = 1;
083        private static MetaClassRegistry instanceInclude;
084        private static MetaClassRegistry instanceExclude;
085    
086    
087        public MetaClassRegistry() {
088            this(true);
089        }
090    
091        public MetaClassRegistry(int loadDefault) {
092            if (loadDefault == LOAD_DEFAULT) {
093                this.useAccessible = true;
094                // lets register the default methods
095                lookup(DefaultGroovyMethods.class).registerInstanceMethods();
096                lookup(DefaultGroovyStaticMethods.class).registerStaticMethods();
097                checkInitialised();
098            }
099            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                        // lets check that the class loader can see the Groovy classes
195                        // if so we'll use that, otherwise lets use the local class loader
196                        try {
197                            loader.loadClass(getClass().getName());
198    
199                            // thats fine, lets use the loader
200                            groovyLoader = new GroovyClassLoader(loader);
201                        }
202                        catch (ClassNotFoundException e) {
203    
204                            // we can't see the groovy classes here
205                            // so lets try create a new loader
206                            ClassLoader localLoader = getClass().getClassLoader();
207                            groovyLoader = new GroovyClassLoader(localLoader);
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    }