Home | History | Annotate | Download | only in guice
      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 (at) 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 
     75   public void testPackageVisibility() {
     76     Injector injector = Guice.createInjector(new PackageVisibilityTestModule());
     77     injector.getInstance(PublicUserOfPackagePrivate.class); // This must pass.
     78   }
     79 
     80   public void testInterceptedPackageVisibility() {
     81     Injector injector = Guice.createInjector(interceptorModule, new PackageVisibilityTestModule());
     82     injector.getInstance(PublicUserOfPackagePrivate.class); // This must pass.
     83   }
     84 
     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 
    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
    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")
    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 {
    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 
    205     public String sayHello() {
    206       return "HELLO";
    207     }
    208   }
    209 
    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 
    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 
    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 
    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 {
    276     String sayHi() {
    277       return "HI";
    278     }
    279   }
    280 
    281   public static class PublicClassPackageMethod {
    282     String sayHi() {
    283       return "HI";
    284     }
    285   }
    286 
    287   protected static class ProtectedClassProtectedMethod {
    288     protected String sayHi() {
    289       return "HI";
    290     }
    291   }
    292 
    293   static class Hidden {
    294   }
    295 
    296   public static class HiddenMethodReturn {
    297     public Hidden method() {
    298       return new Hidden();
    299     }
    300   }
    301 
    302   public static class HiddenMethodParameter {
    303     public void method(Hidden h) {
    304     }
    305   }
    306 
    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