/*
 * Copyright 2009 Google Inc.
 *
 * Licensed 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 com.google.gwt.dev.shell;

import junit.framework.TestCase;

import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;

/**
 * Tests DispatchClassInfoTest.
 */
public class DispatchClassInfoTest extends TestCase {
  /**
   * Used in {@link #testInterface()}.
   */
  private interface Intf {
    void bar(double x);

    void bar(int x);

    void foo(int x);
  }

  public void testBasics() {
    @SuppressWarnings("unused")
    class Foo {
      int field;

      void nonOverloaded(int x) {
      }

      void overloaded(double x) {
      }

      void overloaded(int x) {
      }
    }

    DispatchClassInfo dci = new DispatchClassInfo(Foo.class, 42);
    assertField(dci, "field", "field");
    assertNonExistent(dci, "bogofield");
    assertMethod(dci, "nonOverloaded(I)", Foo.class, "nonOverloaded",
        Integer.TYPE);
    assertMethod(dci, "overloaded(I)", Foo.class, "overloaded", Integer.TYPE);
    assertMethod(dci, "overloaded(D)", Foo.class, "overloaded", Double.TYPE);
    assertNonExistent(dci, "overloaded(*)");
    assertNonExistent(dci, "bogometh(I)");
    assertNonExistent(dci, "bogometh(*)");
  }

  public void testInheritance() {
    /*
     * In these two classes, foo is overloaded in the superclass but not the
     * subclass, and bar vice versa.
     */
    @SuppressWarnings("unused")
    class Super {
      int field;

      void bar(int x) {
      }

      void foo(double x) {
      }

      void foo(int x) {
      }

      void nonover(int x) {
      }

      void over(double x) {
      }

      void over(int x) {
      }
    }

    @SuppressWarnings("unused")
    class Sub extends Super {
      void bar(double x) {
      }

      @Override
      void bar(int x) {
      }

      @Override
      void foo(int x) {
      }
    }

    DispatchClassInfo dci = new DispatchClassInfo(Sub.class, 42);

    assertField(dci, "field", "field");
    assertMethod(dci, "foo(I)", Sub.class, "foo", Integer.TYPE);
    assertMethod(dci, "bar(I)", Sub.class, "bar", Integer.TYPE);
    assertMethod(dci, "bar(D)", Sub.class, "bar", Double.TYPE);
    assertNonExistent(dci, "foo(*)");
    assertNonExistent(dci, "bar(*)");

    assertMethod(dci, "nonover(I)", Super.class, "nonover", Integer.TYPE);

    assertMethod(dci, "over(I)", Super.class, "over", Integer.TYPE);
    assertMethod(dci, "over(D)", Super.class, "over", Double.TYPE);
    assertNonExistent(dci, "over(*)");
  }

  public void testInterface() {
    DispatchClassInfo dci = new DispatchClassInfo(Intf.class, 42);

    assertMethod(dci, "foo(I)", Intf.class, "foo", Integer.TYPE);
    assertMethod(dci, "bar(I)", Intf.class, "bar", Integer.TYPE);
    assertMethod(dci, "bar(D)", Intf.class, "bar", Double.TYPE);
    assertNonExistent(dci, "bar(*)");
  }

  /**
   * Test that bridge methods are ignored for wildcard lookups.
   */
  public void testBridgeMethod() {
    @SuppressWarnings("unused")
    abstract class Super<T> {
      abstract void set(T x);
    }

    class Sub extends Super<String> {
      @Override
      void set(String x) {
      }
    }

    DispatchClassInfo dci = new DispatchClassInfo(Sub.class, 42);

    assertMethod(dci, "set(Ljava/lang/String;)", Sub.class, "set", String.class);

    // For backward compatibility, allow calling a bridge method directly
    assertMethod(dci, "set(Ljava/lang/Object;)", Sub.class, "set", Object.class);
  }

  private void assertField(DispatchClassInfo dci, String ref, String fieldName) {
    Member member = lookupMember(dci, ref);
    Field field = (Field) member;
    assertEquals(fieldName, field.getName());
  }

  private void assertMethod(DispatchClassInfo dci, String ref,
      Class<?> methodClass, String methodName, Class<?> paramType) {
    Member member = lookupMember(dci, ref);
    Method method = (Method) member;
    assertSame(methodClass, member.getDeclaringClass());
    assertEquals(methodName, method.getName());
    assertEquals(1, method.getParameterTypes().length);
    assertEquals(paramType, method.getParameterTypes()[0]);
  }

  private void assertNonExistent(DispatchClassInfo dci, String badref) {
    int handle = dci.getMemberId(badref);
    assertTrue("expected to be a bad reference: " + badref, handle < 0);
  }

  private Member lookupMember(DispatchClassInfo dci, String ref) {
    int handle = dci.getMemberId(ref);
    assertTrue("ref lookup failed: " + ref, handle >= 0);
    Member member = dci.getMember(handle);
    return member;
  }
}
