/*
 * Copyright 2010 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.javac;

import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.typeinfo.JArrayType;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JConstructor;
import com.google.gwt.core.ext.typeinfo.JField;
import com.google.gwt.core.ext.typeinfo.JGenericType;
import com.google.gwt.core.ext.typeinfo.JMethod;
import com.google.gwt.core.ext.typeinfo.JParameterizedType;
import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.JTypeParameter;
import com.google.gwt.core.ext.typeinfo.JWildcardType;
import com.google.gwt.core.ext.typeinfo.NotFoundException;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.core.ext.typeinfo.TypeOracleException;
import com.google.gwt.dev.javac.CompilationUnitTypeOracleUpdater.TypeData;
import com.google.gwt.dev.javac.mediatortest.AfterAssimilate;
import com.google.gwt.dev.javac.mediatortest.BaseInterface;
import com.google.gwt.dev.javac.mediatortest.BeforeAssimilate;
import com.google.gwt.dev.javac.mediatortest.BindToTypeScope;
import com.google.gwt.dev.javac.mediatortest.ConstrainedList;
import com.google.gwt.dev.javac.mediatortest.ConstrainedListAsField;
import com.google.gwt.dev.javac.mediatortest.DeclaresGenericInnerInterface;
import com.google.gwt.dev.javac.mediatortest.DeclaresGenericInnerType;
import com.google.gwt.dev.javac.mediatortest.DefaultClass;
import com.google.gwt.dev.javac.mediatortest.Derived;
import com.google.gwt.dev.javac.mediatortest.DerivedInterface;
import com.google.gwt.dev.javac.mediatortest.EnclosingLocal;
import com.google.gwt.dev.javac.mediatortest.EnclosingLocalWithMember;
import com.google.gwt.dev.javac.mediatortest.ExtendsGenericList;
import com.google.gwt.dev.javac.mediatortest.ExtendsGenericOuter;
import com.google.gwt.dev.javac.mediatortest.ExtendsParameterizedInterface;
import com.google.gwt.dev.javac.mediatortest.Fields;
import com.google.gwt.dev.javac.mediatortest.GenericList;
import com.google.gwt.dev.javac.mediatortest.GenericOuter;
import com.google.gwt.dev.javac.mediatortest.Implementations;
import com.google.gwt.dev.javac.mediatortest.ListAsField;
import com.google.gwt.dev.javac.mediatortest.Methods;
import com.google.gwt.dev.javac.mediatortest.Outer;
import com.google.gwt.dev.javac.mediatortest.OuterInt;
import com.google.gwt.dev.javac.mediatortest.ReferencesGenericListConstant;
import com.google.gwt.dev.javac.mediatortest.ReferencesParameterizedTypeBeforeItsGenericFormHasBeenProcessed;
import com.google.gwt.dev.javac.testing.impl.MockJavaResource;
import com.google.gwt.dev.javac.testing.impl.StaticJavaResource;
import com.google.gwt.dev.resource.Resource;
import com.google.gwt.dev.util.log.AbstractTreeLogger;
import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
import com.google.gwt.thirdparty.guava.common.collect.MapMaker;
import com.google.gwt.thirdparty.guava.common.io.BaseEncoding;

import junit.framework.TestCase;

import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Test TypeOracleUpdater.
 *
 * NOTE: These tests require the test source code to be on the classpath. In
 * Eclipse, make sure your launch configuration includes the 'core/test'
 * directory on the classpath tab.
 */
public abstract class TypeOracleUpdaterTestBase extends TestCase {

  /**
   * A Java resource that provides a check() method.
   */
  protected static abstract class CheckedJavaResource extends MutableJavaResource {
    private final String[] shortTypeNames;

    public CheckedJavaResource(Class<?> clazz, String... shortTypeNames) {
      super(clazz);
      this.shortTypeNames = shortTypeNames;
    }

    public CheckedJavaResource(String packageName, String shortMainTypeName,
        String... shortTypeNames) {
      super(packageName, Shared.makeTypeName(packageName, shortMainTypeName));
      this.shortTypeNames = shortTypeNames;
    }

    public abstract void check(JClassType type) throws NotFoundException;

    public List<String> getTypeNames() {
      List<String> typeNames = new ArrayList<String>();
      typeNames.add(getTypeName());
      for (String shortTypeName : shortTypeNames) {
        String typeName = Shared.makeTypeName(getPackageName(), shortTypeName);
        typeNames.add(typeName);
      }
      return typeNames;
    }
  }

  /**
   * A mutable Java resource.
   */
  protected static abstract class MutableJavaResource extends MockJavaResource {
    private static byte[] getByteCode(Class<?> aClass) throws IOException {
      String resourcePath = aClass.getName().replace(".", "/") + ".class";
      ClassLoader loader = aClass.getClassLoader();
      if (loader == null && aClass.getName().startsWith("java.")) {
        loader = Thread.currentThread().getContextClassLoader();
      }
      try (InputStream istream = loader.getResourceAsStream(resourcePath)) {
        assertNotNull(istream);
        return istream.readAllBytes();
      }
    }

    // For building the type oracle from bytecode
    private final Class<?> clazz;
    private String extraSource = "";
    private final String packageName;

    public MutableJavaResource(Class<?> clazz) {
      super(clazz.getName());
      this.clazz = clazz;
      this.packageName = clazz.getPackage().getName();
    }

    public MutableJavaResource(String packageName, String qualifiedTypeName) {
      super(qualifiedTypeName);
      this.clazz = null;
      this.packageName = packageName;
    }

    public String getPackageName() {
      return packageName;
    }

    /**
     * This method is used to pull sample source from inside the test case. By
     * default, return <code>null</code> to indicate that source should be on
     * the classpath.
     */
    public String getSource() {
      return null;
    }

    /**
     * Pulls Java source from files in the mediatortest package. If source files
     * are on the classpath, prefer this data.
     */
    public String getSourceFromClasspath() {
      if (clazz == null) {
        return null;
      }
      assertFalse(clazz.getName().startsWith("java."));
      ClassLoader loader = clazz.getClassLoader();
      String resourcePath = clazz.getName().replace(".", "/") + ".java";
      try (InputStream istream = loader.getResourceAsStream(resourcePath)) {
        if (istream == null) {
          fail("Could not read " + resourcePath + " from classloader.");
        }
        return new String(istream.readAllBytes(), StandardCharsets.UTF_8);
      } catch (IOException e) {
        throw new UncheckedIOException("Failed while reading " + resourcePath, e);
      }
    }

