| /* |
| * Copyright (C) 2008 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.googlecode.guice; |
| |
| import static com.google.inject.Asserts.getClassPathUrls; |
| import static com.google.inject.matcher.Matchers.any; |
| |
| import com.google.common.testing.GcFinalization; |
| import com.google.inject.AbstractModule; |
| import com.google.inject.Binder; |
| import com.google.inject.Guice; |
| import com.google.inject.Injector; |
| import com.google.inject.Module; |
| import com.googlecode.guice.PackageVisibilityTestModule.PublicUserOfPackagePrivate; |
| import java.lang.ref.WeakReference; |
| import java.lang.reflect.Constructor; |
| import java.lang.reflect.Method; |
| import java.net.URLClassLoader; |
| import javax.inject.Inject; |
| import junit.framework.TestCase; |
| import org.aopalliance.intercept.MethodInterceptor; |
| import org.aopalliance.intercept.MethodInvocation; |
| |
| /** |
| * This test is in a separate package so we can test package-level visibility with confidence. |
| * |
| * @author mcculls@gmail.com (Stuart McCulloch) |
| */ |
| public class BytecodeGenTest extends TestCase { |
| |
| private final ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); |
| |
| private final Module interceptorModule = |
| new AbstractModule() { |
| @Override |
| protected void configure() { |
| bindInterceptor( |
| any(), |
| any(), |
| new MethodInterceptor() { |
| @Override |
| public Object invoke(MethodInvocation chain) throws Throwable { |
| return chain.proceed() + " WORLD"; |
| } |
| }); |
| } |
| }; |
| |
| private final Module noopInterceptorModule = |
| new AbstractModule() { |
| @Override |
| protected void configure() { |
| bindInterceptor( |
| any(), |
| any(), |
| new MethodInterceptor() { |
| @Override |
| public Object invoke(MethodInvocation chain) throws Throwable { |
| return chain.proceed(); |
| } |
| }); |
| } |
| }; |
| |
| public void testPackageVisibility() { |
| Injector injector = Guice.createInjector(new PackageVisibilityTestModule()); |
| injector.getInstance(PublicUserOfPackagePrivate.class); // This must pass. |
| } |
| |
| public void testInterceptedPackageVisibility() { |
| Injector injector = Guice.createInjector(interceptorModule, new PackageVisibilityTestModule()); |
| injector.getInstance(PublicUserOfPackagePrivate.class); // This must pass. |
| } |
| |
| public void testEnhancerNaming() { |
| Injector injector = Guice.createInjector(interceptorModule, new PackageVisibilityTestModule()); |
| PublicUserOfPackagePrivate pupp = injector.getInstance(PublicUserOfPackagePrivate.class); |
| assertTrue( |
| pupp.getClass() |
| .getName() |
| .startsWith(PublicUserOfPackagePrivate.class.getName() + "$$EnhancerByGuice$$")); |
| } |
| |
| // TODO(sameb): Figure out how to test FastClass naming tests. |
| |
| /** Custom URL classloader with basic visibility rules */ |
| static class TestVisibilityClassLoader extends URLClassLoader { |
| |
| final boolean hideInternals; |
| |
| TestVisibilityClassLoader(boolean hideInternals) { |
| this(TestVisibilityClassLoader.class.getClassLoader(), hideInternals); |
| } |
| |
| TestVisibilityClassLoader(ClassLoader classloader, boolean hideInternals) { |
| super(getClassPathUrls(), classloader); |
| this.hideInternals = hideInternals; |
| } |
| |
| /** |
| * Classic parent-delegating classloaders are meant to override findClass. However, |
| * non-delegating classloaders (as used in OSGi) instead override loadClass to provide support |
| * for "class-space" separation. |
| */ |
| @Override |
| protected Class<?> loadClass(final String name, final boolean resolve) |
| throws ClassNotFoundException { |
| |
| synchronized (this) { |
| // check our local cache to avoid duplicates |
| final Class<?> clazz = findLoadedClass(name); |
| if (clazz != null) { |
| return clazz; |
| } |
| } |
| |
| if (name.startsWith("java.")) { |
| |
| // standard bootdelegation of java.* |
| return super.loadClass(name, resolve); |
| |
| } else if (!name.contains(".internal.") && !name.contains(".cglib.")) { |
| |
| /* |
| * load public and test classes directly from the classpath - we don't |
| * delegate to our parent because then the loaded classes would also be |
| * able to see private internal Guice classes, as they are also loaded |
| * by the parent classloader. |
| */ |
| final Class<?> clazz = findClass(name); |
| if (resolve) { |
| resolveClass(clazz); |
| } |
| return clazz; |
| } |
| |
| // hide internal non-test classes |
| if (hideInternals) { |
| throw new ClassNotFoundException(); |
| } |
| return super.loadClass(name, resolve); |
| } |
| } |
| |
| /** as loaded by another class loader */ |
| private Class<ProxyTest> proxyTestClass; |
| |
| private Class<ProxyTestImpl> realClass; |
| private Module testModule; |
| |
| @Override |
| @SuppressWarnings("unchecked") |
| protected void setUp() throws Exception { |
| super.setUp(); |
| |
| ClassLoader testClassLoader = new TestVisibilityClassLoader(true); |
| proxyTestClass = (Class<ProxyTest>) testClassLoader.loadClass(ProxyTest.class.getName()); |
| realClass = (Class<ProxyTestImpl>) testClassLoader.loadClass(ProxyTestImpl.class.getName()); |
| |
| testModule = |
| new AbstractModule() { |
| @Override |
| public void configure() { |
| bind(proxyTestClass).to(realClass); |
| } |
| }; |
| } |
| |
| interface ProxyTest { |
| String sayHello(); |
| } |
| |
| /** |
| * Note: this class must be marked as public or protected so that the Guice custom classloader |
| * will intercept it. Private and implementation classes are not intercepted by the custom |
| * classloader. |
| * |
| * @see com.google.inject.internal.BytecodeGen.Visibility |
| */ |
| public static class ProxyTestImpl implements ProxyTest { |
| |
| static { |
| //System.out.println(ProxyTestImpl.class.getClassLoader()); |
| } |
| |
| @Override |
| public String sayHello() { |
| return "HELLO"; |
| } |
| } |
| |
| public void testProxyClassLoading() throws Exception { |
| Object testObject = |
| Guice.createInjector(interceptorModule, testModule).getInstance(proxyTestClass); |
| |
| // verify method interception still works |
| Method m = realClass.getMethod("sayHello"); |
| assertEquals("HELLO WORLD", m.invoke(testObject)); |
| } |
| |
| public void testSystemClassLoaderIsUsedIfProxiedClassUsesIt() { |
| ProxyTest testProxy = |
| Guice.createInjector( |
| interceptorModule, |
| new Module() { |
| @Override |
| public void configure(Binder binder) { |
| binder.bind(ProxyTest.class).to(ProxyTestImpl.class); |
| } |
| }) |
| .getInstance(ProxyTest.class); |
| |
| if (ProxyTest.class.getClassLoader() == systemClassLoader) { |
| assertSame(testProxy.getClass().getClassLoader(), systemClassLoader); |
| } else { |
| assertNotSame(testProxy.getClass().getClassLoader(), systemClassLoader); |
| } |
| } |
| |
| public void testProxyClassUnloading() { |
| Object testObject = |
| Guice.createInjector(interceptorModule, testModule).getInstance(proxyTestClass); |
| assertNotNull(testObject.getClass().getClassLoader()); |
| assertNotSame(testObject.getClass().getClassLoader(), systemClassLoader); |
| |
| // take a weak reference to the generated proxy class |
| WeakReference<Class<?>> clazzRef = new WeakReference<Class<?>>(testObject.getClass()); |
| |
| assertNotNull(clazzRef.get()); |
| |
| // null the proxy |
| testObject = null; |
| |
| /* |
| * this should be enough to queue the weak reference |
| * unless something is holding onto it accidentally. |
| */ |
| GcFinalization.awaitClear(clazzRef); |
| |
| // This test could be somewhat flaky when the GC isn't working. |
| // If it fails, run the test again to make sure it's failing reliably. |
| assertNull("Proxy class was not unloaded.", clazzRef.get()); |
| } |
| |
| public void testProxyingPackagePrivateMethods() { |
| Injector injector = Guice.createInjector(interceptorModule); |
| assertEquals("HI WORLD", injector.getInstance(PackageClassPackageMethod.class).sayHi()); |
| assertEquals("HI WORLD", injector.getInstance(PublicClassPackageMethod.class).sayHi()); |
| assertEquals("HI WORLD", injector.getInstance(ProtectedClassProtectedMethod.class).sayHi()); |
| } |
| |
| static class PackageClassPackageMethod { |
| String sayHi() { |
| return "HI"; |
| } |
| } |
| |
| public static class PublicClassPackageMethod { |
| String sayHi() { |
| return "HI"; |
| } |
| } |
| |
| protected static class ProtectedClassProtectedMethod { |
| protected String sayHi() { |
| return "HI"; |
| } |
| } |
| |
| static class Hidden {} |
| |
| public static class HiddenMethodReturn { |
| public Hidden method() { |
| return new Hidden(); |
| } |
| } |
| |
| public static class HiddenMethodParameter { |
| public void method(Hidden h) {} |
| } |
| |
| public void testClassLoaderBridging() throws Exception { |
| ClassLoader testClassLoader = new TestVisibilityClassLoader(false); |
| |
| Class hiddenMethodReturnClass = testClassLoader.loadClass(HiddenMethodReturn.class.getName()); |
| Class hiddenMethodParameterClass = |
| testClassLoader.loadClass(HiddenMethodParameter.class.getName()); |
| |
| Injector injector = Guice.createInjector(noopInterceptorModule); |
| |
| Class hiddenClass = testClassLoader.loadClass(Hidden.class.getName()); |
| Constructor ctor = hiddenClass.getDeclaredConstructor(); |
| |
| ctor.setAccessible(true); |
| |
| // don't use bridging for proxies with private parameters |
| Object o1 = injector.getInstance(hiddenMethodParameterClass); |
| o1.getClass().getDeclaredMethod("method", hiddenClass).invoke(o1, ctor.newInstance()); |
| |
| // don't use bridging for proxies with private return types |
| Object o2 = injector.getInstance(hiddenMethodReturnClass); |
| o2.getClass().getDeclaredMethod("method").invoke(o2); |
| } |
| |
| // This tests for a situation where a osgi bundle contains a version of guice. When guice |
| // generates a fast class it will use a bridge classloader |
| public void testFastClassUsesBridgeClassloader() throws Throwable { |
| Injector injector = Guice.createInjector(); |
| // These classes are all in the same classloader as guice itself, so other than the private one |
| // they can all be fast class invoked |
| injector.getInstance(PublicInject.class).assertIsFastClassInvoked(); |
| injector.getInstance(ProtectedInject.class).assertIsFastClassInvoked(); |
| injector.getInstance(PackagePrivateInject.class).assertIsFastClassInvoked(); |
| injector.getInstance(PrivateInject.class).assertIsReflectionInvoked(); |
| |
| // This classloader will load the types in an loader with a different version of guice/cglib |
| // this prevents the use of fastclass for all but the public types (where the bridge |
| // classloader can be used). |
| MultipleVersionsOfGuiceClassLoader fakeLoader = new MultipleVersionsOfGuiceClassLoader(); |
| injector |
| .getInstance(fakeLoader.loadLogCreatorType(PublicInject.class)) |
| .assertIsFastClassInvoked(); |
| injector |
| .getInstance(fakeLoader.loadLogCreatorType(ProtectedInject.class)) |
| .assertIsReflectionInvoked(); |
| injector |
| .getInstance(fakeLoader.loadLogCreatorType(PackagePrivateInject.class)) |
| .assertIsReflectionInvoked(); |
| injector |
| .getInstance(fakeLoader.loadLogCreatorType(PrivateInject.class)) |
| .assertIsReflectionInvoked(); |
| } |
| |
| // This classloader simulates an OSGI environment where a bundle has a conflicting definition of |
| // cglib (or guice). This is sort of the opposite of the BridgeClassloader and is meant to test |
| // its use. |
| static class MultipleVersionsOfGuiceClassLoader extends URLClassLoader { |
| MultipleVersionsOfGuiceClassLoader() { |
| this(MultipleVersionsOfGuiceClassLoader.class.getClassLoader()); |
| } |
| |
| MultipleVersionsOfGuiceClassLoader(ClassLoader classloader) { |
| super(getClassPathUrls(), classloader); |
| } |
| |
| public Class<? extends LogCreator> loadLogCreatorType(Class<? extends LogCreator> cls) |
| throws ClassNotFoundException { |
| return loadClass(cls.getName()).asSubclass(LogCreator.class); |
| } |
| |
| /** |
| * Classic parent-delegating classloaders are meant to override findClass. However, |
| * non-delegating classloaders (as used in OSGi) instead override loadClass to provide support |
| * for "class-space" separation. |
| */ |
| @Override |
| protected Class<?> loadClass(final String name, final boolean resolve) |
| throws ClassNotFoundException { |
| |
| synchronized (this) { |
| // check our local cache to avoid duplicates |
| final Class<?> clazz = findLoadedClass(name); |
| if (clazz != null) { |
| return clazz; |
| } |
| } |
| |
| if (name.startsWith("java.") |
| || name.startsWith("javax.") |
| || name.equals(LogCreator.class.getName()) |
| || (!name.startsWith("com.google.inject.") |
| && !name.contains(".cglib.") |
| && !name.startsWith("com.googlecode.guice"))) { |
| |
| // standard parent delegation |
| return super.loadClass(name, resolve); |
| |
| } else { |
| // load a new copy of the class |
| final Class<?> clazz = findClass(name); |
| if (resolve) { |
| resolveClass(clazz); |
| } |
| return clazz; |
| } |
| } |
| } |
| |
| public static class LogCreator { |
| final Throwable caller; |
| |
| public LogCreator() { |
| this.caller = new Throwable(); |
| } |
| |
| void assertIsFastClassInvoked() throws Throwable { |
| // 2 because the first 2 elements are |
| // LogCreator.<init>() |
| // Subclass.<init>() |
| if (!caller.getStackTrace()[2].getClassName().contains("$$FastClassByGuice$$")) { |
| throw new AssertionError("Caller was not FastClass").initCause(caller); |
| } |
| } |
| |
| void assertIsReflectionInvoked() throws Throwable { |
| // Scan for a call to Constructor.newInstance, but stop if we see the test itself. |
| for (StackTraceElement element : caller.getStackTrace()) { |
| if (element.getClassName().equals(BytecodeGenTest.class.getName())) { |
| // break when we hit the test method. |
| break; |
| } |
| if (element.getClassName().equals(Constructor.class.getName()) |
| && element.getMethodName().equals("newInstance")) { |
| return; |
| } |
| } |
| throw new AssertionError("Caller was not Constructor.newInstance").initCause(caller); |
| } |
| } |
| |
| public static class PublicInject extends LogCreator { |
| @Inject |
| public PublicInject() {} |
| } |
| |
| static class PackagePrivateInject extends LogCreator { |
| @Inject |
| PackagePrivateInject() {} |
| } |
| |
| protected static class ProtectedInject extends LogCreator { |
| @Inject |
| protected ProtectedInject() {} |
| } |
| |
| private static class PrivateInject extends LogCreator { |
| @Inject |
| private PrivateInject() {} |
| } |
| } |