/*
 * Copyright 2012 Google Inc. All Rights Reserved.
 *
 * 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.devtools.j2objc.types;

import com.google.devtools.j2objc.GenerationTest;
import com.google.devtools.j2objc.util.HeaderMap;
import java.io.IOException;

/**
 * Unit tests for the {@link ImplementationImportCollector} class.
 *
 * @author Tom Ball
 */
public class ImplementationImportCollectorTest extends GenerationTest {

  // Verify that invoked method's return value has associated header.
  public void testMethodReturnHasHeader() throws IOException {
    addSourceFile("class FooException extends Exception { AssertionError asAssertion() { "
        + "return new AssertionError(this); }}", "FooException.java");
    addSourceFile(
        "class FooMaker { static FooException makeException() { return new FooException(); }}",
        "FooMaker.java");
    String translation = translateSourceFile(
        "class A { void test() { "
        + "throw FooMaker.makeException().asAssertion(); }}", "A", "A.m");
    assertTranslation(translation, "#include \"FooException.h\"");
  }

  // http://b/7073329
  public void testVarargsMethodNoActualArguments() throws IOException {
    translateSourceFile(
        "class Test { Test(String ... values) { } Test test = new Test(); }",
        "Test", "Test.m");
    // Nothing to do; Successful translation is the test.
  }

  public void testOneArgumentAndVarargs() throws IOException {
    translateSourceFile(
        "class Test { Test(int count, int ... values) { } Test test = new Test(2, 42, 63); }",
        "Test", "Test.m");
    // Nothing to do; Successful translation is the test.
  }

  // http://b/7106570
  public void testVarargsMethodManyArguments() throws IOException {
    translateSourceFile(
        "class Test { Test(int... values) { } Test test = new Test(1,2,3); }",
        "Test", "Test.m");
    // Nothing to do; Successful translation is the test.
  }

  public void testBooleanArrayImport() throws IOException {
    addSourceFile("class A { boolean[] b; }", "A.java");
    String translation = translateSourceFile(
        "class B { int test() { return new A().b.length; }}", "B", "B.m");
    assertTranslation(translation, "#include \"IOSPrimitiveArray.h\"");
  }

  public void testPrimitiveArrayAsParameterImport() throws IOException {
    addSourceFile(
        "class B { protected char bits[]; public B(char[] bits_) { bits = bits_;} }", "B.java");
    String translation = translateSourceFile(
        "class A { public static final B test = new B(new char[]{'a', 'b'}); }", "A", "A.m");
    assertTranslation(translation, "#include \"IOSPrimitiveArray.h\"");
  }

  public void testObjectArrayImport() throws IOException {
    String translation = translateSourceFile(
        "import java.util.BitSet; class A { public BitSet[] test = new BitSet[3]; }", "A", "A.m");
    assertTranslation(translation, "#include \"IOSObjectArray.h\"");
    assertTranslation(translation, "#include \"java/util/BitSet.h\"");
  }

  public void testEnhancedForMethodInvocation() throws IOException {
    addSourceFile("import java.util.*; class A { "
        + "final Map<String,String> map = new HashMap<>(); }", "A.java");
    String translation = translateSourceFile(
        "import java.util.*; class B extends A { "
        + "void test() { for (String s : map.keySet()) {}}}", "B", "B.m");
    assertTranslation(translation, "#include \"java/util/Map.h\"");
  }

  public void testReturnTypeOfSuperclassMethod() throws IOException {
    addSourceFile("interface I {}", "I.java");
    addSourceFile("class A implements I {}", "A.java");
    addSourceFile("class B { A getAnA() { return new A(); } }", "B.java");
    String translation = translateSourceFile(
        "class C extends B { void test() { I i = getAnA(); } }", "C", "C.m");
    assertTranslation(translation, "#include \"A.h\"");
  }

  // Verify that a primitive type literal has a wrapper class import.
  public void testPrimitiveTypeLiteral() throws IOException {
    // Use assignment to a @Weak object to ensure the IOSClass import isn't
    // picked up by visiting a method or function invocation.
    String translation = translateSourceFile(
        "import com.google.j2objc.annotations.Weak; "
        + "class Test { @Weak Object o; void test() { o = double.class; } }",
        "Test", "Test.m");
    assertTranslation(translation, "#include \"IOSClass.h\"");
  }

  // Verify that an object array type literal imports IOSClass.
  public void testArrayTypeLiteralImport() throws IOException {
    String translation = translateSourceFile(
        "class Test { Class arrayType() { return Object[].class; }}",
        "Test", "Test.m");
    assertTranslation(translation, "#include \"IOSClass.h\"");
  }

  // Verify that a multi-dimensional array declaration imports IOSObjectArray.
  public void testMultiArrayTypeLiteralImport() throws IOException {
    String translation = translateSourceFile(
        "class Test { String[][] map = { { \"1\", \"one\" }, { \"2\", \"two\" } }; }",
        "Test", "Test.m");
    assertTranslation(translation, "#include \"IOSObjectArray.h\"");
  }