    public TypeData[] getTypeData() throws IOException {
      return getTypeData(clazz);
    }

    @Override
    public void touch() {
      super.touch();
      extraSource += '\n';
    }

    /**
     * Looks for the source data on the classpath.
     */
    @Override
    public CharSequence getContent() {
      String source = getSource();
      if (source == null) {
        source = getSourceFromClasspath();
        assertNotNull("Make sure your runtime environment includes the source "
            + "for the testcases on the classpath if this assertion fails", source);
      }
      return source + extraSource;
    }

    private TypeData[] getTypeData(Class<?> aClass) throws IOException {
      List<TypeData> results = new ArrayList<TypeData>();
      String packageName = Shared.getPackageName(aClass.getName());
      TypeData newData =
          new TypeData(packageName, aClass.getSimpleName(), aClass.getName().replace(".", "/"),
              getByteCode(aClass), System.currentTimeMillis());
      results.add(newData);
      Class<?>[] subclasses = aClass.getDeclaredClasses();
      for (Class<?> subclass : subclasses) {
        for (TypeData result : getTypeData(subclass)) {
          results.add(result);
        }
      }
      return results.toArray(new TypeData[results.size()]);
    }
  }

  protected static final CheckedJavaResource CU_AfterAssimilate = new CheckedJavaResource(
      AfterAssimilate.class) {
    @Override
    public void check(JClassType type) {
      assertNotNull(type);
      assertEquals("AfterAssimilate", type.getSimpleSourceName());
      assertNotNull(type.getSuperclass());
      assertEquals(getPackageName() + ".BeforeAssimilate", type.getSuperclass()
          .getQualifiedSourceName());
    }
  };

  protected static final CheckedJavaResource CU_BaseInterface = new CheckedJavaResource(
      BaseInterface.class) {

    @Override
    public void check(JClassType type) {
      assertEquals(getTypeName(), type.getQualifiedSourceName());
      assertNotNull(type.isInterface());
    }
  };

  protected static final CheckedJavaResource CU_BeforeAssimilate = new CheckedJavaResource(
      BeforeAssimilate.class) {
    @Override
    public void check(JClassType type) {
      assertEquals(getTypeName(), type.getQualifiedSourceName());
    }
  };

  protected static final CheckedJavaResource CU_BindToTypeScope = new CheckedJavaResource(
      BindToTypeScope.class, "BindToTypeScope.Object", "BindToTypeScope.DerivedObject") {

    @Override
    public void check(JClassType type) throws NotFoundException {
      if ("BindToTypeScope".equals(type.getSimpleSourceName())) {
        checkBindToTypeScope(type);
      } else if ("Object".equals(type.getSimpleSourceName())) {
        checkObject(type);
      } else {
        checkDerivedObject(type);
      }
    }

    public void checkBindToTypeScope(JClassType type) throws NotFoundException {
      assertEquals("BindToTypeScope", type.getSimpleSourceName());
      assertEquals(getTypeName(), type.getQualifiedSourceName());
      JClassType object = type.getNestedType("Object");
      assertNotNull(object);
      JClassType derivedObject = type.getNestedType("DerivedObject");
      assertNotNull(derivedObject);
    }

    public void checkObject(JClassType type) {
      assertEquals("Object", type.getSimpleSourceName());
      assertEquals(getTypeName() + ".Object", type.getQualifiedSourceName());
    }

    private void checkDerivedObject(JClassType type) throws NotFoundException {
      JClassType bindToTypeScope = type.getEnclosingType();
      assertNotNull(bindToTypeScope);

      JClassType object = bindToTypeScope.getNestedType("Object");
      assertNotNull(object);
      assertEquals(object.getSimpleSourceName(), "Object");

      JClassType derivedObject = bindToTypeScope.getNestedType("DerivedObject");
      assertNotNull(derivedObject);
      assertEquals(derivedObject.getSimpleSourceName(), "DerivedObject");

      assertEquals(object, derivedObject.getSuperclass());
    }
  };

  protected static final CheckedJavaResource CU_ConstrainedList = new CheckedJavaResource(
      ConstrainedList.class) {
    @Override
    public void check(JClassType type) throws NotFoundException {
      assertNotNull(type.isGenericType());
    }
  };

  protected static final CheckedJavaResource CU_ConstrainedListAsField = new CheckedJavaResource(
      ConstrainedListAsField.class) {
    @Override
    public void check(JClassType type) throws NotFoundException {
      assertNull(type.isGenericType());
      assertNull(type.getEnclosingType());
    }
  };

  protected static final CheckedJavaResource CU_DeclaresInnerGenericInterface =
      new CheckedJavaResource(DeclaresGenericInnerInterface.class,
          "DeclaresGenericInnerInterface.Inner") {
        @Override
        public void check(JClassType type) throws NotFoundException {
          assertNotNull(type.isGenericType());
        }
      };

  protected static final CheckedJavaResource CU_DeclaresInnerGenericType = new CheckedJavaResource(
      DeclaresGenericInnerType.class, "DeclaresGenericInnerType.Inner") {
    @Override
    public void check(JClassType type) throws NotFoundException {
      assertNotNull(type.isGenericType());
    }
  };

  protected static final CheckedJavaResource CU_DefaultClass = new CheckedJavaResource(
      DefaultClass.class) {
    @Override
    public void check(JClassType type) {
      assertEquals("DefaultClass", type.getSimpleSourceName());
      assertEquals(getTypeName(), type.getQualifiedSourceName());
      JClassType object = type.getOracle().findType("java.lang", "Object");
      assertNotNull(object);

      assertEquals(object, type.getSuperclass());
      assertNull(type.isInterface());
      assertEquals(0, type.getMethods().length);
      assertEquals(0, type.getFields().length);
    }
  };

  protected static final CheckedJavaResource CU_Derived = new CheckedJavaResource(Derived.class,
      "Derived.Nested") {
    @Override
    public void check(JClassType type) {
      if ("Derived".equals(type.getSimpleSourceName())) {
        checkDerived(type);
      } else if ("Nested".equals(type.getSimpleSourceName())) {
        checkNested(type);
      } else {
        assert (false);
      }
    }

    private void checkDerived(JClassType type) {
      assertEquals(getTypeName(), type.getQualifiedSourceName());
    }

    private void checkNested(JClassType type) {
      assertEquals(getTypeName() + ".Nested", type.getQualifiedSourceName());
    }
  };

  protected static final CheckedJavaResource CU_DerivedInterface = new CheckedJavaResource(
      DerivedInterface.class) {

    @Override
    public void check(JClassType type) {
      assertEquals(getTypeName(), type.getQualifiedSourceName());
      assertNotNull(type.isInterface());
    }
  };

