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.lang.reflect.InvocationTargetException;
  import java.lang.reflect.Method;
  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.proxy.InvocationListener;
  import org.apache.aries.proxy.impl.SingleInstanceDispatcher;
  import org.junit.Test;
  import org.osgi.framework.wiring.BundleWiring;
  
  public abstract class AbstractProxyTest {
  
    protected static class TestListener implements InvocationListener {
    
      boolean preInvoke = false;
      boolean postInvoke = false;
      boolean postInvokeExceptionalReturn = false;
      private Method m;
      private Object token;
      private Throwable e;
      
      public Object preInvoke(Object proxy, Method m, Object[] args)
          throws Throwable {
        preInvoke = true;
        token = new Object();
        this.m = m;
        return token;
      }
    
      public void postInvoke(Object token, Object proxy, Method m,
          Object returnValue) throws Throwable {
        postInvoke = this.token == token && this.m == m;
      }
    
      public void postInvokeExceptionalReturn(Object token, Object proxy,
          Method m, Throwable exception) throws Throwable {
        postInvokeExceptionalReturn = this.token == token && this.m == m;
        e = exception;
      }
      
      public void clear() {
        preInvoke = false;
        postInvoke = false;
        postInvokeExceptionalReturn = false;
        token = null;
        m = null;
        e = null;
      }
      
      public Method getLastMethod() {
        return m;
      }
      
      public Throwable getLastThrowable() {
        return e;
      }
    }
  
    protected abstract Object getProxyInstance(Class<?> proxyClass);
    protected abstract Object getProxyInstance(Class<?> proxyClass, InvocationListener listener);
    protected abstract Class<?> getProxyClass(Class<?> clazz);
    protected abstract Object setDelegate(Object proxy, Callable<Object> dispatcher);
    
    protected Class<?> getTestClass() {
  	  return ProxyTestClassGeneral.class;
    }
    
    protected Method getDeclaredMethod(Class<?> testClass, String name,
  		Class<?>... classes) throws Exception {
  	return getProxyClass(testClass).getDeclaredMethod(name, classes);
    }
    
  /**
     * This test uses the ProxySubclassGenerator to generate and load a subclass
     * of the specified getTestClass().
     * 
     * Once the subclass is generated we check that it wasn't null. We check
     * that the InvocationHandler constructor doesn't return a null object
     * either
     * 
     * Test method for
     * {@link org.apache.aries.proxy.impl.ProxySubclassGenerator#generateAndLoadSubclass()}
     * .
     */
    @Test
    public void testGenerateAndLoadProxy() throws Exception
    {
      assertNotNull("Generated proxy subclass was null", getProxyClass(getTestClass()));
      assertNotNull("Generated proxy subclass instance was null", getProxyInstance(getProxyClass(getTestClass())));
    }
    /**
     * Test a basic method invocation on the proxy subclass
     */
    @Test
    public void testMethodInvocation() throws Exception {
      Method m = getDeclaredMethod(getTestClass(), "testMethod", String.class,
          int.class, Object.class);
      String x = "x";
      String returned = (String) m.invoke(getProxyInstance(getProxyClass(getTestClass())), x, 1, new Object());
      assertEquals("Object returned from invocation was not correct.", x, returned);
    }
    
    /**
     * Test different argument types on a method invocation
     */
    @Test
    public void testMethodArgs() throws Exception
    {
      Method m = getDeclaredMethod(getTestClass(), "testArgs", double.class,
          short.class, long.class, char.class, byte.class, boolean.class);
      Character xc = Character.valueOf('x');
      String x = xc.toString();
      String returned = (String) m.invoke(getProxyInstance(getProxyClass(getTestClass())), Double.MAX_VALUE, Short.MIN_VALUE, Long.MAX_VALUE, xc
          .charValue(), Byte.MIN_VALUE, false);
      assertEquals("Object returned from invocation was not correct.", x, returned);
    }
    
    /**
     * Test a method that returns void
     */
    @Test
    public void testReturnVoid() throws Exception
    {
      Method m = getDeclaredMethod(getTestClass(), "testReturnVoid");
      //for these weaving tests we are loading the woven test classes on a different classloader
      //to this class so we need to set the method accessible
      m.setAccessible(true);
      m.invoke(getProxyInstance(getProxyClass(getTestClass())));
    }
  
    /**
     * Test a method that returns an int
     */
    @Test
    public void testReturnInt() throws Exception
    {
      Method m = getDeclaredMethod(getTestClass(), "testReturnInt");
      //for these weaving tests we are loading the woven test classes on a different classloader
      //to this class so we need to set the method accessible
      m.setAccessible(true);
      Integer returned = (Integer) m.invoke(getProxyInstance(getProxyClass(getTestClass())));
      assertEquals("Expected object was not returned from invocation", Integer.valueOf(17), returned);
    }
  
    /**
     * Test a method that returns an Integer
     */
    @Test
    public void testReturnInteger() throws Exception
    {
      Method m = getDeclaredMethod(getTestClass(), "testReturnInteger");
      Integer returned = (Integer) m.invoke(getProxyInstance(getProxyClass(getTestClass())));
      assertEquals("Expected object was not returned from invocation", Integer.valueOf(1), returned);
    }
  
    /**
     * Test a public method declared higher up the superclass hierarchy
     */
    @Test
    public void testPublicHierarchyMethod() throws Exception
    {
      Method m = null;
      try {
        m = getDeclaredMethod(getTestClass(), "bMethod");
      } catch (NoSuchMethodException nsme) {
        m = getProxyClass(getTestClass()).getSuperclass().getDeclaredMethod("bMethod");
      }
      m.invoke(getProxyInstance(getProxyClass(getTestClass())));
    }
  
    /**
     * Test a protected method declared higher up the superclass hierarchy
     */
    @Test
    public void testProtectedHierarchyMethod() throws Exception
    {
      Method m = null;
      try {
        m = getDeclaredMethod(getTestClass(), "bProMethod");
      } catch (NoSuchMethodException nsme) {
        m = getProxyClass(getTestClass()).getSuperclass().getDeclaredMethod("bProMethod");
      }
      //for these weaving tests we are loading the woven test classes on a different classloader
      //to this class so we need to set the method accessible
      m.setAccessible(true);
      m.invoke(getProxyInstance(getProxyClass(getTestClass())));
    }
    
    /**
     * Test a default method declared higher up the superclass hierarchy
     */
    @Test
    public void testDefaultHierarchyMethod() throws Exception
    {
      Method m = null;
      try {
        m = getDeclaredMethod(getTestClass(), "bDefMethod");
      } catch (NoSuchMethodException nsme) {
        m = getProxyClass(getTestClass()).getSuperclass().getDeclaredMethod("bDefMethod", new Class[] {});
      }
      //for these weaving tests we are loading the woven test classes on a different classloader
      //to this class so we need to set the method accessible
      m.setAccessible(true);
      m.invoke(getProxyInstance(getProxyClass(getTestClass())));
    }
  
    /**
     * Test a covariant override method
     */
    @Test
    public void testCovariant() throws Exception
    {
      Class<?> proxy = getProxyClass(ProxyTestClassCovariantOverride.class);
      
      Method m = getDeclaredMethod(ProxyTestClassCovariantOverride.class, "getCovariant");
      Object returned = m.invoke(getProxyInstance(proxy));
      assertTrue("Object was of wrong type: " + returned.getClass().getSimpleName(),
          proxy.isInstance(returned));
    }
  
    /**
     * Test a method with generics
     */
    @Test
    public void testGenerics() throws Exception
    {
      Class<?> proxy = getProxyClass(ProxyTestClassGeneric.class);
      
      Object o = getProxyInstance(proxy);
      Method m = getDeclaredMethod(ProxyTestClassGeneric.class, "setSomething", String.class);
      m.invoke(o, "aString");
      
      if(getClass() == WovenProxyGeneratorTest.class)
      	m = getDeclaredMethod(ProxyTestClassGeneric.class.getSuperclass(), "getSomething");
      else 
          m = getDeclaredMethod(ProxyTestClassGeneric.class, "getSomething");
  
      Object returned = m.invoke(o);
      assertTrue("Object was of wrong type", String.class.isInstance(returned));
      assertEquals("String had wrong value", "aString", returned);
    }
    
    /**
     * Test that we don't generate classes twice
     */
    @Test
    public void testRetrieveClass() throws Exception
    {
      Class<?> retrieved = getProxyClass(getTestClass());
      assertNotNull("The new class was null", retrieved);
      assertEquals("The same class was not returned", retrieved, getProxyClass(getTestClass()));
  
    }
    
    @Test
    public void testEquals() throws Exception {
      Object p1 = getProxyInstance(getProxyClass(getTestClass()));
      Object p2 = getProxyInstance(getProxyClass(getTestClass()));
      
      assertFalse("Should not be equal", p1.equals(p2));
      
      Object p3 = getP3();
      
      p1 = setDelegate(p1, new SingleInstanceDispatcher(p3));
      p2 = setDelegate(p2, new SingleInstanceDispatcher(p3));
      
      assertTrue("Should be equal", p1.equals(p2));
      
      Object p4 = getProxyInstance(getProxyClass(getTestClass()));
      Object p5 = getProxyInstance(getProxyClass(getTestClass()));
      
      p4 = setDelegate(p4, new SingleInstanceDispatcher(p1));
      p5 = setDelegate(p5, new SingleInstanceDispatcher(p2));
      
      assertTrue("Should be equal", p4.equals(p5));
    }
    
    protected abstract Object getP3() throws Exception;
    
    @Test
    public void testInterception() throws Throwable {
      
      TestListener tl = new TestListener();
      Object obj = getProxyInstance(getProxyClass(getTestClass()), tl);
      
      assertCalled(tl, false, false, false);
      
      Method m = getDeclaredMethod(getTestClass(), "testReturnInteger", new Class[] {});
      m.invoke(obj);
      
      assertCalled(tl, true, true, false);
      
      tl.clear();
      assertCalled(tl, false, false, false);
      
      m = getDeclaredMethod(getTestClass(), "testException", new Class[] {});
  
        Method finalM = m;
        InvocationTargetException re = assertThrows(InvocationTargetException.class,
                () -> finalM.invoke(obj));
        if (!re.getTargetException().getClass().equals(RuntimeException.class))
            throw re.getTargetException();
        assertCalled(tl, true, false, true);
  
      
      tl.clear();
      assertCalled(tl, false, false, false);
      
      m = getDeclaredMethod(getTestClass(), "testInternallyCaughtException", new Class[] {});
      try {
        m.invoke(obj);
      } finally {
        assertCalled(tl, true, true, false);
      }
    }
    
    protected void assertCalled(TestListener listener, boolean pre, boolean post, boolean ex) {
      assertEquals(pre, listener.preInvoke);
      assertEquals(post, listener.postInvoke);
      assertEquals(ex, listener.postInvokeExceptionalReturn);
    }
    @Test
    public void testStaticInner() throws Exception {
      assertNotNull(getProxyInstance(getProxyClass(ProxyTestClassStaticInner.class)));
    }
    @Test
    public void testInner() throws Exception {
      //An inner class has no no-args (the parent gets added as an arg) so we can't
      //get an instance
      assertNotNull(getProxyClass(ProxyTestClassInner.class));
    }
    
    /**
     * Test an abstract class
     */
    @Test
    public void testAbstractClass() throws Exception
    {
      Object ptca = getProxyInstance(getProxyClass(ProxyTestClassAbstract.class));
      ptca = setDelegate(ptca, new Callable<Object>() {
  
        public Object call() throws Exception {
          //We have to use a proxy instance here because we need it to be a subclass
          //of the one from the weaving loader in the weaving test...
          return getProxyInstance(ProxyTestClassChildOfAbstract.class);
        }
      });
      
      Method m = ptca.getClass().getDeclaredMethod("getMessage");
      assertEquals("Working", m.invoke(ptca));
    }
    
    public static BundleWiring getWiring(ClassLoader loader) throws Exception {
        BundleWiring wiring = mock(BundleWiring.class);
        when(wiring.getClassLoader()).thenReturn(loader);
        return wiring;
    }
  }
  