001    /*
002     $Id: MetaClassRegistry.java,v 1.21 2005/07/16 16:11:28 glaforge 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.21 $
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                            final ClassLoader localLoader = getClass().getClassLoader();
207                            groovyLoader = (GroovyClassLoader) AccessController.doPrivileged(new PrivilegedAction() {
208                                public Object run() {
209                                    return new GroovyClassLoader(localLoader);
210                                }
211                            }); 
212                        }
213                    }
214                    loaderMap.put(loader, groovyLoader);
215                }
216    
217                return groovyLoader;
218            }
219        }
220    
221        /**
222         * Ensures that all the registered MetaClass instances are initalized
223         */
224        void checkInitialised() {
225            // lets copy all the classes in the repository right now 
226            // to avoid concurrent modification exception
227            List list = new ArrayList(metaClasses.values());
228            for (Iterator iter = list.iterator(); iter.hasNext();) {
229                MetaClass metaClass = (MetaClass) iter.next();
230                metaClass.checkInitialised();
231            }
232        }
233    
234        /**
235         * Used by MetaClass when registering new methods which avoids initializing the MetaClass instances on lookup
236         */
237        MetaClass lookup(Class theClass) {
238            MetaClass answer = (MetaClass) metaClasses.get(theClass);
239            if (answer == null) {
240                try {
241                    answer = new MetaClass(this, theClass);
242                }
243                catch (IntrospectionException e) {
244                    throw new GroovyRuntimeException("Could not introspect class: " + theClass.getName() + ". Reason: " + e,
245                            e);
246                }
247                metaClasses.put(theClass, answer);
248            }
249            return answer;
250        }
251    
252    
253        public MetaMethod getDefinedMethod(Class theClass, String methodName, Class[] args, boolean isStatic) {
254            MetaClass metaclass = this.getMetaClass(theClass);
255            if (metaclass == null) {
256                return null;
257            }
258            else {
259                if (isStatic) {
260                    return metaclass.retrieveStaticMethod(methodName, args);
261                }
262                else {
263                    return metaclass.retrieveMethod(methodName, args);
264                }
265            }
266        }
267    
268        public Constructor getDefinedConstructor(Class theClass, Class[] args) {
269            MetaClass metaclass = this.getMetaClass(theClass);
270            if (metaclass == null) {
271                return null;
272            }
273            else {
274                return metaclass.retrieveConstructor(args);
275            }
276        }
277    
278        /**
279         * Singleton of MetaClassRegistry. Shall we use threadlocal to store the instance?
280         *
281         * @param includeExtension
282         * @return
283         */
284        public static MetaClassRegistry getIntance(int includeExtension) {
285            if (includeExtension != DONT_LOAD_DEFAULT) {
286                if (instanceInclude == null) {
287                    instanceInclude = new MetaClassRegistry();
288                }
289                return instanceInclude;
290            }
291            else {
292                if (instanceExclude == null) {
293                    instanceExclude = new MetaClassRegistry(DONT_LOAD_DEFAULT);
294                }
295                return instanceExclude;
296            }
297        }
298    }