package com.intellij.util.xml;

import com.intellij.util.ArrayUtil;
import junit.framework.TestCase;
import net.sf.cglib.proxy.AdvancedProxy;
import net.sf.cglib.proxy.InvocationHandler;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * @author peter
 */
public class ProxyTest extends TestCase {

  public void testExtendClass() throws Throwable {
    final List<String> invocations = new ArrayList<String>();
    Implementation implementation = AdvancedProxy.createProxy(Implementation.class, new Class[]{Interface3.class}, new InvocationHandler(){
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        invocations.add(method.getName());
        if (Object.class.equals(method.getDeclaringClass())) {
          return method.invoke(this, args);
        }
        return Implementation.class.getMethod("getField").invoke(proxy);
      }
    }, "239");
    implementation.hashCode();
    implementation.method();
    assertEquals("239", implementation.getFoo());
    implementation.setField("42");
    assertEquals("42", implementation.getBar());
    assertEquals("42", implementation.toString());
    assertEquals(Arrays.asList("hashCode", "getFoo", "getFoo", "getBar"), invocations);

    assertEquals("42", Interface1.class.getMethod("getFoo").invoke(implementation));

    assertEquals("42", Interface3.class.getMethod("bar").invoke(implementation));

    assertEquals("42", Interface1.class.getMethod("foo").invoke(implementation));
    assertEquals("42", Interface2.class.getMethod("foo").invoke(implementation));
    assertEquals("42", Interface2.class.getMethod("foo").invoke(implementation));
    assertEquals("42", Implementation.class.getMethod("foo").invoke(implementation));
  }

  public interface Interface1 {
    Object getFoo();
    Object foo();
  }

  public interface Interface2 extends Interface1 {
    @Override
    CharSequence getFoo();
    @Override
    CharSequence foo();
  }

  public interface Interface3 extends Interface1 {
    @Override
    String foo();
    String bar();
  }

  private abstract static class Implementation implements Interface2 {
    private String myField;

    protected Implementation(final String field) {
      myField = field;
    }

    public String getField() {
      return myField;
    }

    public void setField(final String field) {
      myField = field;
    }

    public void method() {
      getFoo();
    }

    public String toString() {
      return myField;
    }

    @Override
    public abstract String getFoo();

    public abstract String getBar();

  }

  public void testAddInterfaces() throws Throwable {
    final BaseImpl proxy = AdvancedProxy.createProxy(BaseImpl.class, BaseIEx.class);
    assertEquals(proxy.sayA(), "a");
    assertEquals(((BaseI)proxy).sayA(), "a");
    assertEquals(((BaseIEx)proxy).sayA(), "a");
  }

  public interface BaseI {
    Object sayA();
  }

  public interface BaseIEx extends BaseI {
    @Override
    CharSequence sayA();
  }

  public static abstract class BaseImpl implements BaseI {
    @Override
    public String sayA() {
      return "a";
    }
  }

  public static abstract class AbstractBase implements BaseI {
    @Override
    public abstract String sayA();

    public abstract static class AbstractBaseImpl extends AbstractBase {}
  }

  public void testCovariantFromInterface() throws Throwable {
    final AbstractBase.AbstractBaseImpl proxy = AdvancedProxy.createProxy(AbstractBase.AbstractBaseImpl.class, ArrayUtil.EMPTY_CLASS_ARRAY,
                                                                          new InvocationHandler() {
                                                                            @Override
                                                                            public Object invoke(Object proxy, Method method, Object[] args)
                                                                              throws Throwable {
                                                                              return "a";
                                                                            }
                                                                          }, false, new Object[0]);
    assertEquals(proxy.sayA(), "a");
    assertEquals(((AbstractBase)proxy).sayA(), "a");
    assertEquals(((BaseI)proxy).sayA(), "a");
  }

  public static class CovariantFromBaseClassTest {
    public static interface Intf {
      String sayA();
    }

    public static class Base {
      public Object sayA() {
        return "beeee";
      }
    }

    public abstract static class Impl extends Base implements Intf {
      @Override
      public abstract String sayA();
    }
  }

  public void testCovariantFromBaseClass() throws Throwable {
    final CovariantFromBaseClassTest.Impl proxy = AdvancedProxy.createProxy(CovariantFromBaseClassTest.Impl.class,
                                                                            ArrayUtil.EMPTY_CLASS_ARRAY, new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
          return "a";
        }
      }, false, new Object[0]);
    assertEquals(proxy.sayA(), "a");
    assertEquals(((CovariantFromBaseClassTest.Base)proxy).sayA(), "a");
    assertEquals(((CovariantFromBaseClassTest.Intf)proxy).sayA(), "a");
  }


}

@@ -61,16 +61,62 @@
 ayUtil;%0A
+import com.intellij.util.xml.ui.DomUIFactory;%0A
 import j
@@ -5482,11 +5482,675 @@
 );%0A  %7D%0A%0A
+  public void testGenericMethodInvocationJava8() throws Throwable %7B%0A    ConcreteInterface proxy = AdvancedProxy.createProxy(new InvocationHandler() %7B%0A      @Override%0A      public Object invoke(Object proxy, Method method, Object%5B%5D args) throws Throwable %7B%0A        return 42;%0A      %7D%0A    %7D, null, ConcreteInterface.class);%0A    Method foo = DomUIFactory.findMethod(GenericInterface.class, %22foo%22);%0A    assert foo != null;%0A    assertEquals(42, proxy.foo(%22a%22));%0A    assertEquals(42, foo.invoke(proxy, %22a%22));%0A  %7D%0A%0A  interface GenericInterface%3CT%3E %7B%0A    Object foo(T t);%0A  %7D%0A%0A  interface ConcreteInterface extends GenericInterface%3CString%3E %7B%0A    Object foo(String t);%0A  %7D%0A%0A
 %0A%7D%0A

add a proxy test that fails on java 8