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 }