  protected static final CheckedJavaResource CU_EnclosingLocalClass = new CheckedJavaResource(
      EnclosingLocal.class) {

    @Override
    public void check(JClassType type) {
      final String name = type.getSimpleSourceName();
      assertEquals("EnclosingLocal", name);
      checkEnclosing(type);
    }

    public void checkEnclosing(JClassType type) {
      assertEquals("EnclosingLocal", type.getSimpleSourceName());
      assertEquals(getTypeName(), type.getQualifiedSourceName());
      // verify the local class doesn't show up
      JClassType[] nested = type.getNestedTypes();
      assertEquals(0, nested.length);
    }
  };

  protected static final CheckedJavaResource CU_EnclosingLocalWithMember = new CheckedJavaResource(
      EnclosingLocalWithMember.class) {

    @Override
    public void check(JClassType type) {
      final String name = type.getSimpleSourceName();
      assertEquals("EnclosingLocalWithMember", name);
      checkEnclosing(type);
    }

    public void checkEnclosing(JClassType type) {
      assertEquals(getTypeName(), type.getQualifiedSourceName());
      // verify the local class doesn't show up
      JClassType[] nested = type.getNestedTypes();
      assertEquals(0, nested.length);
    }
  };

  protected static final CheckedJavaResource CU_ExtendsGenericList = new CheckedJavaResource(
      ExtendsGenericList.class) {

    @Override
    public void check(JClassType type) throws NotFoundException {
      assertNotNull(type.getSuperclass().isParameterized());
    }
  };

  protected static final CheckedJavaResource CU_ExtendsGenericOuterInner = new CheckedJavaResource(
      ExtendsGenericOuter.class, "ExtendsGenericOuter.ExtendsInner") {

    @Override
    public void check(JClassType type) {
      final String name = type.getSimpleSourceName();
      if ("ExtendsGenericOuter".equals(name)) {
        checkOuter(type);
      } else {
        checkInner(type);
      }
    }

    public void checkInner(JClassType type) {
      assertEquals("ExtendsInner", type.getSimpleSourceName());
      assertEquals(getTypeName() + ".ExtendsInner", type.getQualifiedSourceName());
      assertEquals(getTypeName(), type.getEnclosingType().getQualifiedSourceName());
    }

    public void checkOuter(JClassType type) {
      assertEquals("ExtendsGenericOuter", type.getSimpleSourceName());
      assertEquals(getTypeName(), type.getQualifiedSourceName());
      JClassType[] nested = type.getNestedTypes();
      assertEquals(1, nested.length);
      JClassType inner = nested[0];
      assertEquals(getTypeName() + ".ExtendsInner", inner.getQualifiedSourceName());
    }
  };

  protected static final CheckedJavaResource CU_ExtendsParameterizedInterface =
      new CheckedJavaResource(ExtendsParameterizedInterface.class) {
        @Override
        public void check(JClassType type) throws NotFoundException {
          assertNotNull(type.getSuperclass().isParameterized());
        }
      };

  protected static final CheckedJavaResource CU_FieldsAndTypes = new CheckedJavaResource(
      Fields.class) {
    @Override
    public void check(JClassType type) throws NotFoundException {
      if ("Fields".equals(type.getSimpleSourceName())) {
        assertEquals(getTypeName(), type.getQualifiedSourceName());

        TypeOracle tio = type.getOracle();

        JField[] fields = type.getFields();
        assertEquals(12, fields.length);

        JField field;
        JType fieldType;
        JArrayType arrayType;
        JType componentType;
        final JClassType someType = tio.getType(getPackageName(), "DefaultClass");
        final JArrayType intArrayType = tio.getArrayType(JPrimitiveType.INT);
        final JArrayType someTypeArrayType = tio.getArrayType(someType);
        final JArrayType intArrayArrayType = tio.getArrayType(intArrayType);

        field = type.getField("privateInt");
        assertTrue(field.isPrivate());
        assertEquals(JPrimitiveType.INT, field.getType());

        field = type.getField("privateSomeType");
        assertTrue(field.isPrivate());
        assertEquals(someType, field.getType());

        field = type.getField("protectedInt");
        assertTrue(field.isProtected());

        field = type.getField("publicInt");
        assertTrue(field.isPublic());

        field = type.getField("packageInt");
        assertTrue(field.isDefaultAccess());

        field = type.getField("staticInt");
        assertTrue(field.isStatic());

        field = type.getField("transientInt");
        assertTrue(field.isTransient());

        field = type.getField("volatileInt");
        assertTrue(field.isVolatile());

        field = type.getField("multiInt");
        assertTrue(field.isPublic());
        assertTrue(field.isStatic());
        assertTrue(field.isFinal());
        assertTrue(field.isTransient());

        field = type.getField("intArray");
        fieldType = field.getType();
        arrayType = fieldType.isArray();
        assertNotNull(arrayType);
        assertSame(intArrayType, arrayType);
        componentType = arrayType.getComponentType();
        assertNotNull(componentType);
        assertSame(JPrimitiveType.INT, componentType);
        assertEquals("int[]", fieldType.getQualifiedSourceName());

        field = type.getField("someTypeArray");
        fieldType = field.getType();
        arrayType = fieldType.isArray();
        assertNotNull(arrayType);
        assertSame(someTypeArrayType, arrayType);
        componentType = arrayType.getComponentType();
        assertNotNull(componentType);
        assertSame(someType, componentType);
        assertEquals(getPackageName() + ".DefaultClass[]", fieldType.getQualifiedSourceName());

        field = type.getField("intArrayArray");
        fieldType = field.getType();
        arrayType = fieldType.isArray();
        assertNotNull(arrayType);
        assertSame(intArrayArrayType, arrayType);
        componentType = arrayType.getComponentType();
        assertNotNull(componentType);
        assertSame(intArrayType, arrayType.getComponentType());
        arrayType = (JArrayType) arrayType.getComponentType();
        assertSame(JPrimitiveType.INT, arrayType.getComponentType());
        assertEquals("int[][]", fieldType.getQualifiedSourceName());

      } else {
        // No need to check SomeType since there's already a DefaultClass
        // test.
      }
    }
  };

  protected static final CheckedJavaResource CU_GenericList = new CheckedJavaResource(
      GenericList.class) {
    @Override
    public void check(JClassType type) throws NotFoundException {
      assertNotNull(type.isGenericType());
    }
  };

