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.assertNotNull;
  import static org.junit.Assert.assertNull;
  import static org.junit.Assert.assertSame;
  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.Closeable;
  import java.io.InputStream;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collection;
  import java.util.Collections;
  import java.util.List;
  import java.util.Map;
  import java.util.concurrent.Callable;
  
  import org.apache.aries.blueprint.proxy.AbstractProxyTest.TestListener;
  import org.apache.aries.blueprint.proxy.complex.AriesTransactionManager;
  import org.apache.aries.proxy.UnableToProxyException;
  import org.apache.aries.proxy.impl.interfaces.InterfaceProxyGenerator;
  import org.junit.Before;
  import org.junit.Test;
  import org.mockito.Mockito;
  import org.osgi.framework.Bundle;
  import org.osgi.framework.wiring.BundleWiring;
  
  
  @SuppressWarnings("unchecked")
  public class InterfaceProxyingTest {
  
    public final static class TestCallable implements Callable<Object> {
      
      private Object list = new Callable<Object>() {
  
  
        public Object call() throws Exception {
          return null;
        }
      };
      
      public Object call() throws Exception {
        return list;
      }
      
      public void setReturn(Object o) {
        list = o;
      }
    }
  
    private Bundle testBundle;
  
    @Before
    public void setup() {
      testBundle = Mockito.mock(Bundle.class);
      BundleWiring bundleWiring = Mockito.mock(BundleWiring.class);
      Mockito.when(testBundle.adapt(BundleWiring.class)).thenReturn(bundleWiring);
    }
    
    @Test
    public void testGetProxyInstance1() throws Exception{
      
      Collection<Class<?>> classes = new ArrayList<Class<?>>(Arrays.asList(Closeable.class));
      
      Object o = InterfaceProxyGenerator.getProxyInstance(testBundle, null, classes, constantly(null), null);
      
      assertTrue(o instanceof Closeable);
    }
    
    @Test
    public void testGetProxyInstance2() throws Exception{
      
      Collection<Class<? extends Object>> classes = Arrays.asList(Closeable.class, Iterable.class, Map.class);
      
      Object o = InterfaceProxyGenerator.getProxyInstance(testBundle, null, classes, constantly(null), null);
      
      assertTrue(o instanceof Closeable);
      assertTrue(o instanceof Iterable);
      assertTrue(o instanceof Map);
      
    }
  
    /**
     * Test a class whose super couldn't be woven
     */
    @Test
    public void testDelegationAndInterception() throws Exception
    {
      Collection<Class<?>> classes = new ArrayList<Class<?>>(Arrays.asList(Callable.class));
      TestListener tl = new TestListener();
      TestCallable tc = new TestCallable();
      
      Callable<Object> o = (Callable<Object>) InterfaceProxyGenerator.getProxyInstance(testBundle, 
          null, classes, tc, tl);
      
      assertCalled(tl, false, false, false);
      
      assertNull(null, o.call());
      
      assertCalled(tl, true, true, false);
      
      assertEquals(Callable.class.getMethod("call"), 
          tl.getLastMethod());
  
      tl.clear();
      assertCalled(tl, false, false, false);
      
      tc.setReturn(new Callable<Object>() {
  
   
        public Object call() throws Exception {
          throw new RuntimeException();
        }
      });
      RuntimeException re = assertThrows(RuntimeException.class, () -> o.call());
      assertCalled(tl, true, false, true);
      assertSame(re, tl.getLastThrowable());
  
      tl.clear();
      assertCalled(tl, false, false, false);
      
      tc.setReturn(new Callable<Object>() {
  
       
        public Object call() throws Exception {
          try {
            throw new RuntimeException();
          } catch (RuntimeException re) {
            return new Object();
          }
        }
      });
      
      
      try {
        assertNotNull(o.call());
      } finally {
        assertCalled(tl, true, true, false);
      }
    }
    
    @Test
    public void testCaching() throws Exception {
      Collection<Class<?>> classes = new ArrayList<Class<?>>(Arrays.asList(Closeable.class));
      
      Object o1 = InterfaceProxyGenerator.getProxyInstance(testBundle, null, classes, constantly(null), null);
      Object o2 = InterfaceProxyGenerator.getProxyInstance(testBundle, null, classes, constantly(null), null);
      
      assertSame(o1.getClass(), o2.getClass());
    }
    
    @Test
    public void testComplexInterface() throws Exception {
      Collection<Class<?>> classes = new ArrayList<Class<?>>(Arrays.asList(ProxyTestInterface.class));
      
      final TestCallable tc = new TestCallable();
      tc.setReturn(5);
      
      Object o = InterfaceProxyGenerator.getProxyInstance(testBundle, null, classes, constantly(tc), null);
      
      assertTrue(o instanceof ProxyTestInterface);
      
      assertTrue(o instanceof Callable);
      
      assertEquals(5, ((Callable<Object>)o).call());
    }
    
    @Test
    public void testHandlesObjectMethods() throws Exception {
        TestListener listener = new TestListener();
        List<String> list = Arrays.asList("one", "two", "three");
        Object proxied = InterfaceProxyGenerator.getProxyInstance(testBundle, null, Arrays.<Class<?>>asList(List.class), constantly(list), listener);
        
        // obeys hashCode and equals, they *are* on the interface (actually they're
        // on several interfaces, we process them in alphabetical order, so Collection
        // comes ahead of List.
        assertTrue(proxied.equals(Arrays.asList("one", "two", "three")));
        assertEquals(Collection.class.getMethod("equals", Object.class), listener.getLastMethod());
        listener.clear();
        assertEquals(Arrays.asList("one", "two", "three").hashCode(), proxied.hashCode());
        assertEquals(Collection.class.getMethod("hashCode"), listener.getLastMethod());
        listener.clear();
        // and toString
        assertEquals(list.toString(), proxied.toString());
        assertEquals(Object.class.getMethod("toString"), listener.getLastMethod());
        listener.clear();
        
        Runnable runnable = new Runnable() {
          public void run() {}
        };
        proxied = InterfaceProxyGenerator.getProxyInstance(testBundle, null, Arrays.<Class<?>>asList(Runnable.class), constantly(runnable), listener);
        
        // obeys hashCode and equals, they *are not* on the interface
        assertTrue(proxied.equals(runnable));
        assertEquals(Object.class.getMethod("equals", Object.class), listener.getLastMethod());
        listener.clear();
        assertEquals(runnable.hashCode(), proxied.hashCode());
        assertEquals(Object.class.getMethod("hashCode"), listener.getLastMethod());
        listener.clear();
    }
    
    private static class TestClassLoader extends ClassLoader {
        public TestClassLoader() throws Exception {
            
            InputStream is = TestClassLoader.class.getClassLoader().getResourceAsStream("org/apache/aries/blueprint/proxy/TestInterface.class");
            ByteArrayOutputStream bout = new ByteArrayOutputStream();
  
            int b;
            while ((b = is.read()) != -1) {
                bout.write(b);
            }
            
            is.close();
            
            byte[] bytes = bout.toByteArray();
            defineClass("org.apache.aries.blueprint.proxy.TestInterface", bytes, 0, bytes.length);
        }
    }
    
    @Test
    public void testNoStaleProxiesForRefreshedBundle() throws Exception {
        Bundle bundle = mock(Bundle.class);
        
        TestClassLoader loader = new TestClassLoader();
        when(bundle.getLastModified()).thenReturn(10l);
        BundleWiring wiring = AbstractProxyTest.getWiring(loader);
        when(bundle.adapt(BundleWiring.class)).thenReturn(wiring);
        Class<?> clazz = loader.loadClass("org.apache.aries.blueprint.proxy.TestInterface");
        
        Object proxy = InterfaceProxyGenerator.getProxyInstance(bundle, null, Arrays.<Class<?>>asList(clazz), constantly(null), null);
        assertTrue(clazz.isInstance(proxy));
  
        ClassLoader parent1 = proxy.getClass().getClassLoader().getParent();
        
        /* Now again but with a changed classloader as if the bundle had refreshed */
        
        TestClassLoader loaderToo = new TestClassLoader();
        when(bundle.getLastModified()).thenReturn(20l);
  
        // let's change the returned revision
        BundleWiring wiring2 = AbstractProxyTest.getWiring(loaderToo);
        when(bundle.adapt(BundleWiring.class)).thenReturn(wiring2);
        
        Class<?> clazzToo = loaderToo.loadClass("org.apache.aries.blueprint.proxy.TestInterface");
        
        Object proxyToo = InterfaceProxyGenerator.getProxyInstance(bundle, null, Arrays.<Class<?>>asList(clazzToo), constantly(null), null);
        assertTrue(clazzToo.isInstance(proxyToo));
  
        ClassLoader parent2= proxyToo.getClass().getClassLoader().getParent();
  
        // 
        assertTrue("parents should be different, as the are the classloaders of different bundle revisions", parent1 != parent2);
    }
  
  
    // Test for https://issues.apache.org/jira/browse/ARIES-1618
    @Test
    public void checkDuplicateInterfaces() throws UnableToProxyException
    {
      Collection<Class<?>> classes = Collections.<Class<?>>singletonList(AriesTransactionManager.class);
  
      Object o = InterfaceProxyGenerator.getProxyInstance(testBundle, null, classes, constantly(null), null);
  
      assertTrue(o instanceof AriesTransactionManager);
    }
  
    protected void assertCalled(TestListener listener, boolean pre, boolean post, boolean ex) {
      assertEquals(pre, listener.preInvoke);
      assertEquals(post, listener.postInvoke);
      assertEquals(ex, listener.postInvokeExceptionalReturn);
    }
    
    private Callable<Object> constantly(final Object result) {
        return new Callable<Object>() {
            public Object call() throws Exception {
                return result;
            }             
          };
    }
    
  }
  