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.assertThrows;
  import static org.junit.Assert.assertTrue;
  import static org.mockito.Mockito.mock;
  import static org.mockito.Mockito.when;
  
  import java.io.ByteArrayOutputStream;
  import java.io.InputStream;
  import java.io.ObjectOutputStream;
  import java.lang.reflect.Method;
  import java.net.URL;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collection;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.concurrent.Callable;
  
  import org.apache.aries.blueprint.proxy.ProxyTestClassInnerClasses.ProxyTestClassInner;
  import org.apache.aries.blueprint.proxy.ProxyTestClassInnerClasses.ProxyTestClassStaticInner;
  import org.apache.aries.blueprint.proxy.ProxyTestClassInnerClasses.ProxyTestClassUnweavableInnerChild;
  import org.apache.aries.blueprint.proxy.pkg.ProxyTestClassUnweavableSuperWithDefaultMethodWrongPackageParent;
  import org.apache.aries.proxy.FinalModifierException;
  import org.apache.aries.proxy.InvocationListener;
  import org.apache.aries.proxy.UnableToProxyException;
  import org.apache.aries.proxy.impl.AsmProxyManager;
  import org.apache.aries.proxy.impl.SingleInstanceDispatcher;
  import org.apache.aries.proxy.impl.SystemModuleClassLoader;
  import org.apache.aries.proxy.impl.gen.ProxySubclassMethodHashSet;
  import org.apache.aries.proxy.impl.weaving.WovenProxyGenerator;
  import org.apache.aries.proxy.weaving.WovenProxy;
  import org.junit.BeforeClass;
  import org.junit.Test;
  import org.osgi.framework.Bundle;
  import org.osgi.framework.wiring.BundleWiring;
  
  
  @SuppressWarnings({"unchecked", "rawtypes"})
  public class WovenProxyGeneratorTest extends AbstractProxyTest
  {
    private static final String hexPattern = "[0-9_a-f]";
    private static final int[] uuid_pattern = new int[] {8,4,4,4,12};
    private static final String regexp;
    
    static {
      StringBuilder sb = new StringBuilder(".*");
      for(int i : uuid_pattern) {
        for(int j = 0; j < i; j++){
          sb.append(hexPattern);
        }
        sb.append("_");
      }
      sb.deleteCharAt(sb.length() -1);
      sb.append("\\d*");
      regexp = sb.toString();
    }
    
    /** An array of classes that will be woven - note no UnweavableParents should be in here! */
    private static final List<Class<?>> CLASSES = Arrays.asList(new Class<?>[]{ProxyTestClassGeneral.class, ProxyTestClassSuper.class,
      ProxyTestClassFinalMethod.class, ProxyTestClassFinal.class, ProxyTestClassGeneric.class,
      ProxyTestClassGenericSuper.class, ProxyTestClassCovariant.class, ProxyTestClassCovariantOverride.class,
      ProxyTestClassUnweavableChild.class, ProxyTestClassUnweavableSibling.class, ProxyTestClassInner.class, 
      ProxyTestClassStaticInner.class, ProxyTestClassUnweavableInnerChild.class, 
      ProxyTestClassUnweavableChildWithFinalMethodParent.class, 
      ProxyTestClassUnweavableChildWithDefaultMethodWrongPackageParent.class, 
      ProxyTestClassSerializable.class, ProxyTestClassSerializableWithSVUID.class,
      ProxyTestClassSerializableChild.class, ProxyTestClassSerializableInterface.class,
      ProxyTestClassStaticInitOfChild.class, ProxyTestClassAbstract.class});
    
    /** An array of classes that are loaded by the WeavingLoader, but not actually woven **/
    private static final List<Class<?>> OTHER_CLASSES = Arrays.asList(new Class<?>[] {ProxyTestClassUnweavableSuper.class,
  		  ProxyTestClassStaticInitOfChildParent.class, ProxyTestClassChildOfAbstract.class});
   
    private static final Map<String, byte[]> rawClasses = new HashMap<String, byte[]>();
    
    protected static final ClassLoader weavingLoader = new SystemModuleClassLoader() {
      public Class<?> loadClass(String className)  throws ClassNotFoundException
      {
        return loadClass(className, false);
      }
      public Class<?> loadClass(String className, boolean b) throws ClassNotFoundException
      {
        if (!className.startsWith("org.apache.aries.blueprint.proxy.ProxyTest")){
          return Class.forName(className);
        }
        
        Class<?> clazz = findLoadedClass(className);
        if(clazz != null)
          return clazz;
        
        byte[] bytes = rawClasses.get(className);
        if(bytes == null)
          return super.loadClass(className, b);
        
        boolean weave = false;
        
        for(Class<?> c : CLASSES) {
          if(c.getName().equals(className)) {
            weave = true;
            break;
          }
        }
        if(weave)
          bytes = WovenProxyGenerator.getWovenProxy(bytes, this);
        
        return defineClass(className, bytes, 0, bytes.length);
      }
      
      protected URL findResource(String resName) {
        return WovenProxyGeneratorTest.class.getResource(resName);
      }
    };
     
    /**
     * @throws java.lang.Exception
     */
    @BeforeClass
    public static void setUp() throws Exception
    {
      List<Class<?>> classes = new ArrayList<Class<?>>(CLASSES.size() + OTHER_CLASSES.size());
      
      classes.addAll(CLASSES);
      classes.addAll(OTHER_CLASSES);
      
      for(Class<?> clazz : classes) {
        InputStream is = clazz.getClassLoader().getResourceAsStream(
            clazz.getName().replace('.', '/') + ".class");
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        
        byte[] buffer = new byte[2048];
        int read = is.read(buffer);
        while(read != -1) {
          baos.write(buffer, 0, read);
          read = is.read(buffer);
        }
        rawClasses.put(clazz.getName(), baos.toByteArray());
      }
    }
  
    /**
     * This test uses the WovenProxyGenerator to generate and load the specified getTestClass().
     * 
     * Once the subclass is generated we check that it wasn't null. 
     * 
     * Test method for
     * {@link WovenProxyGenerator#getProxySubclass(byte[], String)}.
     */
    @Test
    public void testGenerateAndLoadProxy() throws Exception
    {
      super.testGenerateAndLoadProxy();
      assertTrue("Should be a WovenProxy", WovenProxy.class.isAssignableFrom(getProxyClass(getTestClass())));
    }
  
    /**
     * Test that the methods found declared on the generated proxy are
     * the ones that we expect.
     */
    @Test
    public void testExpectedMethods() throws Exception
    {
      ProxySubclassMethodHashSet<String> originalMethods = getMethods(getTestClass());
  
      ProxySubclassMethodHashSet<String> generatedMethods = getMethods(weavingLoader.
          loadClass(getTestClass().getName()));
  
      // check that all the methods we have generated were expected
      for (String gen : generatedMethods) {
        assertTrue("Unexpected method: " + gen, originalMethods.contains(gen));
      }
      // check that all the expected methods were generated
      for (String exp : originalMethods) {
        assertTrue("Method was not generated: " + exp, generatedMethods.contains(exp));
      }
      // check the sets were the same
      assertEquals("Sets were not the same", originalMethods, generatedMethods);
    }
  
    private ProxySubclassMethodHashSet<String> getMethods(Class<?> clazz) {
      
      ProxySubclassMethodHashSet<String> foundMethods = 
        new ProxySubclassMethodHashSet<String>(12);
      do {
        Method[] declaredMethods = clazz.getDeclaredMethods();
        List<Method> listOfDeclaredMethods = new ArrayList<Method>();
        for (Method m : declaredMethods) {
          if(m.getName().startsWith(WovenProxy.class.getName().replace('.', '_')) ||
              m.getName().startsWith("getListener") || m.getName().startsWith("getInvocationTarget") ||
              //four hex digits
              m.getName().matches(regexp))
            continue;
          
          listOfDeclaredMethods.add(m);
        }
        declaredMethods = listOfDeclaredMethods.toArray(new Method[] {});
        foundMethods.addMethodArray(declaredMethods);
        clazz = clazz.getSuperclass();
      } while (clazz != null);
      return foundMethods;
    }
  
    /**
     * Test a method marked final
     */
    @Test
    public void testFinalMethod() throws Exception
    {
      assertNotNull(weavingLoader.loadClass(ProxyTestClassFinalMethod.class
          .getName()));
    }
  
    /**
     * Test a class marked final
     */
    @Test
    public void testFinalClass() throws Exception
    {
      assertNotNull(weavingLoader.loadClass(ProxyTestClassFinal.class
          .getName()));
    }
  
    /**
     * Test a private constructor
     */
    @Test
    public void testPrivateConstructor() throws Exception
    {
      assertNotNull(weavingLoader.loadClass(ProxyTestClassFinal.class
          .getName()));
    }
    
    /**
     * Test a class whose super couldn't be woven
     */
    @Test
    public void testUnweavableSuper() throws Exception
    {
      Class<?> woven = getProxyClass(ProxyTestClassUnweavableChild.class);
      
      assertNotNull(woven);
      assertNotNull(getProxyInstance(woven));
      
      TestListener tl = new TestListener();
      Object ptcuc = getProxyInstance(woven, tl);
      assertCalled(tl, false, false, false);
      
      Method m = ptcuc.getClass().getMethod("doStuff");
      
      assertEquals("Hi!", m.invoke(ptcuc));
      
      assertCalled(tl, true, true, false);
      
      assertEquals(ProxyTestClassUnweavableGrandParent.class.getMethod("doStuff"), 
          tl.getLastMethod());
      
      tl.clear();
  
      //Because default access works on the package, and we are defined on a different classloader
      //we have to call setAccessible...
      
      m = getDeclaredMethod(ProxyTestClassUnweavableChild.class, "doStuff2");
      m.setAccessible(true);
      assertEquals("Hello!", m.invoke(ptcuc));
      
      assertCalled(tl, true, true, false);
      
      assertEquals(weavingLoader.loadClass(ProxyTestClassUnweavableSuper.class.getName()).getDeclaredMethod("doStuff2"),
      		tl.getLastMethod());
    }
    
    @Test
    public void testUnweavableSuperWithNoNoargsAllTheWay() throws Exception
    {
      RuntimeException re = assertThrows(RuntimeException.class, () ->
        getProxyClass(ProxyTestClassUnweavableSibling.class));
        assertTrue(re.getCause() instanceof UnableToProxyException);
        assertEquals(ProxyTestClassUnweavableSibling.class.getName(),
            ((UnableToProxyException)re.getCause()).getClassName());
    }  
    
    /**
     * Test a class whose super couldn't be woven
     */
    @Test
    public void testUnweavableSuperWithFinalMethod() throws Exception
    {
        RuntimeException re = assertThrows(RuntimeException.class, () -> {
            getProxyClass(ProxyTestClassUnweavableChildWithFinalMethodParent.class);
        });
  
        assertTrue(re.getCause() instanceof FinalModifierException);
        assertEquals(ProxyTestClassUnweavableSuperWithFinalMethod.class.getName(),
            ((FinalModifierException)re.getCause()).getClassName());
        assertEquals("doStuff2", ((FinalModifierException)re.getCause())
            .getFinalMethods());
    }
    
    /**
     * Test a class whose super couldn't be woven
     */
    @Test
    public void testUnweavableSuperWithDefaultMethodInWrongPackage() throws Exception
    {
        RuntimeException re = assertThrows(RuntimeException.class, () -> {
                    getProxyClass(ProxyTestClassUnweavableChildWithDefaultMethodWrongPackageParent.class);
                });
        assertTrue(re.getCause() instanceof UnableToProxyException);
        assertEquals(ProxyTestClassUnweavableSuperWithDefaultMethodWrongPackageParent
            .class.getName(), ((UnableToProxyException)re.getCause()).getClassName());
    }
    
    @Test
    public void testInnerWithNoParentNoArgs() throws Exception {
      //An inner class has no no-args (the parent gets added as an arg) so we can't
      //get an instance
        RuntimeException re = assertThrows(RuntimeException.class, () -> {
        getProxyClass(ProxyTestClassUnweavableInnerChild.class);
      });
        assertTrue(re.getCause() instanceof UnableToProxyException);
        assertEquals(ProxyTestClassUnweavableInnerChild.class.getName(), 
            ((UnableToProxyException)re.getCause()).getClassName());
    }
    
    @Test(expected=NoSuchFieldException.class)
    public void testNonSerializableClassHasNoGeneratedSerialVersionUID() throws Exception {
      Class<?> woven = getProxyClass(getTestClass());
      woven.getDeclaredField("serialVersionUID");
    }
    
    @Test
    public void testSerialization() throws Exception {
      
      ProxyTestClassSerializable in = new ProxyTestClassSerializable();
      in.value = 5;
      
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      ObjectOutputStream oos = new ObjectOutputStream(baos);
      oos.writeObject(in);
      
      ProxyTestClassSerializable.checkDeserialization(baos.toByteArray(), 5);
  
      Class<?> woven = getProxyClass(ProxyTestClassSerializable.class);
      
      woven.getMethod("checkDeserialization", byte[].class, int.class).invoke(null, baos.toByteArray(), 5);
    }
    
    @Test
    public void testInheritedSerialization() throws Exception {
      
      ProxyTestClassSerializableChild in = new ProxyTestClassSerializableChild();
      in.value = 4;
      
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      ObjectOutputStream oos = new ObjectOutputStream(baos);
      oos.writeObject(in);
      
      ProxyTestClassSerializable.checkDeserialization(baos.toByteArray(), 4);
  
      Class<?> woven = getProxyClass(ProxyTestClassSerializable.class);
      
      woven.getMethod("checkDeserialization", byte[].class, int.class).invoke(null, baos.toByteArray(), 4);
    }
    
    @Test
    public void testInterfaceInheritedSerialization() throws Exception {
      
      ProxyTestClassSerializableInterface in = new ProxyTestClassSerializableInterface();
      in.value = 3;
      
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      ObjectOutputStream oos = new ObjectOutputStream(baos);
      oos.writeObject(in);
      
      ProxyTestClassSerializableInterface.checkDeserialization(baos.toByteArray(), 3);
  
      Class<?> woven = getProxyClass(ProxyTestClassSerializableInterface.class);
      
      woven.getMethod("checkDeserialization", byte[].class, int.class).invoke(null, baos.toByteArray(), 3);
    }
    
    @Test
    public void testGeneratedSVUIDisSynthetic() throws Exception {
      
      Class<?> woven = getProxyClass(ProxyTestClassSerializable.class);
      
      assertTrue(woven.getDeclaredField("serialVersionUID").isSynthetic());
      
      woven = getProxyClass(ProxyTestClassSerializableWithSVUID.class);
      
      assertFalse(woven.getDeclaredField("serialVersionUID").isSynthetic());
    }
    
    /**
     * This test covers a weird case on Mac VMs where we sometimes
     * get a ClassCircularityError if a static initializer in a
     * non-woven superclass references a subclass that's being
     * woven, and gets triggered by the weaving process. Not known
     * to fail on IBM or Sun/Oracle VMs
     */
    @Test
    public void testSuperStaticInitOfChild() throws Exception {
      Class<?> parent = weavingLoader.loadClass(ProxyTestClassStaticInitOfChildParent.class.getName());
      parent.getMethod("doStuff").invoke(null);
    }
  
    @Override
    protected Object getProxyInstance(Class<?> proxyClass) {
      try {
        if(proxyClass.getName().equals(ProxyTestClassAbstract.class.getName())) {
          Collection<Class<?>> coll = new ArrayList<Class<?>>();
          coll.add(proxyClass);
          return new AsmProxyManager().createNewProxy(null, coll, new Callable() {
            public Object call() throws Exception {
              return null;
            }}, null);
        }
        return proxyClass.newInstance();
      } catch (Exception e) {
        return null;
      }
    }
  
    @Override
    protected Class<?> getProxyClass(Class<?> clazz) {
      try {
        return weavingLoader.loadClass(clazz.getName());
      } catch (ClassNotFoundException e) {
        return null;
      }
    }
  
    @Override
    protected Object setDelegate(Object proxy, Callable<Object> dispatcher) {
      return ((WovenProxy) proxy).
      org_apache_aries_proxy_weaving_WovenProxy_createNewProxyInstance(
          dispatcher, null);
    }
  
    @Override
    protected Object getProxyInstance(Class<?> proxyClass,
        InvocationListener listener) {
      WovenProxy proxy = (WovenProxy) getProxyInstance(proxyClass);
      proxy = proxy.org_apache_aries_proxy_weaving_WovenProxy_createNewProxyInstance(
          new SingleInstanceDispatcher(proxy), listener);
      return proxy;
    }
    
    protected Object getP3() {
      return getProxyInstance(getProxyClass(getTestClass()));
    }
    
    /**
     * This tests that the Synthesizer ran correctly and the packaged
     * WovenProxy class has been modified with the synthetic modifier
     */
    @Test
    public void testWovenProxyIsSynthetic(){
      assertTrue(WovenProxy.class.isSynthetic());
    }
    
    /**
     * This test checks that we can add interfaces to classes that don't implement
     * them using dynamic subclassing. This is a little odd, but it came for
     * free with support for proxying abstract classes!
     * @throws Exception 
     */
    @Test
    public void testWovenClassPlusInterfaces() throws Exception {
      Bundle b = mock(Bundle.class);
      BundleWiring wiring = getWiring(weavingLoader);
      when(b.adapt(BundleWiring.class)).thenReturn(wiring);
  
      Object toCall = new AsmProxyManager().createDelegatingProxy(b, Arrays.asList(
          getProxyClass(ProxyTestClassAbstract.class), Callable.class), new Callable() {
  
            public Object call() throws Exception {
              return weavingLoader.loadClass(ProxyTestClassChildOfAbstract.class.getName()).newInstance();
            }
        
      }, null);
      
      //Should proxy the abstract method on the class
      Method m = getProxyClass(ProxyTestClassAbstract.class).getMethod("getMessage");
      assertEquals("Working", m.invoke(toCall));
      
      //Should be a callable too!
      assertEquals("Callable Works too!", ((Callable)toCall).call());
      
    }
  }
  
  