  protected static final CheckedJavaResource CU_GenericOuterInner = new CheckedJavaResource(
      GenericOuter.class, "GenericOuter.Inner") {

    @Override
    public void check(JClassType type) {
      final String name = type.getSimpleSourceName();
      if ("GenericOuter".equals(name)) {
        checkOuter(type);
      } else {
        checkInner(type);
      }
    }

    public void checkInner(JClassType type) {
      assertEquals("Inner", type.getSimpleSourceName());
      assertEquals(getTypeName() + ".Inner", type.getQualifiedSourceName());
      assertEquals(getTypeName(), type.getEnclosingType().getQualifiedSourceName());
    }

    public void checkOuter(JClassType type) {
      assertEquals("GenericOuter", type.getSimpleSourceName());
      assertEquals(getTypeName(), type.getQualifiedSourceName());
      JClassType[] nested = type.getNestedTypes();
      assertEquals(1, nested.length);
      JClassType inner = nested[0];
      assertEquals(getTypeName() + ".Inner", inner.getQualifiedSourceName());
    }
  };

  protected static final CheckedJavaResource CU_List = new CheckedJavaResource(List.class) {

    @Override
    public void check(JClassType type) throws NotFoundException {
      assertNotNull(type.isGenericType());
      assertNotNull(type.isInterface());
      // TODO(zundel): This is a bug when building from source: interfaces
      // should not be default instantiable
      // assertNull(type.isDefaultInstantiable());
    }

    @Override
    public String getSource() {
      StringBuilder sb = new StringBuilder();
      sb.append("package java.util;\n");
      sb.append("public interface List<E> {\n");
      sb.append("}");
      return sb.toString();
    }
  };

  protected static final CheckedJavaResource CU_Object = new CheckedJavaResource(Object.class) {
    @Override
    public void check(JClassType type) {
      assertEquals("Object", type.getSimpleSourceName());
      assertEquals("java.lang.Object", type.getQualifiedSourceName());
    }

    @Override
    public String getSource() {
      StringBuilder sb = new StringBuilder();
      sb.append("package java.lang;");
      sb.append("public class Object { }");
      return sb.toString();
    }
  };

  protected static final CheckedJavaResource CU_OuterInner = new CheckedJavaResource(Outer.class,
      "Outer.Inner") {

    @Override
    public void check(JClassType type) {
      final String name = type.getSimpleSourceName();
      if ("Outer".equals(name)) {
        checkOuter(type);
      } else {
        checkInner(type);
      }
    }

    public void checkInner(JClassType type) {
      assertEquals("Inner", type.getSimpleSourceName());
      assertEquals(CU_OuterInner.getTypeName() + ".Inner", type.getQualifiedSourceName());
      assertEquals(CU_OuterInner.getTypeName(), type.getEnclosingType().getQualifiedSourceName());
    }

    public void checkOuter(JClassType type) {
      assertEquals("Outer", type.getSimpleSourceName());
      assertEquals(CU_OuterInner.getTypeName(), type.getQualifiedSourceName());
      JClassType[] nested = type.getNestedTypes();
      assertEquals(1, nested.length);
      JClassType inner = nested[0];
      assertEquals(CU_OuterInner.getTypeName() + ".Inner", inner.getQualifiedSourceName());
    }
  };

  /**
   * Not sourced off disk like the others since some build systems refuse files with $ in the name.
   */
  protected static final MutableJavaResource CU_PseudoInner = new MutableJavaResource(
      "com.google.gwt.dev.javac.mediatortest",
      "com.google.gwt.dev.javac.mediatortest.Pseudo$Inner") {

        @Override
        public CharSequence getContent() {
          StringBuilder code = new StringBuilder();
          code.append("package com.google.gwt.dev.javac.mediatortest;\n");
          code.append("public class Pseudo$Inner {}\n");
          return code;
        }

        @Override
        public TypeData[] getTypeData() {
          byte[] classBytes = BaseEncoding.base16().decode(
              "CAFEBABE000000330010070002010032636F6D2F676F6F676C652F6777742F6465762F6A617661632"
              + "F6D65646961746F72746573742F50736575646F24496E6E65720700040100106A6176612F6C616E672"
              + "F4F626A6563740100063C696E69743E010003282956010004436F64650A000300090C0005000601000"
              + "F4C696E654E756D6265725461626C650100124C6F63616C5661726961626C655461626C65010004746"
              + "869730100344C636F6D2F676F6F676C652F6777742F6465762F6A617661632F6D65646961746F72746"
              + "573742F50736575646F24496E6E65723B01000A536F7572636546696C6501001150736575646F24496"
              + "E6E65722E6A617661002100010003000000000001000100050006000100070000002F0001000100000"
              + "0052AB70008B100000002000A00000006000100000003000B0000000C000100000005000C000D00000"
              + "001000E00000002000F");
          TypeData classData = new TypeData("com.google.gwt.dev.javac.mediatortest", "Pseudo$Inner",
              "com/google/gwt/dev/javac/mediatortest/Pseudo$Inner", classBytes,
              System.currentTimeMillis());
          return new TypeData[] {classData};
        }
      };

  protected static final CheckedJavaResource CU_ReferencesGenericListConstant =
      new CheckedJavaResource(ReferencesGenericListConstant.class) {
        @Override
        public void check(JClassType type) throws NotFoundException {
          assertEquals(getTypeName(), type.getQualifiedSourceName());
        }
      };

  protected static final CheckedJavaResource CU_ReferencesParameterizedTypeBeforeItsGenericFormHasBeenProcessed =
      new CheckedJavaResource(ReferencesParameterizedTypeBeforeItsGenericFormHasBeenProcessed.class) {

        @Override
        public void check(JClassType type) throws NotFoundException {

          JClassType[] intfs = type.getImplementedInterfaces();
          assertEquals(1, intfs.length);
          assertNotNull(intfs[0].isParameterized());
        }
      };

  protected static final CheckedJavaResource CU_String = new CheckedJavaResource(String.class) {
    @Override
    public void check(JClassType type) {
      assertEquals("String", type.getSimpleSourceName());
      assertEquals("java.lang.String", type.getQualifiedSourceName());
    }

    @Override
    public String getSource() {
      StringBuilder sb = new StringBuilder();
      sb.append("package java.lang;");
      sb.append("public class String { }");
      return sb.toString();
    }
  };

  protected static final CheckedJavaResource CU_Throwable =
      new CheckedJavaResource(Throwable.class) {

        @Override
        public void check(JClassType type) {
          assertEquals("Throwable", type.getSimpleSourceName());
          assertEquals("java.lang.Throwable", type.getQualifiedSourceName());
        }

        @Override
        public String getSource() {
          StringBuilder sb = new StringBuilder();
          sb.append("package java.lang;");
          sb.append("public class Throwable { }");
          return sb.toString();
        }
      };

