1 /** 2 * Copyright (C) 2008 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.googlecode.guice; 18 19 import static com.google.inject.matcher.Matchers.any; 20 21 import com.google.inject.AbstractModule; 22 import com.google.inject.Binder; 23 import com.google.inject.Guice; 24 import com.google.inject.Injector; 25 import com.google.inject.Module; 26 import com.googlecode.guice.PackageVisibilityTestModule.PublicUserOfPackagePrivate; 27 28 import junit.framework.TestCase; 29 30 import org.aopalliance.intercept.MethodInterceptor; 31 import org.aopalliance.intercept.MethodInvocation; 32 33 import java.io.File; 34 import java.lang.ref.Reference; 35 import java.lang.ref.WeakReference; 36 import java.lang.reflect.Constructor; 37 import java.lang.reflect.Method; 38 import java.net.MalformedURLException; 39 import java.net.URL; 40 import java.net.URLClassLoader; 41 import java.util.concurrent.TimeoutException; 42 43 /** 44 * This test is in a separate package so we can test package-level visibility 45 * with confidence. 46 * 47 * @author mcculls@gmail.com (Stuart McCulloch) 48 */ 49 public class BytecodeGenTest extends TestCase { 50 51 private final ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); 52 53 private final Module interceptorModule = new AbstractModule() { 54 protected void configure() { 55 bindInterceptor(any(), any(), new MethodInterceptor() { 56 public Object invoke(MethodInvocation chain) 57 throws Throwable { 58 return chain.proceed() + " WORLD"; 59 } 60 }); 61 } 62 }; 63 64 private final Module noopInterceptorModule = new AbstractModule() { 65 protected void configure() { 66 bindInterceptor(any(), any(), new MethodInterceptor() { 67 public Object invoke(MethodInvocation chain) 68 throws Throwable { 69 return chain.proceed(); 70 } 71 }); 72 } 73 }; 74 testPackageVisibility()75 public void testPackageVisibility() { 76 Injector injector = Guice.createInjector(new PackageVisibilityTestModule()); 77 injector.getInstance(PublicUserOfPackagePrivate.class); // This must pass. 78 } 79 testInterceptedPackageVisibility()80 public void testInterceptedPackageVisibility() { 81 Injector injector = Guice.createInjector(interceptorModule, new PackageVisibilityTestModule()); 82 injector.getInstance(PublicUserOfPackagePrivate.class); // This must pass. 83 } 84 testEnhancerNaming()85 public void testEnhancerNaming() { 86 Injector injector = Guice.createInjector(interceptorModule, new PackageVisibilityTestModule()); 87 PublicUserOfPackagePrivate pupp = injector.getInstance(PublicUserOfPackagePrivate.class); 88 assertTrue(pupp.getClass().getName().startsWith( 89 PublicUserOfPackagePrivate.class.getName() + "$$EnhancerByGuice$$")); 90 } 91 92 // TODO(sameb): Figure out how to test FastClass naming tests. 93 94 /** 95 * Custom URL classloader with basic visibility rules 96 */ 97 static class TestVisibilityClassLoader 98 extends URLClassLoader { 99 100 boolean hideInternals; 101 TestVisibilityClassLoader(boolean hideInternals)102 public TestVisibilityClassLoader(boolean hideInternals) { 103 super(new URL[0]); 104 105 this.hideInternals = hideInternals; 106 107 final String[] classpath = System.getProperty("java.class.path").split(File.pathSeparator); 108 for (final String element : classpath) { 109 try { 110 // is it a remote/local URL? 111 addURL(new URL(element)); 112 } catch (final MalformedURLException e1) { 113 try { 114 // nope - perhaps it's a filename? 115 addURL(new File(element).toURI().toURL()); 116 } catch (final MalformedURLException e2) { 117 throw new RuntimeException(e1); 118 } 119 } 120 } 121 } 122 123 /** 124 * Classic parent-delegating classloaders are meant to override findClass. 125 * However, non-delegating classloaders (as used in OSGi) instead override 126 * loadClass to provide support for "class-space" separation. 127 */ 128 @Override loadClass(final String name, final boolean resolve)129 protected Class<?> loadClass(final String name, final boolean resolve) 130 throws ClassNotFoundException { 131 132 synchronized (this) { 133 // check our local cache to avoid duplicates 134 final Class<?> clazz = findLoadedClass(name); 135 if (clazz != null) { 136 return clazz; 137 } 138 } 139 140 if (name.startsWith("java.")) { 141 142 // standard bootdelegation of java.* 143 return super.loadClass(name, resolve); 144 145 } else if (!name.contains(".internal.") && !name.contains(".cglib.")) { 146 147 /* 148 * load public and test classes directly from the classpath - we don't 149 * delegate to our parent because then the loaded classes would also be 150 * able to see private internal Guice classes, as they are also loaded 151 * by the parent classloader. 152 */ 153 final Class<?> clazz = findClass(name); 154 if (resolve) { 155 resolveClass(clazz); 156 } 157 return clazz; 158 } 159 160 // hide internal non-test classes 161 if (hideInternals) { 162 throw new ClassNotFoundException(); 163 } 164 return super.loadClass(name, resolve); 165 } 166 } 167 168 /** as loaded by another class loader */ 169 private Class<ProxyTest> proxyTestClass; 170 private Class<ProxyTestImpl> realClass; 171 private Module testModule; 172 173 @SuppressWarnings("unchecked") setUp()174 protected void setUp() throws Exception { 175 super.setUp(); 176 177 ClassLoader testClassLoader = new TestVisibilityClassLoader(true); 178 proxyTestClass = (Class<ProxyTest>) testClassLoader.loadClass(ProxyTest.class.getName()); 179 realClass = (Class<ProxyTestImpl>) testClassLoader.loadClass(ProxyTestImpl.class.getName()); 180 181 testModule = new AbstractModule() { 182 public void configure() { 183 bind(proxyTestClass).to(realClass); 184 } 185 }; 186 } 187 188 interface ProxyTest { sayHello()189 String sayHello(); 190 } 191 192 /** 193 * Note: this class must be marked as public or protected so that the Guice 194 * custom classloader will intercept it. Private and implementation classes 195 * are not intercepted by the custom classloader. 196 * 197 * @see com.google.inject.internal.BytecodeGen.Visibility 198 */ 199 public static class ProxyTestImpl implements ProxyTest { 200 201 static { 202 //System.out.println(ProxyTestImpl.class.getClassLoader()); 203 } 204 sayHello()205 public String sayHello() { 206 return "HELLO"; 207 } 208 } 209 testProxyClassLoading()210 public void testProxyClassLoading() throws Exception { 211 Object testObject = Guice.createInjector(interceptorModule, testModule) 212 .getInstance(proxyTestClass); 213 214 // verify method interception still works 215 Method m = realClass.getMethod("sayHello"); 216 assertEquals("HELLO WORLD", m.invoke(testObject)); 217 } 218 testSystemClassLoaderIsUsedIfProxiedClassUsesIt()219 public void testSystemClassLoaderIsUsedIfProxiedClassUsesIt() { 220 ProxyTest testProxy = Guice.createInjector(interceptorModule, new Module() { 221 public void configure(Binder binder) { 222 binder.bind(ProxyTest.class).to(ProxyTestImpl.class); 223 } 224 }).getInstance(ProxyTest.class); 225 226 if (ProxyTest.class.getClassLoader() == systemClassLoader) { 227 assertSame(testProxy.getClass().getClassLoader(), systemClassLoader); 228 } else { 229 assertNotSame(testProxy.getClass().getClassLoader(), systemClassLoader); 230 } 231 } 232 testProxyClassUnloading()233 public void testProxyClassUnloading() { 234 Object testObject = Guice.createInjector(interceptorModule, testModule) 235 .getInstance(proxyTestClass); 236 assertNotNull(testObject.getClass().getClassLoader()); 237 assertNotSame(testObject.getClass().getClassLoader(), systemClassLoader); 238 239 // take a weak reference to the generated proxy class 240 Reference<Class<?>> clazzRef = new WeakReference<Class<?>>(testObject.getClass()); 241 242 assertNotNull(clazzRef.get()); 243 244 // null the proxy 245 testObject = null; 246 247 /* 248 * this should be enough to queue the weak reference 249 * unless something is holding onto it accidentally. 250 */ 251 final int MAX_COUNT = 100; 252 String[] buf; 253 System.gc(); 254 //TODO(cgruber): Use com.google.common.testing.GcFinalization and a countdown latch to un-flake. 255 for (int count = 0 ; clazzRef.get() != null ; count++) { 256 buf = new String[8 * 1024 * 1024]; 257 buf = null; 258 System.gc(); 259 assertTrue("Timeout waiting for class to be unloaded. This may be a flaky result.", 260 count <= MAX_COUNT); 261 } 262 263 // This test could be somewhat flaky when the GC isn't working. 264 // If it fails, run the test again to make sure it's failing reliably. 265 assertNull("Proxy class was not unloaded.", clazzRef.get()); 266 } 267 testProxyingPackagePrivateMethods()268 public void testProxyingPackagePrivateMethods() { 269 Injector injector = Guice.createInjector(interceptorModule); 270 assertEquals("HI WORLD", injector.getInstance(PackageClassPackageMethod.class).sayHi()); 271 assertEquals("HI WORLD", injector.getInstance(PublicClassPackageMethod.class).sayHi()); 272 assertEquals("HI WORLD", injector.getInstance(ProtectedClassProtectedMethod.class).sayHi()); 273 } 274 275 static class PackageClassPackageMethod { sayHi()276 String sayHi() { 277 return "HI"; 278 } 279 } 280 281 public static class PublicClassPackageMethod { sayHi()282 String sayHi() { 283 return "HI"; 284 } 285 } 286 287 protected static class ProtectedClassProtectedMethod { sayHi()288 protected String sayHi() { 289 return "HI"; 290 } 291 } 292 293 static class Hidden { 294 } 295 296 public static class HiddenMethodReturn { method()297 public Hidden method() { 298 return new Hidden(); 299 } 300 } 301 302 public static class HiddenMethodParameter { method(Hidden h)303 public void method(Hidden h) { 304 } 305 } 306 testClassLoaderBridging()307 public void testClassLoaderBridging() throws Exception { 308 ClassLoader testClassLoader = new TestVisibilityClassLoader(false); 309 310 Class hiddenMethodReturnClass = testClassLoader.loadClass(HiddenMethodReturn.class.getName()); 311 Class hiddenMethodParameterClass = testClassLoader.loadClass(HiddenMethodParameter.class.getName()); 312 313 Injector injector = Guice.createInjector(noopInterceptorModule); 314 315 Class hiddenClass = testClassLoader.loadClass(Hidden.class.getName()); 316 Constructor ctor = hiddenClass.getDeclaredConstructor(); 317 318 ctor.setAccessible(true); 319 320 // don't use bridging for proxies with private parameters 321 Object o1 = injector.getInstance(hiddenMethodParameterClass); 322 o1.getClass().getDeclaredMethod("method", hiddenClass).invoke(o1, ctor.newInstance()); 323 324 // don't use bridging for proxies with private return types 325 Object o2 = injector.getInstance(hiddenMethodReturnClass); 326 o2.getClass().getDeclaredMethod("method").invoke(o2); 327 } 328 } 329