  // Verify that a multi-catch clause imports are all collected.
  public void testMultiCatchClauses() throws IOException {
    String translation = translateSourceFile(
        "class Test { void test() {"
        + "  try { System.out.println(); } catch (ArithmeticException | AssertionError | "
        + "      ClassCastException | SecurityException e) {} }}",
        "Test", "Test.m");
    assertTranslation(translation, "#include \"java/lang/ArithmeticException.h\"");
    assertTranslation(translation, "#include \"java/lang/AssertionError.h\"");
    assertTranslation(translation, "#include \"java/lang/ClassCastException.h\"");
    assertTranslation(translation, "#include \"java/lang/SecurityException.h\"");
  }

  // Verify that platform class packages aren't truncated with --no-package-directories.
  public void testPlatformImports() throws IOException {
    options.getHeaderMap().setOutputStyle(HeaderMap.OutputStyleOption.NONE);
    String translation = translateSourceFile(
        "package foo.bar; import org.xml.sax.*; import org.xml.sax.helpers.*; "
        + "class Test { XMLReader test() { "
        + "  try { return XMLReaderFactory.createXMLReader(); } catch (SAXException e) {} "
        + "  return null; }}",
        "Test", "Test.m");

    // Test file's import should not have package.
    assertTranslation(translation, "#include \"Test.h\"");

    // Platform file's imports should.
    assertTranslation(translation, "#include \"org/xml/sax/SAXException.h\"");
    assertTranslation(translation, "#include \"org/xml/sax/XMLReader.h\"");
    assertTranslation(translation, "#include \"org/xml/sax/helpers/XMLReaderFactory.h\"");
  }

  // Verify that platform class packages aren't changed with --preserve-full-paths.
  public void testPlatformImportsSourceDirs() throws IOException {
    options.getHeaderMap().setOutputStyle(HeaderMap.OutputStyleOption.SOURCE);
    String translation = translateSourceFile(
        "package foo.bar; import org.xml.sax.*; import org.xml.sax.helpers.*; "
        + "class Test { XMLReader test() { "
        + "  try { return XMLReaderFactory.createXMLReader(); } catch (SAXException e) {} "
        + "  return null; }}",
        "Test", "Test.m");

    // Test file's import should not have package.
    assertTranslation(translation, "#include \"Test.h\"");

    // Platform file's imports should.
    assertTranslation(translation, "#include \"org/xml/sax/SAXException.h\"");
    assertTranslation(translation, "#include \"org/xml/sax/XMLReader.h\"");
    assertTranslation(translation, "#include \"org/xml/sax/helpers/XMLReaderFactory.h\"");
  }

  public void testAddsHeaderForRenamedMainType() throws IOException {
    String translation = translateSourceFile(
        "package foo; import com.google.j2objc.annotations.ObjectiveCName;"
        + " @ObjectiveCName(\"Bar\") class Test {}", "foo.Test", "foo/Test.m");
    assertTranslation(translation, "#include \"foo/Test.h\"");
  }

  public void testImportsNativeExpressionType() throws IOException {
    addSourceFile("class Foo { "
        + "  static java.util.List list = java.util.Collections.EMPTY_LIST; "
        + "}", "Foo.java");
    String translation = translateSourceFile("class Test { void test() { "
        + "java.util.Collection c = null; c = Foo.list; }}", "Test", "Test.m");
    assertTranslation(translation, "#include \"java/util/List.h\"");
  }

  public void testNoImportsForAbstractMethod() throws IOException {
    String translation = translateSourceFile(
        "interface Test { java.util.Map foo(java.util.List l); }", "Test", "Test.m");
    assertNotInTranslation(translation, "Map.h");
    assertNotInTranslation(translation, "List.h");
  }

  public void testReturnExpressionTypeIncluded() throws IOException {
    addSourceFile("interface I {}", "I.java");
    addSourceFile("class J implements I {}", "J.java");
    addSourceFile("class A { J j; }", "A.java");
    String translation = translateSourceFile(
        "class B extends A { I foo() { return j; } }", "B", "B.m");
    assertTranslation(translation, "#include \"J.h\"");
  }

  // Issue #802.
  public void testEnhancedForStatementExpressionTypeIncluded() throws IOException {
    addSourceFile("class A { public java.util.ArrayList<String> list; }", "A.java");
    String translation = translateSourceFile(
        "class B extends A { void foo() { if (list != null) { for (String elem : list) { "
        + "elem.hashCode(); } } } }", "B", "B.m");
    assertTranslation(translation, "#include \"java/util/ArrayList.h\"");
  }

  // Issue #924.
  public void testImportMultipleOuterClass() throws IOException {
    createClassFile("a.b.c.Test", "package a.b.c; public class Test {} class Foo {}");
    parser.addClasspathEntry(tempDir.getAbsolutePath());
    String translation = translateSourceFile(
        "package a.b.c; public class Bar { Object test() { return new Foo(); }}",
        "a.b.c.Bar", "a/b/c/Bar.m");

    // Verify that Test.h is imported, since that's what is generated from Test.java.
    assertTranslation(translation, "#include \"a/b/c/Test.h\"");
    assertNotInTranslation(translation, "#include \"a/b/c/Foo.h\"");
  }
}