  protected static final CheckedJavaResource CU_UnnestedImplementations = new CheckedJavaResource(
      Implementations.class, "Implementations.OuterImpl", "Implementations.InnerImpl") {
    @Override
    public void check(JClassType type) {
      if (type.getSimpleSourceName().equals("Implementations")) {
        assertEquals(getTypeName(), type.getQualifiedSourceName());
      }
    }
  };

  protected static void assertIsAssignable(JClassType from, JClassType to) {
    assertTrue("'" + from + "' should be assignable to '" + to + "'", from.isAssignableTo(to));
    assertTrue("'" + to + "' should be assignable from '" + from + "'", to.isAssignableFrom(from));
  }

  protected static void assertIsNotAssignable(JClassType from, JClassType to) {
    assertFalse(from + " should not be assignable to " + to, from.isAssignableTo(to));
    assertFalse(to + " should not be assignable to " + from, to.isAssignableFrom(from));
  }

  protected static void recordAssignability(Map<JClassType, Set<JClassType>> assignabilityMap,
      JClassType from, JClassType to) {
    Set<JClassType> set = assignabilityMap.get(from);
    if (set == null) {
      set = new HashSet<JClassType>();
      assignabilityMap.put(from, set);
    }
    set.add(to);
  }

  /**
   * Public so that this will be initialized before the CUs.
   */
  public final Map<String, CheckedJavaResource> publicTypeNameToTestCupMap =
      new HashMap<String, CheckedJavaResource>();

  protected CheckedJavaResource CU_ListAsField = new CheckedJavaResource(ListAsField.class) {
    @Override
    public void check(JClassType type) throws NotFoundException {
      assertNull(type.isGenericType());
      assertNull(type.getEnclosingType());
    }
  };

  protected CheckedJavaResource CU_MethodsAndParams = new CheckedJavaResource(Methods.class) {

    @Override
    public void check(JClassType type) throws NotFoundException {
      TypeOracle tio = type.getOracle();
      JMethod[] methods = type.getMethods();
      assertEquals(6, methods.length);
      JMethod method;
      JType[] thrownTypes;
      final JClassType javaLangObject = tio.findType("java.lang", "Object");
      final JClassType javaLangThrowable = tio.findType("java.lang", "Throwable");
      final JType[] noParamTypes = new JType[0];

      method = type.getMethod("returnsInt", noParamTypes);
      assertSame(JPrimitiveType.INT, method.getReturnType());
      assertEquals(0, method.getParameters().length);

      method = type.getMethod("returnsSomeType", noParamTypes);
      assertSame(javaLangObject, method.getReturnType());
      assertEquals(0, method.getParameters().length);

      method = type.getMethod("staticMethod", noParamTypes);
      assertSame(JPrimitiveType.VOID, method.getReturnType());
      assertEquals(0, method.getParameters().length);
      assertTrue(method.isStatic());

      method = type.getMethod("finalMethod", noParamTypes);
      assertSame(JPrimitiveType.VOID, method.getReturnType());
      assertEquals(0, method.getParameters().length);
      assertTrue(method.isFinal());

      try {
        method = type.getMethod("overloaded", noParamTypes);
        fail("expected throw");
      } catch (NotFoundException e) {
      }

      methods = type.getOverloads("overloaded");
      assertEquals(2, methods.length);
      for (JMethod element : methods) {
        assertEquals("overloaded", element.getName());
      }

      method = type.getMethod("overloaded", new JType[]{JPrimitiveType.INT, javaLangObject});
      assertSame(JPrimitiveType.VOID, method.getReturnType());
      thrownTypes = method.getThrows();
      assertEquals(1, thrownTypes.length);
      assertSame(javaLangThrowable, thrownTypes[0]);

      method = type.getMethod("overloaded", new JType[]{JPrimitiveType.INT, JPrimitiveType.CHAR});
      assertSame(javaLangObject, method.getReturnType());
      thrownTypes = method.getThrows();
      assertEquals(0, thrownTypes.length);
    }
  };

  protected CheckedJavaResource CU_NestedGenericInterfaces = new CheckedJavaResource(
      OuterInt.class, "OuterInt.InnerInt") {

    @Override
    public void check(JClassType type) {
      final String name = type.getSimpleSourceName();
      if ("OuterInt".equals(name)) {
        checkOuter(type);
      } else {
        checkInner(type);
      }
    }

    public void checkInner(JClassType type) {
      assertEquals("InnerInt", type.getSimpleSourceName());
      assertEquals(getTypeName() + ".InnerInt", type.getQualifiedSourceName());
      assertEquals(getTypeName(), type.getEnclosingType().getQualifiedSourceName());
    }

    public void checkOuter(JClassType type) {
      assertEquals("OuterInt", type.getSimpleSourceName());
      assertEquals(getTypeName(), type.getQualifiedSourceName());
      JClassType[] nested = type.getNestedTypes();
      assertEquals(1, nested.length);
      JClassType inner = nested[0];
      assertEquals(getTypeName() + ".InnerInt", inner.getQualifiedSourceName());
    }
  };

  protected final Set<Resource> resources = new LinkedHashSet<Resource>();

  protected TypeOracle typeOracle;

  public void checkTypes(JClassType[] types) throws NotFoundException {
    for (JClassType type : types) {
      check(type);

      JClassType[] nestedTypes = type.getNestedTypes();
      checkTypes(nestedTypes);
    }
  }

  /**
   * Tests which variant of AbstractRefrenceMap we want to store the map for
   * parameterizedTypes, arrayTypes, and wildCardTypes in TypeOracle. Note: this
   * test is manual because gc can be unreliable.
   */
  public void manualTestAbstractRefrenceMap() {

    /*
     * with a HARD -> WEAK map, verify that the entry remains if there is no
     * reference to key, but is deleted when the reference to value is gone
     */
    Map<Integer, Integer> simpleMap = new MapMaker().weakValues().makeMap();
    Integer bar = new Integer(42);
    simpleMap.put(new Integer(32), bar);
    Runtime.getRuntime().gc();
    assertEquals(1, simpleMap.size());
    bar = null;
    Runtime.getRuntime().gc();
    assertEquals(0, simpleMap.size());

    /*
     * with a WEAK -> WEAK map, verify that the entry is gone if there are no
     * references to either the key or the value.
     */
    simpleMap = new MapMaker().weakKeys().weakValues().makeMap();
    Map<Integer, Integer> reverseMap = new MapMaker().weakKeys().weakValues().makeMap();
    Integer foo = new Integer(32);
    bar = new Integer(42);
    simpleMap.put(foo, bar);
    reverseMap.put(bar, foo);
    Runtime.getRuntime().gc();
    assertEquals(1, simpleMap.size());
    assertEquals(1, reverseMap.size());
    bar = null;
    Runtime.getRuntime().gc();
    assertEquals(0, simpleMap.size());
    assertEquals(0, reverseMap.size());
  }

