001    /*
002     $Id: MetaClassRegistry.java 4554 2006-12-21 23:54:28Z blackdrag $
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 java.lang.reflect.Constructor;
049    import java.lang.reflect.Method;
050    import java.security.AccessController;
051    import java.security.PrivilegedAction;
052    import java.util.LinkedList;
053    import java.util.List;
054    
055    import org.codehaus.groovy.classgen.ReflectorGenerator;
056    import org.codehaus.groovy.runtime.DefaultGroovyMethods;
057    import org.codehaus.groovy.runtime.DefaultGroovyStaticMethods;
058    import org.codehaus.groovy.runtime.MethodHelper;
059    import org.codehaus.groovy.runtime.ReferenceMap;
060    import org.codehaus.groovy.runtime.Reflector;
061    import org.codehaus.groovy.runtime.ReflectorLoader;
062    import org.objectweb.asm.ClassWriter;
063    
064    /**
065     * A registery of MetaClass instances which caches introspection &
066     * reflection information and allows methods to be dynamically added to
067     * existing classes at runtime
068     *
069     * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
070     * @author John Wilson
071     * @author <a href="mailto:blackdrag@gmx.org">Jochen Theodorou</a>
072     * @version $Revision: 4554 $
073     */
074    public class MetaClassRegistry {
075        private ReferenceMap metaClasses = new ReferenceMap();
076        private ReferenceMap loaderMap = new ReferenceMap();
077        private boolean useAccessible;
078        
079        private LinkedList instanceMethods = new LinkedList();
080        private LinkedList staticMethods = new LinkedList();
081    
082        public static final int LOAD_DEFAULT = 0;
083        public static final int DONT_LOAD_DEFAULT = 1;
084        private static MetaClassRegistry instanceInclude;
085        private static MetaClassRegistry instanceExclude;
086    
087    
088        public MetaClassRegistry() {
089            this(LOAD_DEFAULT, true);
090        }
091    
092        public MetaClassRegistry(int loadDefault) {
093            this(loadDefault, true);
094        }
095    
096        /**
097         * @param useAccessible defines whether or not the {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)}
098         *                      method will be called to enable access to all methods when using reflection
099         */
100        public MetaClassRegistry(boolean useAccessible) {
101            this(LOAD_DEFAULT, useAccessible);
102        }
103        
104        public MetaClassRegistry(final int loadDefault, final boolean useAccessible) {
105            this.useAccessible = useAccessible;
106            
107            if (loadDefault == LOAD_DEFAULT) {
108                // lets register the default methods
109                registerMethods(DefaultGroovyMethods.class, true);
110                registerMethods(DefaultGroovyStaticMethods.class, false);
111            }
112        }
113        
114        private void registerMethods(final Class theClass, final boolean useInstanceMethods) {
115            Method[] methods = theClass.getMethods();
116            for (int i = 0; i < methods.length; i++) {
117                Method method = methods[i];
118                if (MethodHelper.isStatic(method)) {
119                    Class[] paramTypes = method.getParameterTypes();
120                    if (paramTypes.length > 0) {
121                        if (useInstanceMethods) {
122                            instanceMethods.add(method);
123                        } else {
124                            staticMethods.add(method);
125                        }
126                    }
127                }
128            }
129        }
130    
131        public MetaClass getMetaClass(Class theClass) {
132            synchronized (theClass) {
133                MetaClass answer = (MetaClass) metaClasses.get(theClass);
134                if (answer == null) {
135                    answer = getMetaClassFor(theClass);
136                    answer.initialize();
137                    metaClasses.put(theClass, answer);
138                }
139                return answer;
140            }
141        }
142    
143        public void removeMetaClass(Class theClass) {
144            synchronized (theClass) {
145                metaClasses.remove(theClass);
146            }
147        }
148    
149    
150        /**
151         * Registers a new MetaClass in the registry to customize the type
152         *
153         * @param theClass
154         * @param theMetaClass
155         */
156        public void setMetaClass(Class theClass, MetaClass theMetaClass) {
157            synchronized(theClass) {
158                metaClasses.putStrong(theClass, theMetaClass);
159            }
160        }
161    
162        public boolean useAccessible() {
163            return useAccessible;
164        }
165    
166        private ReflectorLoader getReflectorLoader(final ClassLoader loader) {
167            synchronized (loaderMap) {
168                ReflectorLoader reflectorLoader = (ReflectorLoader) loaderMap.get(loader);
169                if (reflectorLoader == null) {
170                    reflectorLoader = (ReflectorLoader) AccessController.doPrivileged(new PrivilegedAction() {
171                        public Object run() {
172                            return new ReflectorLoader(loader);
173                        }
174                    }); 
175                    loaderMap.put(loader, reflectorLoader);
176                }
177                return reflectorLoader;
178            }
179        }
180    
181        /**
182         * Used by MetaClass when registering new methods which avoids initializing the MetaClass instances on lookup
183         */
184        MetaClass lookup(Class theClass) {
185            synchronized (theClass) {
186                MetaClass answer = (MetaClass) metaClasses.get(theClass);
187                if (answer == null) {
188                    answer = getMetaClassFor(theClass);
189                    metaClasses.put(theClass, answer);
190                }
191                return answer;
192            }
193        }
194    
195        /**
196         * Find a MetaClass for the class
197         * If there is a custom MetaClass then return an instance of that. Otherwise return an instance of the standard MetaClass
198         * 
199         * @param theClass
200         * @return An instace of the MetaClass which will handle this class
201         */
202        private MetaClass getMetaClassFor(final Class theClass) {
203            try {
204                final Class customMetaClass = Class.forName("groovy.runtime.metaclass." + theClass.getName() + "MetaClass");
205                final Constructor customMetaClassConstructor = customMetaClass.getConstructor(new Class[]{MetaClassRegistry.class, Class.class});
206                
207                return (MetaClass)customMetaClassConstructor.newInstance(new Object[]{this, theClass});
208            } catch (final ClassNotFoundException e) {
209                return new MetaClassImpl(this, theClass);
210            } catch (final Exception e) {
211                throw new GroovyRuntimeException("Could not instantiate custom Metaclass for class: " + theClass.getName() + ". Reason: " + e, e);
212            }
213        }
214    
215        /**
216         * Singleton of MetaClassRegistry. Shall we use threadlocal to store the instance?
217         *
218         * @param includeExtension
219         */
220        public static MetaClassRegistry getInstance(int includeExtension) {
221            if (includeExtension != DONT_LOAD_DEFAULT) {
222                if (instanceInclude == null) {
223                    instanceInclude = new MetaClassRegistry();
224                }
225                return instanceInclude;
226            }
227            else {
228                if (instanceExclude == null) {
229                    instanceExclude = new MetaClassRegistry(DONT_LOAD_DEFAULT);
230                }
231                return instanceExclude;
232            }
233        }
234    
235        public synchronized Reflector loadReflector(final Class theClass, List methods) {
236            final String name = getReflectorName(theClass);
237            ClassLoader loader = (ClassLoader) AccessController.doPrivileged(new PrivilegedAction() {
238                public Object run() {
239                    ClassLoader loader = theClass.getClassLoader();
240                    if (loader == null) loader = this.getClass().getClassLoader();
241                    return loader;
242                }
243            });
244            final ReflectorLoader rloader = getReflectorLoader(loader);
245            Class ref = rloader.getLoadedClass(name);
246            if (ref == null) {
247                /*
248                 * Lets generate it && load it.
249                 */                        
250                ReflectorGenerator generator = new ReflectorGenerator(methods);
251                ClassWriter cw = new ClassWriter(true);
252                generator.generate(cw, name);
253                final byte[] bytecode = cw.toByteArray();
254                ref = (Class) AccessController.doPrivileged(new PrivilegedAction() {
255                    public Object run() {
256                        return rloader.defineClass(name, bytecode, getClass().getProtectionDomain());
257                    }
258                }); 
259            }
260            try {
261                return (Reflector) ref.newInstance();
262            } catch (Exception e) {
263                throw new GroovyRuntimeException("Could not generate and load the reflector for class: " + name + ". Reason: " + e, e);
264            }
265        }
266        
267        private String getReflectorName(Class theClass) {
268            String className = theClass.getName();
269            String packagePrefix = "gjdk.";
270            String name = packagePrefix + className + "_GroovyReflector";
271            if (theClass.isArray()) {
272                   Class clazz = theClass;
273                   name = packagePrefix;
274                   int level = 0;
275                   while (clazz.isArray()) {
276                      clazz = clazz.getComponentType();
277                      level++;
278                   }
279                String componentName = clazz.getName();
280                name = packagePrefix + componentName + "_GroovyReflectorArray";
281                if (level>1) name += level;
282            }
283            return name;
284        }
285    
286        List getInstanceMethods() {
287            return instanceMethods;
288        }
289    
290        List getStaticMethods() {
291            return staticMethods;
292        }
293    }