content: /*
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you 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 org.apache.aries.blueprint.proxy;
  
  import static org.junit.Assert.assertEquals;
  import static org.junit.Assert.assertFalse;
  import static org.junit.Assert.assertNotNull;
  import static org.junit.Assert.assertTrue;
  
  import java.io.ByteArrayOutputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.lang.reflect.InvocationHandler;
  import java.lang.reflect.InvocationTargetException;
  import java.lang.reflect.Method;
  import java.lang.reflect.Modifier;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  import java.util.concurrent.Callable;
  
  import org.apache.aries.proxy.FinalModifierException;
  import org.apache.aries.proxy.InvocationListener;
  import org.apache.aries.proxy.UnableToProxyException;
  import org.apache.aries.proxy.impl.AbstractProxyManager;
  import org.apache.aries.proxy.impl.AsmProxyManager;
  import org.apache.aries.proxy.impl.ProxyHandler;
  import org.apache.aries.proxy.impl.SingleInstanceDispatcher;
  import org.apache.aries.proxy.impl.gen.ProxySubclassGenerator;
  import org.apache.aries.proxy.impl.gen.ProxySubclassMethodHashSet;
  import org.apache.commons.io.IOUtils;
  import org.junit.Before;
  import org.junit.Test;
  
  /**
   * This class uses the {@link ProxySubclassGenerator} to test
   */
  public class ProxySubclassGeneratorTest extends AbstractProxyTest
  {
    private static final Class<?> FINAL_METHOD_CLASS = ProxyTestClassFinalMethod.class;
    private static final Class<?> FINAL_CLASS = ProxyTestClassFinal.class;
    private static final Class<?> GENERIC_CLASS = ProxyTestClassGeneric.class;
    private static final Class<?> COVARIANT_CLASS = ProxyTestClassCovariantOverride.class;
    private static ProxySubclassMethodHashSet<String> expectedMethods = new ProxySubclassMethodHashSet<String>(
        12);
    private InvocationHandler ih = null;
    Class<?> generatedProxySubclass = null;
    Object o = null;
  
    /**
     * @throws java.lang.Exception
     */
    @Before
    public void setUp() throws Exception
    {
      ih = new FakeInvocationHandler();
      ((FakeInvocationHandler)ih).setDelegate(getTestClass().newInstance());
      generatedProxySubclass = getGeneratedSubclass();
      o = getProxyInstance(generatedProxySubclass);
    }
  
  
    /**
     * Test that the methods found declared on the generated proxy subclass are
     * the ones that we expect.
     */
    @Test
    public void testExpectedMethods() throws Exception
    {
      Class<?> superclass = getTestClass();
  
      do {
        Method[] declaredMethods = superclass.getDeclaredMethods();
        List<Method> listOfDeclaredMethods = new ArrayList<Method>();
        for (Method m : declaredMethods) {
          int i = m.getModifiers();
          if (Modifier.isPrivate(i) || Modifier.isFinal(i)) {
            // private or final don't get added
          } else if (!(Modifier.isPublic(i) || Modifier.isPrivate(i) || Modifier.isProtected(i))) {
            // the method is default visibility, check the package
            if (m.getDeclaringClass().getPackage().equals(getTestClass().getPackage())) {
              // default vis with same package gets added
              listOfDeclaredMethods.add(m);
            }
          } else {
            listOfDeclaredMethods.add(m);
          }
        }
  
        declaredMethods = listOfDeclaredMethods.toArray(new Method[] {});
        ProxySubclassMethodHashSet<String> foundMethods = new ProxySubclassMethodHashSet<String>(
            declaredMethods.length);
        foundMethods.addMethodArray(declaredMethods);
        // as we are using a set we shouldn't get duplicates
        expectedMethods.addAll(foundMethods);
        superclass = superclass.getSuperclass();
      } while (superclass != null);
  
      // add the getter and setter for the invocation handler to the expected
      // set
      // and the unwrapObject method
      Method[] ihMethods = new Method[] {
          generatedProxySubclass.getMethod("setInvocationHandler",
              new Class[] { InvocationHandler.class }),
          generatedProxySubclass.getMethod("getInvocationHandler", new Class[] {}) };
      expectedMethods.addMethodArray(ihMethods);
  
      Method[] generatedProxySubclassMethods = generatedProxySubclass.getDeclaredMethods();
      ProxySubclassMethodHashSet<String> generatedMethods = new ProxySubclassMethodHashSet<String>(
          generatedProxySubclassMethods.length);
      generatedMethods.addMethodArray(generatedProxySubclassMethods);
  
      // check that all the methods we have generated were expected
      for (String gen : generatedMethods) {
        assertTrue("Unexpected method: " + gen, expectedMethods.contains(gen));
      }
      // check that all the expected methods were generated
      for (String exp : expectedMethods) {
        assertTrue("Method was not generated: " + exp, generatedMethods.contains(exp));
      }
      // check the sets were the same
      assertEquals("Sets were not the same", expectedMethods, generatedMethods);
  
    }
    
    /**
     * Test a method marked final
     */
    @Test
    public void testFinalMethod() throws Exception
    {
      try {
        ProxySubclassGenerator.getProxySubclass(FINAL_METHOD_CLASS);
      } catch (FinalModifierException e) {
        assertFalse("Should have found final method not final class", e.isFinalClass());
      }
    }
  
    /**
     * Test a class marked final
     */
    @Test
    public void testFinalClass() throws Exception
    {
      try {
        ProxySubclassGenerator.getProxySubclass(FINAL_CLASS);
      } catch (FinalModifierException e) {
        assertTrue("Should have found final class", e.isFinalClass());
      }
    }
  
    /**
     * Test a private constructor
     */
    @Test
    public void testPrivateConstructor() throws Exception
    {
      Object o = ProxySubclassGenerator.newProxySubclassInstance(
          ProxyTestClassPrivateConstructor.class, ih);
      assertNotNull("The new instance was null", o);
  
    }
  
    /**
     * Test a generating proxy class of class with package access constructor.
     */
    @SuppressWarnings("unchecked")
    @Test
    public void testPackageAccessCtor() throws Exception  {
        Class<ProxyTestClassPackageAccessCtor> proxyClass = 
                (Class<ProxyTestClassPackageAccessCtor>) ProxySubclassGenerator.getProxySubclass(ProxyTestClassPackageAccessCtor.class);
        ProxyTestClassPackageAccessCtor proxy = (ProxyTestClassPackageAccessCtor) getProxyInstance(proxyClass); 
        assertNotNull("The new instance was null", proxy);
    }
  //  /**
  //   * Test object equality between real and proxy using a Collaborator
  //   */
  //  @Test
  //  public void testObjectEquality() throws Exception
  //  {
  //    Object delegate = getTestClass().newInstance();
  //    InvocationHandler collaborator = new Collaborator(null, null, AsmInterceptorWrapper.passThrough(delegate));
  //    Object o = ProxySubclassGenerator.newProxySubclassInstance(getTestClass(), collaborator);
  //    //Calling equals on the proxy with an arg of the unwrapped object should be true
  //    assertTrue("The proxy object should be equal to its delegate",o.equals(delegate));
  //    InvocationHandler collaborator2 = new Collaborator(null, null, AsmInterceptorWrapper.passThrough(delegate));
  //    Object o2 = ProxySubclassGenerator.newProxySubclassInstance(getTestClass(), collaborator2);
  //    //The proxy of a delegate should equal another proxy of the same delegate
  //    assertTrue("The proxy object should be equal to another proxy instance of the same delegate", o2.equals(o));
  //  }
  //  
  //  private static class ProxyTestOverridesFinalize {
  //      public boolean finalizeCalled = false;
  //      
  //      @Override
  //      protected void finalize() {
  //          finalizeCalled = true;
  //      }
  //  }
  //  
  //  @Test
  //  public void testFinalizeNotCalled() throws Exception {
  //      ProxyTestOverridesFinalize testObj = new ProxyTestOverridesFinalize();
  //      InvocationHandler ih = new Collaborator(null, null, AsmInterceptorWrapper.passThrough(testObj));
  //      Object o = ProxySubclassGenerator.newProxySubclassInstance(ProxyTestOverridesFinalize.class, ih);
  //      
  //      Method m = o.getClass().getDeclaredMethod("finalize");
  //      m.setAccessible(true);
  //      m.invoke(o);
  //      
  //      assertFalse(testObj.finalizeCalled);
  //  }
    
  
    /**
     * Test a covariant override method
     */
    @Test
    public void testCovariant() throws Exception
    {
      ((FakeInvocationHandler)ih).setDelegate(COVARIANT_CLASS.newInstance());
      o = ProxySubclassGenerator.newProxySubclassInstance(COVARIANT_CLASS, ih);
      generatedProxySubclass = o.getClass();
      Method m = generatedProxySubclass.getDeclaredMethod("getCovariant", new Class[] {});
      Object returned = m.invoke(o);
      assertTrue("Object was of wrong type: " + returned.getClass().getSimpleName(), COVARIANT_CLASS
          .isInstance(returned));
    }
    
    /**
     * Test a covariant override method
     */
    @Test
    public void testGenerics() throws Exception
    {
      ((FakeInvocationHandler)ih).setDelegate(GENERIC_CLASS.newInstance());
      super.testGenerics();
    }
  
    @SuppressWarnings("unchecked")
  @Test
    public void testClassLoaders() throws Exception {
      ClassLoader clA = new LimitedClassLoader("org.apache.aries.proxy.test.a", null, null);
      ClassLoader clB = new LimitedClassLoader("org.apache.aries.proxy.test.b", "org.apache.aries.proxy.test.a", clA);
      ClassLoader clC = new LimitedClassLoader("org.apache.aries.proxy.test.c", "org.apache.aries.proxy.test.b", clB);
  
      Class<?> clazzA = clA.loadClass("org.apache.aries.proxy.test.a.ProxyTestClassA");
      Class<?> clazzB = clB.loadClass("org.apache.aries.proxy.test.b.ProxyTestClassB");
      Class<?> clazzC = clC.loadClass("org.apache.aries.proxy.test.c.ProxyTestClassC");
  
      final Object object = clazzC.getConstructor(String.class).newInstance("hello");
  
      o = new AsmProxyManager().createNewProxy(null, Arrays.asList(clazzA, clazzB, clazzC), constantly(object), null);
      generatedProxySubclass = o.getClass();
      Method m = generatedProxySubclass.getDeclaredMethod("hello", new Class[] {});
      Object returned = m.invoke(o);
      assertEquals("hello", returned);
    }
  
    private static class LimitedClassLoader extends ClassLoader {
      Set<String> providedPackages;
      Set<String> importedPackages;
      List<ClassLoader> parents;
  
      public LimitedClassLoader(String provided, String imported, ClassLoader parent) {
        providedPackages = Collections.singleton(provided);
        importedPackages = imported != null ? Collections.singleton(imported) : Collections.<String>emptySet();
        parents = parent != null ? Collections.singletonList(parent) : Collections.<ClassLoader>emptyList();
      }
      
      final Map<String, Object> clLocks = new HashMap<String, Object>();
      protected synchronized Object getClassLoadingLock (String name) {
      	if (!clLocks.containsKey(name)) { 
      		clLocks.put(name, new Object());
      	}
      	return clLocks.get(name);
      }
  
      @Override
      protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
          // First, check if the class has already been loaded
          Class<?> c = findLoadedClass(name);
          if (c == null) {
            String pkg = name.substring(0, name.lastIndexOf('.'));
            if (pkg.startsWith("java.") || pkg.startsWith("sun.reflect")) {
              return getClass().getClassLoader().loadClass(name);
            } else if (providedPackages.contains(pkg)) {
              c = findClass(name);
            } else if (importedPackages.contains(pkg)) {
              for (ClassLoader cl : parents) {
                try {
                  c = cl.loadClass(name);
                } catch (ClassNotFoundException e) {
                  // Ignore
                }
              }
            }
          }
          if (c == null) {
            throw new ClassNotFoundException(name);
          }
          if (resolve) {
            resolveClass(c);
          }
          return c;
        }
      }
  
      @Override
      protected Class<?> findClass(String name) throws ClassNotFoundException {
        String pkg = name.substring(0, name.lastIndexOf('.'));
        if (getPackage(pkg) == null) {
          definePackage(pkg, null, null, null, null, null, null, null);
        }
        String path = name.replace('.', '/').concat(".class");
        InputStream is = LimitedClassLoader.class.getClassLoader().getResourceAsStream(path);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
          IOUtils.copy(is, baos);
        } catch (IOException e) {
          throw new ClassNotFoundException(name, e);
        }
        byte[] buf = baos.toByteArray();
        return defineClass(name, buf, 0, buf.length);
      }
    }
    
    private Class<?> getGeneratedSubclass() throws Exception
    {
      return getProxyClass(getTestClass());
    }
  
    private class FakeInvocationHandler implements InvocationHandler
    {
      private Object delegate = null;
      /*
       * (non-Javadoc)
       * 
       * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object,
       * java.lang.reflect.Method, java.lang.Object[])
       */
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
      {
        try {
        Object result = (delegate instanceof Callable) ? 
            method.invoke(((Callable<?>)delegate).call(), args) : 
            method.invoke(delegate, args) ;
        return result;
        } catch (InvocationTargetException ite) {
          throw ite.getTargetException();
        }
      }
  
      void setDelegate(Object delegate){
        this.delegate = delegate;
      }
      
    }
  
    @Override
    protected Object getProxyInstance(Class<?> proxyClass) {
      return getProxyInstance(proxyClass, ih);
    }
    
    private Object getProxyInstance(Class<?> proxyClass, InvocationHandler ih) {
      try {
        if(proxyClass.equals(ProxyTestClassChildOfAbstract.class))
          return proxyClass.newInstance();
        
        Object proxyInstance = proxyClass.getConstructor().newInstance();
        Method setIH = proxyInstance.getClass().getMethod("setInvocationHandler", InvocationHandler.class);
        setIH.invoke(proxyInstance, ih);
        return proxyInstance;
      } catch (Exception e) {
        return null;
      }
    }
  
    @Override
    protected Class<?> getProxyClass(Class<?> clazz) {
      try {
        return ProxySubclassGenerator.getProxySubclass(clazz);
      } catch (UnableToProxyException e) {
        return null;
      }
    }
  
  
    @Override
    protected Object setDelegate(Object proxy, Callable<Object> dispatcher) {
      AbstractProxyManager apm = new AsmProxyManager();
      return getProxyInstance(proxy.getClass(), new ProxyHandler(apm, dispatcher, null));  
    }
  
  
    @Override
    protected Object getProxyInstance(Class<?> proxyClass,
        InvocationListener listener) {
      AbstractProxyManager apm = new AsmProxyManager();
      return getProxyInstance(proxyClass, new ProxyHandler(apm, new SingleInstanceDispatcher(getProxyInstance(proxyClass)), listener));  
    }
  
  
    @Override
    protected Object getP3() {
      return new ProxyTestClassGeneral();
    }
  
    private Callable<Object> constantly(final Object result) {
      return new Callable<Object>() {
        public Object call() throws Exception {
          return result;
        }
      };
    }
  }
  