  @Override
  public void setUp() {
    resources.clear();
  }

  public void testAssignable() throws TypeOracleException {
    // The order of adding resources is important for testing from byte code
    addTestResource(CU_Object);
    addTestResource(CU_BaseInterface);
    addTestResource(CU_DerivedInterface);
    addTestResource(CU_OuterInner);
    addTestResource(CU_Derived);
    buildTypeOracle();

    Map<JClassType, Set<JClassType>> assignabilityMap = new HashMap<JClassType, Set<JClassType>>();
    JClassType obj = typeOracle.findType(CU_Object.getTypeName());
    assertNotNull(obj);

    JClassType inner = typeOracle.findType(CU_OuterInner.getTypeName() + ".Inner");
    assertNotNull(inner);

    JClassType derived = typeOracle.findType(CU_Derived.getTypeName());
    assertNotNull(derived);

    JClassType nested = typeOracle.findType(CU_Derived.getTypeName() + ".Nested");
    assertNotNull(nested);

    JClassType baseIntf = typeOracle.findType(CU_Derived.getPackageName() + ".BaseInterface");
    assertNotNull(baseIntf);

    JClassType derivedIntf = typeOracle.findType(CU_Derived.getPackageName() + ".DerivedInterface");
    assertNotNull(derivedIntf);

    recordAssignability(assignabilityMap, derivedIntf, baseIntf);
    recordAssignability(assignabilityMap, derived, inner);
    recordAssignability(assignabilityMap, nested, inner);
    recordAssignability(assignabilityMap, nested, derivedIntf);
    recordAssignability(assignabilityMap, nested, baseIntf);

    JClassType[] allTypes = typeOracle.getTypes();
    assertEquals(7, allTypes.length);

    for (JClassType fromType : allTypes) {
      for (JClassType toType : allTypes) {
        if (fromType == toType || toType == typeOracle.getJavaLangObject()) {
          assertIsAssignable(fromType, toType);
        } else {
          Set<JClassType> set = assignabilityMap.get(fromType);
          if (set != null && set.contains(toType)) {
            assertIsAssignable(fromType, toType);
          } else {
            assertIsNotAssignable(fromType, toType);
          }
        }
      }
    }
  }

  public void testAssimilation() throws TypeOracleException {
    addTestResource(CU_Object);
    addTestResource(CU_BeforeAssimilate);
    buildTypeOracle();

    assertNotNull(typeOracle.findType(CU_Object.getTypeName()));
    assertNotNull(typeOracle.findType(CU_BeforeAssimilate.getTypeName()));
    assertEquals(2, typeOracle.getTypes().length);

    // Build onto an existing type oracle.
    addTestResource(CU_AfterAssimilate);
    buildTypeOracle();

    assertNotNull(typeOracle.findType(CU_Object.getTypeName()));
    assertNotNull(typeOracle.findType(CU_BeforeAssimilate.getTypeName()));
    assertNotNull(typeOracle.findType(CU_AfterAssimilate.getTypeName()));
    assertEquals(3, typeOracle.getTypes().length);
  }

  public void testBindToTypeScope() throws TypeOracleException {
    addTestResource(CU_Object);
    addTestResource(CU_BindToTypeScope);
    buildTypeOracle();

    assertNotNull(typeOracle.findType(CU_Object.getTypeName()));
    assertNotNull(typeOracle.findType("com.google.gwt.dev.javac.mediatortest.BindToTypeScope"));
    assertNotNull(
        typeOracle.findType("com.google.gwt.dev.javac.mediatortest.BindToTypeScope.DerivedObject"));
    assertNotNull(
        typeOracle.findType("com.google.gwt.dev.javac.mediatortest.BindToTypeScope.Object"));
    assertEquals(4, typeOracle.getTypes().length);
  }

  public void testConstrainedField() throws TypeOracleException {
    addTestResource(CU_Object);
    addTestResource(CU_ConstrainedList);
    addTestResource(CU_ConstrainedListAsField);
    addTestResource(CU_Throwable);
    buildTypeOracle();

    // Get the types produced by the TypeOracle
    JClassType type = typeOracle.getType(CU_ConstrainedListAsField.getTypeName());
    assertNull(type.isParameterized());
    JField[] fields = type.getFields();
    assert (fields.length == 1);
    JField field = type.getField("field");
    assertNotNull(field);
    JType fieldType = field.getType();
    JParameterizedType fieldParamType = fieldType.isParameterized();
    assertNotNull(fieldParamType);
    assertNull(fieldParamType.getEnclosingType());
    JGenericType baseType = fieldParamType.getBaseType();
    assertNotNull(baseType);
    assertEquals(CU_ConstrainedList.getTypeName(), baseType.getQualifiedSourceName());
    JClassType[] typeArgs = fieldParamType.getTypeArgs();
    assertEquals(1, typeArgs.length);
    JWildcardType wildcard = typeArgs[0].isWildcard();
    assertNotNull(wildcard);
    JClassType upperBound = wildcard.getUpperBound();
    assertEquals("Throwable", upperBound.getSimpleSourceName());
  }

  public void testConstrainedList() throws TypeOracleException {
    addTestResource(CU_Object);
    addTestResource(CU_Throwable);
    addTestResource(CU_ConstrainedList);

    buildTypeOracle();

    JClassType type = typeOracle.getType(CU_ConstrainedList.getPackageName() + ".ConstrainedList");
    JClassType throwable = typeOracle.getType("java.lang.Throwable");
    assertNotNull(throwable);
    assertEquals("Throwable", throwable.getSimpleSourceName());

    assertNull(type.isParameterized());
    JGenericType genericType = type.isGenericType();
    assertNotNull(genericType);
    JTypeParameter[] typeParams = genericType.getTypeParameters();
    assertEquals(1, typeParams.length);
    assertEquals(throwable, typeParams[0].getBaseType());
    assertEquals(type, typeParams[0].getDeclaringClass());
    JClassType[] bounds = typeParams[0].getBounds();
    assertEquals(1, bounds.length);
    assertEquals(throwable, bounds[0]);
  }

