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.assertTrue;
  
  import java.lang.reflect.Constructor;
  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.Iterator;
  import java.util.List;
  import java.util.Map;
  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.SingleInstanceDispatcher;
  import org.apache.aries.proxy.impl.gen.ProxySubclassGenerator;
  import org.apache.aries.proxy.impl.gen.ProxySubclassMethodHashSet;
  import org.apache.aries.proxy.impl.interfaces.InterfaceProxyGenerator;
  import org.apache.aries.proxy.weaving.WovenProxy;
  import org.junit.Test;
  
  /**
   * This class uses the {@link ProxySubclassGenerator} to test
   */
  @SuppressWarnings("unchecked")
  public class WovenSubclassGeneratorTest 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 Callable<Object> testCallable = null;
  
  
    /**
     * 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) {
      	
          if(m.getName().equals("clone") || m.getName().equals("finalize"))
          	continue;
      	
          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);
  
      
      
      Method[] subclassMethods = getProxyClass(getTestClass()).getDeclaredMethods();
      List<Method> listOfDeclaredMethods = new ArrayList<Method>();
      for (Method m : subclassMethods) {
        if(m.getName().startsWith(WovenProxy.class.getName().replace('.', '_')))
          continue;
        
        listOfDeclaredMethods.add(m);
      }
      subclassMethods = listOfDeclaredMethods.toArray(new Method[] {});
      
      ProxySubclassMethodHashSet<String> generatedMethods = new ProxySubclassMethodHashSet<String>(
      		subclassMethods.length);
          generatedMethods.addMethodArray(subclassMethods);
          
      // 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 {
        InterfaceProxyGenerator.getProxyInstance(null, FINAL_METHOD_CLASS, Collections.EMPTY_SET, 
            new Callable<Object>() {
          public Object call() throws Exception {
            return null;
          }} , null).getClass();
      } catch (RuntimeException re) {
        FinalModifierException e = (FinalModifierException) re.getCause();
        assertFalse("Should have found final method not final class", e.isFinalClass());
      }
    }
  
    /**
     * Test a class marked final
     */
    @Test
    public void testFinalClass() throws Exception
    {
      try {
        InterfaceProxyGenerator.getProxyInstance(null, FINAL_CLASS, Collections.EMPTY_SET, 
            new Callable<Object>() {
          public Object call() throws Exception {
            return null;
          }} , null).getClass();
      } catch (FinalModifierException e) {
        assertTrue("Should have found final class", e.isFinalClass());
      }
    }
  
    /**
     * Test a covariant override method
     */
    @Test
    public void testCovariant() throws Exception
    {
      testCallable = new SingleInstanceDispatcher(COVARIANT_CLASS.newInstance());
      Object o = setDelegate(getProxyInstance(getProxyClass(COVARIANT_CLASS)), testCallable);
      Class<?> 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
    {
      testCallable = new SingleInstanceDispatcher(GENERIC_CLASS.newInstance());
      super.testGenerics();
    }
    
    @Test
    public void testInner() {
  	  //This implementation can never pass this test. It doesn't support classes with no no-args constructor.
    }
     
    
    @Test
    public void testAddingInterfacesToClass() throws Exception {
  	  
      Object proxy = InterfaceProxyGenerator.getProxyInstance(null, getTestClass(), Arrays.asList(Map.class, Iterable.class), new Callable<Object>() {
  
          int calls = 0;
          private Map<String, String> map = new HashMap<String, String>();
          
          {
            map.put("key", "value");
          }
  
          public Object call() throws Exception {
            switch(++calls) {
              case 1 :
                return getTestClass().newInstance();
              case 2 :
                return map;
              default :
                return map.values();
            }
  		}
      	
      }, null);
      
      
      assertEquals(17, ((ProxyTestClassGeneralWithNoDefaultOrProtectedAccess)proxy).testReturnInt());
      assertEquals("value", ((Map<String, String>)proxy).put("key", "value2"));
      Iterator<?> it = ((Iterable<?>)proxy).iterator();
      assertEquals("value2", it.next());
      assertFalse(it.hasNext());
  	  
    }
    
    @Override
    protected Object getProxyInstance(Class<?> proxyClass) {
  	 
      if(proxyClass == ProxyTestClassChildOfAbstract.class) {
      	return new ProxyTestClassChildOfAbstract();
      }
  	  
      try {
        Constructor<?> con = proxyClass.getDeclaredConstructor(Callable.class, InvocationListener.class);
        con.setAccessible(true);
        return con.newInstance((testCallable == null) ? new SingleInstanceDispatcher(getTestClass().newInstance()) : testCallable, null);
      } catch (Exception e) {
        return null;
      }
    }
    
  
    @Override
    protected Class<?> getTestClass() {
  	return ProxyTestClassGeneralWithNoDefaultOrProtectedAccess.class;
    }
  
  
  @Override
    protected Class<?> getProxyClass(Class<?> clazz) {
      try {
        return InterfaceProxyGenerator.getProxyInstance(null, clazz, Collections.EMPTY_SET, 
            new Callable<Object>() {
          public Object call() throws Exception {
            return null;
          }} , null).getClass();
      } catch (UnableToProxyException e) {
        return null;
      } catch (RuntimeException re) {
        if(re.getCause() instanceof UnableToProxyException)
          return null;
        else
          throw re;
      }
    }
  
  
    @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() throws Exception {
      return getTestClass().newInstance();
    }
  }