  public void testConstructors() throws TypeOracleException {
    addTestResource(CU_Object);
    buildTypeOracle();
    JClassType objectType = typeOracle.findType(CU_Object.getTypeName());
    assertNotNull(objectType);
    assertEquals("Object", objectType.getSimpleSourceName());
    JConstructor[] ctors = objectType.getConstructors();
    assertEquals(1, ctors.length);
    JConstructor defaultCtor = ctors[0];
    assertEquals("Object", defaultCtor.getName());
    assertEquals(0, defaultCtor.getParameters().length);
  }

  public void testDefaultClass() throws TypeOracleException {
    addTestResource(CU_Object);
    addTestResource(CU_DefaultClass);
    buildTypeOracle();

    assertNotNull(typeOracle.findType(CU_Object.getTypeName()));
    assertNotNull(typeOracle.findType(CU_DefaultClass.getTypeName()));
    assertEquals(2, typeOracle.getTypes().length);
  }

  public void testEnclosingGenericType() throws TypeOracleException {
    addTestResource(CU_Object);
    addTestResource(CU_String);
    addTestResource(CU_List);
    addTestResource(CU_GenericOuterInner);
    addTestResource(CU_ExtendsGenericOuterInner);

    buildTypeOracle();

    // Get the types produced by the TypeOracle
    JClassType outer = typeOracle.getType(CU_GenericOuterInner.getPackageName() + ".GenericOuter");
    JClassType inner =
        typeOracle.getType(CU_GenericOuterInner.getPackageName() + ".GenericOuter.Inner");

    assertNull(outer.getEnclosingType());
    assertEquals(outer, inner.getEnclosingType());
    assertNull(inner.isParameterized());
    assertNotNull(outer.isGenericType());
    assertNotNull(inner.isGenericType());
    JField[] fields = inner.getFields();
    assertEquals(fields.length, 2);
    JField field = inner.getField("field");
    assertNotNull(field);
    JType fieldType = field.getType();
    JTypeParameter typeParam = fieldType.isTypeParameter();
    assertNotNull(typeParam);
    assertEquals("V", typeParam.getName());
    JClassType[] bounds = typeParam.getBounds();
    assertEquals(1, bounds.length);
    assertEquals(typeOracle.getJavaLangObject(), bounds[0]);

    JClassType extendsOuter =
        typeOracle.getType(CU_GenericOuterInner.getPackageName() + ".ExtendsGenericOuter");
    JClassType extendsInner =
        typeOracle.getType(CU_GenericOuterInner.getPackageName()
            + ".ExtendsGenericOuter.ExtendsInner");
    assertNull(extendsOuter.getEnclosingType());
    assertEquals(extendsOuter, extendsInner.getEnclosingType());

    JClassType outerSuper = extendsOuter.getSuperclass();
    JParameterizedType outerSuperParam = outerSuper.isParameterized();
    assertNotNull(outerSuperParam);
    assertEquals(outer, outerSuperParam.getBaseType());
    JClassType innerSuper = extendsInner.getSuperclass();
    assertEquals("GenericOuter.Inner", innerSuper.getName());
    field = inner.getField("field");
    assertNotNull(field);

    /*
     * This test fails for OpenJDK compiled classes compared to JDT classes. The
     * reason is that the superclass of this type doesn't contain a type
     * signature for OpenJDK byte code.
     *
     * Commenting out this code for the tests: I'm not sure any generators
     * depend on this subtle difference.
     */
    // assertEquals("java.lang.String",
    // field.getType().getQualifiedSourceName());
    // JParameterizedType innerSuperParam = innerSuper.isParameterized();
    // assertNotNull(innerSuperParam);
    // assertEquals(inner, innerSuperParam.getBaseType());
  }

  public void testEnclosingType() throws TypeOracleException {
    addTestResource(CU_Object);
    addTestResource(CU_List);
    addTestResource(CU_ListAsField);

    buildTypeOracle();

    JClassType listAsField = typeOracle.getType(CU_ListAsField.getTypeName());
    assertNotNull(listAsField);
    assertNull(listAsField.isParameterized());
    JField field = listAsField.getField("field");
    assertNotNull(field);
    JType fieldType = field.getType();
    JParameterizedType fieldParamType = fieldType.isParameterized();
    assertNotNull(fieldParamType);
    assertNull(fieldParamType.getEnclosingType());
    JGenericType baseType = fieldParamType.getBaseType();
    assertNotNull(baseType);
    assertEquals("java.util.List", baseType.getQualifiedSourceName());
  }

  public void testFieldsAndTypes() throws TypeOracleException {
    addTestResource(CU_Object);
    addTestResource(CU_DefaultClass);
    addTestResource(CU_FieldsAndTypes);
    buildTypeOracle();

    assertNotNull(typeOracle.findType(CU_Object.getTypeName()));
    assertNotNull(typeOracle.findType(CU_DefaultClass.getTypeName()));
    assertNotNull(typeOracle.findType(CU_FieldsAndTypes.getTypeName()));
    assertEquals(3, typeOracle.getTypes().length);
  }

  // Check that anonymous classes are not reflected in TypeOracle
  public void testLocal() throws TypeOracleException {
    addTestResource(CU_Object);
    addTestResource(CU_EnclosingLocalClass);
    buildTypeOracle();
    JClassType[] types = typeOracle.getTypes();
    assertEquals(2, types.length);
  }

  public void testLocalWithSynthetic() throws TypeOracleException {
    addTestResource(CU_Object);
    addTestResource(CU_EnclosingLocalWithMember);
    buildTypeOracle();

    assertNotNull(typeOracle.findType(CU_EnclosingLocalWithMember.getTypeName()));
    assertNotNull(typeOracle.findType(CU_Object.getTypeName()));
    assertEquals(2, typeOracle.getTypes().length);
  }

  public void testMethodsAndParams() throws TypeOracleException {
    addTestResource(CU_Object);
    addTestResource(CU_Throwable);
    addTestResource(CU_MethodsAndParams);
    buildTypeOracle();

    assertNotNull(typeOracle.findType(CU_Object.getTypeName()));
    assertNotNull(typeOracle.findType(CU_Throwable.getTypeName()));
    assertNotNull(typeOracle.findType(CU_MethodsAndParams.getTypeName()));
    // Can't make an assertion about the # of loaded types in this test since the results are
    // different for source versus bytecode.
  }

  public void testOuterInner() throws TypeOracleException {
    addTestResource(CU_Object);
    addTestResource(CU_OuterInner);
    buildTypeOracle();

    assertNotNull(typeOracle.findType(CU_Object.getTypeName()));
    assertNotNull(typeOracle.findType(CU_OuterInner.getTypeName()));
    JClassType outer = typeOracle.findType("com.google.gwt.dev.javac.mediatortest.Outer");
    assertEquals(3, typeOracle.getTypes().length);
    assertNotNull(outer);
    assertEquals("Outer", outer.getSimpleSourceName());
    JClassType superclass = outer.getSuperclass();
    JType objectRef = typeOracle.getJavaLangObject();
    assertNotNull(objectRef);
    assertEquals(objectRef, superclass);
  }

  /**
   * Tests that we can build nested parameterized types even if that happens
   * while the type oracle is being built. This test assumes that
   * CU_ReferencesParameterizedTypeBeforeItsGenericFormHasBeenProcessed will
   * cause a parameterized form of CU_DeclaresInnerGenericInterface to be
   * created before the type oracle has had a chance to resolve
   * CU_DeclaresInnerGenericInterface.
   */
  public void testParameterizedTypeBuildDependencies() throws TypeOracleException {
    addTestResource(CU_ReferencesParameterizedTypeBeforeItsGenericFormHasBeenProcessed);
    // Intentionally omitting the ExtendsParameterizedInterface resource
    // addResource(CU_ExtendsParameterizedInterface);
    addTestResource(CU_DeclaresInnerGenericInterface);
    addTestResource(CU_Object);

    buildTypeOracle();

    assertNull(typeOracle.findType(CU_ExtendsParameterizedInterface.getTypeName()));
  }

  public void testPseudoInnerSourceName() throws TypeOracleException {
    addTestResource(CU_Object);
    resources.add(CU_PseudoInner);
    buildTypeOracle();

    JClassType pseudoInnerType = typeOracle.findType(CU_PseudoInner.getTypeName());
    assertNotNull(pseudoInnerType);
    assertEquals("Pseudo$Inner", pseudoInnerType.getSimpleSourceName());
    assertEquals(CU_PseudoInner.getTypeName(), pseudoInnerType.getQualifiedSourceName());
  }

  /**
   * Test that modifying a type will cause any types that depend on it to be
   * rebuilt by the TypeOracleUpdater during a refresh.
   *
   * @throws NotFoundException
   */
  public void testRefresh() throws TypeOracleException {
    addTestResource(CU_Object);
    addTestResource(CU_ExtendsGenericList);
    addTestResource(CU_GenericList);
    addTestResource(CU_ReferencesGenericListConstant);

    buildTypeOracle();

    // Get the types produced by the TypeOracle
    JClassType extendsGenericListType = typeOracle.getType(CU_ExtendsGenericList.getTypeName());
    JClassType genericListType = typeOracle.getType(CU_GenericList.getTypeName());
    JClassType referencesGenericListConstantType =
        typeOracle.getType(CU_ReferencesGenericListConstant.getTypeName());

    /*
     * Invalidate CU_GenericList and simulate a refresh. This should cause
     * anything that depends on GenericList to be rebuilt by the type oracle.
     */
    CU_GenericList.touch();
    buildTypeOracle();

    assertNotSame(genericListType.getQualifiedSourceName() + "; ", typeOracle
        .getType(CU_GenericList.getTypeName()), genericListType);
    assertNotSame(extendsGenericListType.getQualifiedSourceName() + "; ", typeOracle
        .getType(CU_ExtendsGenericList.getTypeName()), extendsGenericListType);

    /*
     * Make sure that referencing a constant field will cause a type to be
     * rebuilt if the constant changes.
     */
    assertNotSame(referencesGenericListConstantType.getQualifiedSourceName(), typeOracle
        .getType(CU_ReferencesGenericListConstant.getTypeName()), referencesGenericListConstantType);
  }

  public void testTypeParams() throws TypeOracleException {
    addTestResource(CU_Object);
    addTestResource(CU_NestedGenericInterfaces);
    addTestResource(CU_UnnestedImplementations);
    buildTypeOracle();

    assertNotNull(typeOracle.findType(CU_Object.getTypeName()));
    assertNotNull(typeOracle.findType(CU_NestedGenericInterfaces.getTypeName()));
    assertNotNull(typeOracle.findType("com.google.gwt.dev.javac.mediatortest.OuterInt"));
    assertNotNull(typeOracle.findType(CU_UnnestedImplementations.getTypeName()));
    assertNotNull(typeOracle.findType(CU_UnnestedImplementations.getTypeName() + ".OuterImpl"));

    JClassType innerImplType =
        typeOracle.findType(CU_UnnestedImplementations.getTypeName() + ".InnerImpl");
    assertNotNull(innerImplType);
    assertEquals(6, typeOracle.getTypes().length);
    JClassType[] interfaces = innerImplType.getImplementedInterfaces();
    assertEquals(1, interfaces.length);
    JClassType intf = interfaces[0];
    JParameterizedType intfParam = intf.isParameterized();
    assertNotNull(intfParam);
    JClassType intfEnclosing = intf.getEnclosingType();
    assertNotNull(intfEnclosing.isRawType());
  }

  /**
   * Creates a {@link Resource} and adds it the set of resources.
   */
  protected void addResource(String qualifiedTypeName, CharSequence source) {
    resources.add(new StaticJavaResource(qualifiedTypeName, source));
  }

  protected abstract void buildTypeOracle() throws TypeOracleException;

  /**
   * Tweak this if you want to see the log output.
   */
  protected TreeLogger createTreeLogger() {
    boolean reallyLog = false;
    if (reallyLog) {
      AbstractTreeLogger logger = new PrintWriterTreeLogger();
      logger.setMaxDetail(TreeLogger.ALL);
      return logger;
    } else {
      return TreeLogger.NULL;
    }
  }

  protected void addTestResource(CheckedJavaResource checkedResource) {
    resources.add(checkedResource);
    for (String typeName : checkedResource.getTypeNames()) {
      register(typeName, checkedResource);
    }
  }

  private void check(JClassType classInfo) throws NotFoundException {
    final String qName = classInfo.getQualifiedSourceName();
    CheckedJavaResource cup = publicTypeNameToTestCupMap.get(qName);
    if (cup != null) {
      cup.check(classInfo);
    }
  }

  private void register(String qualifiedTypeName, CheckedJavaResource cup) {
    assertFalse(publicTypeNameToTestCupMap.containsKey(qualifiedTypeName));
    publicTypeNameToTestCupMap.put(qualifiedTypeName, cup);
  }
}
