/*******************************************************************************
 * Copyright (c) 2007 - 2018 BEA Systems, Inc. and others
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *    wharley@bea.com - initial API and implementation
 *******************************************************************************/

package org.eclipse.jdt.apt.pluggable.tests;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.IPath;
import org.eclipse.jdt.apt.core.util.AptConfig;
import org.eclipse.jdt.apt.pluggable.tests.processors.filertester.FilerTesterProc;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.compiler.CategorizedProblem;
import org.eclipse.jdt.core.tests.builder.Problem;
import org.eclipse.jdt.internal.compiler.ClassFile;

import junit.framework.Test;
import junit.framework.TestSuite;

/**
 * Basic tests for the Filer interface in the IDE.
 * @see javax.annotation.processing.Filer
 */
public class FilerTests extends TestBase
{

	public FilerTests(String name) {
		super(name);
	}

	public static Test suite() {
		return new TestSuite(FilerTests.class);
	}

	/**
	 * Test generation of a source file, using the GenClass6 annotation
	 * @see javax.annotation.processing.Filer#createClassFile(CharSequence, javax.lang.model.element.Element...)
	 */
	public void testCreateSourceFile() throws Throwable
	{
		// Temporary workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=201931
		// Bail out on Linux
		String osName = System.getProperty("os.name");
		if (null == osName || !osName.contains("Windows")) {
			return;
		}

		IJavaProject jproj = createJavaProject(_projectName);
		IProject proj = jproj.getProject();
		IdeTestUtils.copyResources(proj, "targets/filer01a", "src/targets/filer");
		AptConfig.setEnabled(jproj, true);
		fullBuild();
		expectingNoProblems();

		// Check whether generated sources were generated and compiled
		expectingFile(proj, ".apt_generated/gen6/Generated01.java");
		final String[] expectedClasses = { "targets.filer.Parent01", "gen6.Generated01" };
		expectingUniqueCompiledClasses(expectedClasses);

		// Check whether non-source resource was generated in final round
		expectingFile( proj, ".apt_generated/summary.txt" );

		//TODO: if a parent file is modified,
		// the generated file should be regenerated and recompiled.
		// The generated resource file should be regenerated (but not compiled).

		// Modify target file to remove annotation and incrementally rebuild;
		// generated file should be deleted.
		IdeTestUtils.copyResources(proj, "targets/filer01b", "src/targets/filer");
		incrementalBuild();
		expectingNoProblems();

		final String[] expectedClasses2 = { "targets.filer.Parent01" };
		expectingUniqueCompiledClasses(expectedClasses2);

		expectingNoFile(proj, ".apt_generated/gen6/Generated01.java");
		expectingNoFile( proj, ".apt_generated/summary.txt" );
	}

	/**
	 * Test generation of a source file that is referenced by the parent, using the GenClass6 annotation
	 * @see javax.annotation.processing.Filer#createSourceFile(CharSequence, javax.lang.model.element.Element...)
	 */
	public void testCreateSourceFileWithGenReference() throws Throwable
	{
		// Temporary workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=201931
		// Bail out on Linux
		String osName = System.getProperty("os.name");
		if (null == osName || !osName.contains("Windows")) {
			return;
		}

		IJavaProject jproj = createJavaProject(_projectName);
		IProject proj = jproj.getProject();
		IdeTestUtils.copyResources(proj, "targets/filer02a", "src/targets/filer");

		disableJava5Factories(jproj);
		AptConfig.setEnabled(jproj, true);
		fullBuild();
		expectingNoProblems();

		// Check whether generated sources were generated and compiled
		expectingFile(proj, ".apt_generated/gen6/Generated02.java");
		final String[] expectedClasses = { "targets.filer.Parent02", "gen6.Generated02" };
		expectingUniqueCompiledClasses(expectedClasses);

		// Modify target file to change name of generated file and incrementally rebuild;
		// generated file should be deleted.
		IdeTestUtils.copyResources(proj, "targets/filer02b", "src/targets/filer");
		incrementalBuild();

		IPath parentPath = proj.getFullPath().append("src/targets/filer/Parent02.java");
		expectingOnlySpecificProblemFor(parentPath, new Problem("Parent02", "gen6.Generated02 cannot be resolved to a type", parentPath, 842, 858, CategorizedProblem.CAT_TYPE, IMarker.SEVERITY_ERROR));

		// This test only works if there are no Java 5 processors (e.g., apt.tests plugin) in the factory path.
		// If Java 5 processors are present, then gen6.Generated02 will also be recompiled, before it's deleted.
		final String[] expectedClasses2 = { "gen6.XxxGenerated02", "targets.filer.Parent02" };
		expectingUniqueCompiledClasses(expectedClasses2);

		expectingNoFile(proj, ".apt_generated/gen6/Generated02.java");
	}

	/**
	 * Test generation of a source file that is referenced by the parent, using the GenClass6 annotation.
	 * Processor calls getEnclosedElements(), causing the to-be-generated type to be resolved before it exists.
	 */
	public void testBug269934() throws Throwable
	{
		// Temporary workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=201931
		// Bail out on Linux
		String osName = System.getProperty("os.name");
		if (null == osName || !osName.contains("Windows")) {
			return;
		}

		IJavaProject jproj = createJavaProject(_projectName);
		IProject proj = jproj.getProject();
		IdeTestUtils.copyResources(proj, "targets/filer03a", "src/targets/filer");

		disableJava5Factories(jproj);
		AptConfig.setEnabled(jproj, true);
		fullBuild();
		// Without the fix for bug 269934, the following lines must be uncommented for the test to pass:
			// IPath parentPath = proj.getFullPath().append("src/targets/filer/Parent03.java");
			// Problem problem = new Problem("Parent03", "Generated03 cannot be resolved to a type", parentPath, 992, 1003, CategorizedProblem.CAT_TYPE, IMarker.SEVERITY_ERROR);
			// expectingOnlySpecificProblemFor(parentPath, problem);
		// With the fix, the following line works:
		expectingNoProblems();

		// Check whether generated sources were generated and compiled
		expectingFile(proj, ".apt_generated/gen6/Generated03.java");
		final String[] expectedClasses = { "targets.filer.Parent03", "gen6.Generated03" };
		expectingUniqueCompiledClasses(expectedClasses);

		// Modify target file to and incrementally rebuild; file should be regenerated.
		IdeTestUtils.copyResources(proj, "targets/filer03b", "src/targets/filer");
		incrementalBuild();
		// Note that even with bug 269934, after an incremental build there are no problems reported.
		expectingNoProblems();
		expectingUniqueCompiledClasses(expectedClasses);
	}

	/**
	 * Call FilerTesterProc.testGetResource01(), which checks getResource() in SOURCE_OUTPUT location
	 */
	public void testGetResource01() throws Throwable {
		ProcessorTestStatus.reset();
		IJavaProject jproj = createJavaProject(_projectName);
		disableJava5Factories(jproj);
		IProject proj = jproj.getProject();
		IPath projPath = proj.getFullPath();

		env.addClass(projPath.append("src"), "p", "Trigger",
				"package p;\n" +
				"import org.eclipse.jdt.apt.pluggable.tests.annotations.FilerTestTrigger;\n" +
				"@FilerTestTrigger(test = \"testGetResource01\", arg0 = \"g\", arg1 = \"Test.java\")" +
				"public class Trigger {\n" +
				"}"
			);

		AptConfig.setEnabled(jproj, true);

		// FilerTesterProc looks for the existence and contents of this class:
		env.addClass(projPath.append(".apt_generated"), "g", "Test",
				FilerTesterProc.resource01FileContents);

		fullBuild();
		expectingNoProblems();
		assertTrue("Processor did not run", ProcessorTestStatus.processorRan());
		assertEquals("Processor reported errors", ProcessorTestStatus.NO_ERRORS, ProcessorTestStatus.getErrors());
	}

	/**
	 * Call FilerTesterProc.testGetResource02(), which checks getResource() in CLASS_OUTPUT location
	 */
	public void testGetResource02() throws Throwable {
		ProcessorTestStatus.reset();
		IJavaProject jproj = createJavaProject(_projectName);
		disableJava5Factories(jproj);
		IProject proj = jproj.getProject();
		IPath projPath = proj.getFullPath();

		env.addClass(projPath.append("src"), "p", "Trigger",
				"package p;\n" +
				"import org.eclipse.jdt.apt.pluggable.tests.annotations.FilerTestTrigger;\n" +
				"@FilerTestTrigger(test = \"testGetResource02\", arg0 = \"t\", arg1 = \"Test.txt\")" +
				"public class Trigger {\n" +
				"}"
			);

		AptConfig.setEnabled(jproj, true);

		// FilerTesterProc looks for the existence and contents of this file after it is copied to the output folder:
		IFolder textFileFolder = proj.getFolder("src/t");
		textFileFolder.create(false, true, null);
		IFile textFile = proj.getFile(textFileFolder.getProjectRelativePath().append("Test.txt"));
		InputStream textSource = new ByteArrayInputStream(FilerTesterProc.resource02FileContents.getBytes());
		textFile.create(textSource, false, null);

		fullBuild();
		expectingNoProblems();
		assertTrue("Processor did not run", ProcessorTestStatus.processorRan());
		assertEquals("Processor reported errors", ProcessorTestStatus.NO_ERRORS, ProcessorTestStatus.getErrors());
	}

	/**
	 * Call FilerTesterProc.testCreateNonSourceFile(), which creates a non-source output file
	 */
	public void testCreateNonSourceFile() throws Throwable {
		ProcessorTestStatus.reset();
		IJavaProject jproj = createJavaProject(_projectName);
		disableJava5Factories(jproj);
		IProject proj = jproj.getProject();
		IPath projPath = proj.getFullPath();

		env.addClass(projPath.append("src"), "p", "Trigger",
				"package p;\n" +
				"import org.eclipse.jdt.apt.pluggable.tests.annotations.FilerTestTrigger;\n" +
				"@FilerTestTrigger(test = \"testCreateNonSourceFile\", arg0 = \"t\", arg1 = \"Test.txt\")" +
				"public class Trigger {\n" +
				"}"
			);

		AptConfig.setEnabled(jproj, true);

		fullBuild();
		expectingNoProblems();
		assertTrue("Processor did not run", ProcessorTestStatus.processorRan());
		assertEquals("Processor reported errors", ProcessorTestStatus.NO_ERRORS, ProcessorTestStatus.getErrors());
	}

	/**
	 * Call FilerTesterProc.testNullParents(), which checks handling of null originatingElements
	 * in the Filer.createXxx() methods.
	 */
	public void testNullParents() throws Throwable {
		ProcessorTestStatus.reset();
		IJavaProject jproj = createJavaProject(_projectName);
		disableJava5Factories(jproj);
		IProject proj = jproj.getProject();
		IPath projPath = proj.getFullPath();

		env.addClass(projPath.append("src"), "p", "Trigger",
				"package p;\n" +
				"import org.eclipse.jdt.apt.pluggable.tests.annotations.FilerTestTrigger;\n" +
				"@FilerTestTrigger(test = \"testNullParents\", arg0 = \"t\", arg1 = \"Test\")" +
				"public class Trigger {\n" +
				"}"
		);

		AptConfig.setEnabled(jproj, true);

		fullBuild();
		expectingNoProblems();
		assertTrue("Processor did not run", ProcessorTestStatus.processorRan());
		assertEquals("Processor reported errors", ProcessorTestStatus.NO_ERRORS, ProcessorTestStatus.getErrors());

		expectingFile(proj, ".apt_generated/t/Test.java");
		expectingFile(proj, ".apt_generated/t/Test.txt");
		final String[] expectedClasses = { "p.Trigger", "t.Test" };
		expectingUniqueCompiledClasses(expectedClasses);
	}

	/**
	 * Call FilerTesterProc.testURI, which tests the FileObject.toUri() method on
	 * various different sorts of files
	 */
	public void testURI() throws Throwable {
		ProcessorTestStatus.reset();
		IJavaProject jproj = createJavaProject(_projectName);
		disableJava5Factories(jproj);
		IProject proj = jproj.getProject();
		IPath projPath = proj.getFullPath();

		env.addClass(projPath.append("src"), "p", "Trigger",
				"package p;\n" +
				"import org.eclipse.jdt.apt.pluggable.tests.annotations.FilerTestTrigger;\n" +
				"@FilerTestTrigger(test = \"testURI\", arg0 = \"t\", arg1 = \"Test.txt\")" +
				"public class Trigger {\n" +
				"}"
			);

		IFolder textFileFolder = proj.getFolder("src/t");
		textFileFolder.create(false, true, null);
		IFile textFile = proj.getFile(textFileFolder.getProjectRelativePath().append("Test.txt"));
		InputStream textSource = new ByteArrayInputStream(FilerTesterProc.helloStr.getBytes());
		textFile.create(textSource, false, null);

		AptConfig.setEnabled(jproj, true);

		fullBuild();
		expectingNoProblems();
		assertTrue("Processor did not run", ProcessorTestStatus.processorRan());
		assertEquals("Processor reported errors", ProcessorTestStatus.NO_ERRORS, ProcessorTestStatus.getErrors());
	}

	/**
	 * Call FilerTesterProc.testGetCharContentLarge(), which checks FileObject.getCharContent()
	 * for a large (multiple buffers long) file
	 */
	public void testGetCharContentLarge() throws Throwable {
		ProcessorTestStatus.reset();
		IJavaProject jproj = createJavaProject(_projectName);
		disableJava5Factories(jproj);
		IProject proj = jproj.getProject();
		IPath projPath = proj.getFullPath();

		env.addClass(projPath.append("src"), "p", "Trigger",
				"package p;\n" +
				"import org.eclipse.jdt.apt.pluggable.tests.annotations.FilerTestTrigger;\n" +
				"@FilerTestTrigger(test = \"testGetCharContentLarge\", arg0 = \"g\", arg1 = \"Test.java\")" +
				"public class Trigger {\n" +
				"}"
			);

		AptConfig.setEnabled(jproj, true);

		// FilerTesterProc looks for the existence and contents of this class:
		env.addClass(projPath.append(".apt_generated"), "g", "Test",
				FilerTesterProc.largeJavaClass());

		fullBuild();
		expectingNoProblems();
		assertTrue("Processor did not run", ProcessorTestStatus.processorRan());
		assertEquals("Processor reported errors", ProcessorTestStatus.NO_ERRORS, ProcessorTestStatus.getErrors());
	}
	public void _testBug534979() throws Throwable {
		ProcessorTestStatus.reset();
		IJavaProject jproj = createJavaProject(_projectName);
		disableJava5Factories(jproj);
		IProject proj = jproj.getProject();
		IPath projPath = proj.getFullPath();

		env.addClass(projPath.append("src"), "p", "Trigger",
				"package p;\n" +
				"import org.eclipse.jdt.apt.pluggable.tests.annotations.FilerTestTrigger;\n" +
				"@FilerTestTrigger(test = \"testBug534979\", arg0 = \"p\", arg1 = \"Trigger\")" +
				"public class Trigger {\n" +
				"}"
		); 

		AptConfig.setEnabled(jproj, true);
		fullBuild();
		assertEquals("Processor reported errors", "FilerException invoking test method testBug534979 - see console for details", ProcessorTestStatus.getErrors());
	}

	public void _testCollisionInOtherModule() throws Throwable {
		if (!canRunJava9()) {
			return;
		}
		ProcessorTestStatus.reset();
		IJavaProject jproj = createJava9Project(_projectName);
		disableJava5Factories(jproj);
		IProject proj = jproj.getProject();
		IPath projPath = proj.getFullPath();

		IPath root = projPath.append("src");
		env.addClass(root, null, "module-info", "module example {requires annotations;}");
		env.addClass(root, "p", "Trigger",
				"package p;\n" +
				"import org.eclipse.jdt.apt.pluggable.tests.annotations.FilerTestTrigger;\n" +
				"@FilerTestTrigger(test = \"testBug534979\", arg0 = \"java.util\", arg1 = \"HashMap\")" +
				"public class Trigger {\n" +
				"}"
		); 

		AptConfig.setEnabled(jproj, true);
		fullBuild();
		assertEquals("Processor reported errors", "FilerException invoking test method testBug534979 - see console for details", ProcessorTestStatus.getErrors());
	}
	public void _testCollisionWithClassThatTriggers() throws Throwable {
		if (!canRunJava9()) {
			return;
		}
		ProcessorTestStatus.reset();
		IJavaProject jproj = createJava9Project(_projectName);
		disableJava5Factories(jproj);
		IProject proj = jproj.getProject();
		IPath projPath = proj.getFullPath();

		IPath root = projPath.append("src");
		env.addClass(root, null, "module-info", "module example {requires annotations;}");
		env.addClass(root, "p", "Trigger",
				"package p;\n" +
				"import org.eclipse.jdt.apt.pluggable.tests.annotations.FilerTestTrigger;\n" +
				"@FilerTestTrigger(test = \"testBug534979\", arg0 = \"p\", arg1 = \"Trigger\")" +
				"public class Trigger {\n" +
				"}"
		); 

		AptConfig.setEnabled(jproj, true);
		fullBuild();
		assertEquals("Processor reported errors", "FilerException invoking test method testBug534979 - see console for details", ProcessorTestStatus.getErrors());
	}
	public void testNoCollisionInSameModule() throws Throwable {
		if (!canRunJava9()) {
			return;
		}
		ProcessorTestStatus.reset();
		IJavaProject jproj = createJava9Project(_projectName);
		disableJava5Factories(jproj);
		IProject proj = jproj.getProject();
		IPath projPath = proj.getFullPath();

		IPath root = projPath.append("src");
		env.addClass(root, null, "module-info", "module example {requires annotations;}");
		env.addClass(root, "p", "Trigger",
				"package p;\n" +
				"import org.eclipse.jdt.apt.pluggable.tests.annotations.FilerTestTrigger;\n" +
				"@FilerTestTrigger(test = \"testBug534979\", arg0 = \"p\", arg1 = \"Other\")" +
				"public class Trigger {\n" +
				"}"
		); 

		AptConfig.setEnabled(jproj, true);
		fullBuild();
		assertEquals("Processor reported errors", ProcessorTestStatus.NO_ERRORS, ProcessorTestStatus.getErrors());
	}
	public void _testBug542090() throws Throwable {
		if (!canRunJava9()) {
			return;
		}
		ProcessorTestStatus.reset();
		IJavaProject jproj = createJava9Project(_projectName);
		disableJava5Factories(jproj);
		IProject proj = jproj.getProject();
		IPath projPath = proj.getFullPath();

		IPath root = projPath.append("src");
		env.addClass(root, null, "module-info", "module example {requires annotations;}");
		env.addClass(root, "p", "Trigger",
				"package p;\n" +
				"import org.eclipse.jdt.apt.pluggable.tests.annotations.FilerTestTrigger;\n" +
				"@FilerTestTrigger(test = \"testBug542090a\", arg0 = \"p\", arg1 = \"Other\")" +
				"public class Trigger {\n" +
				"}"
		); 

		AptConfig.setEnabled(jproj, true);
		fullBuild();
		env.addClass(root, "p", "Trigger",
				"package p;\n" +
				"import org.eclipse.jdt.apt.pluggable.tests.annotations.FilerTestTrigger;\n" +
				"@FilerTestTrigger(test = \"testBug542090a\", arg0 = \"p\", arg1 = \"Other\")" +
				"public class Trigger {\n" +
				"}/*added comment */"
		); 
		incrementalBuild();
		assertEquals("Processor reported errors", ProcessorTestStatus.NO_ERRORS, ProcessorTestStatus.getErrors());

		env.addClass(root, "p", "Trigger",
				"package p;\n" +
				"import org.eclipse.jdt.apt.pluggable.tests.annotations.FilerTestTrigger;\n" +
				"@FilerTestTrigger(test = \"testBug542090b\", arg0 = \"p\", arg1 = \"Other\")" +
				"public class Trigger {\n" +
				"}/*added comment */"
		); 
		incrementalBuild();
		assertEquals("Processor reported errors", "FilerException invoking test method testBug542090b - see console for details", ProcessorTestStatus.getErrors());
	}

	public void testCreateClass1() throws Exception {
		FilerTesterProc.roundNo = 0;
		ProcessorTestStatus.reset();
		IJavaProject jproj = createJavaProject(_projectName);
		disableJava5Factories(jproj);
		IProject proj = jproj.getProject();
		IPath projPath = proj.getFullPath();

		env.addClass(projPath.append("src"), "p", "Trigger",
				"package p;\n" +
				"import org.eclipse.jdt.apt.pluggable.tests.annotations.FilerTestTrigger;\n" +
				"@FilerTestTrigger(test = \"testCreateClass1\", arg0 = \"p\", arg1 = \"Test.java\")" +
				"public class Trigger {\n" +
				"}"
			);
		AptConfig.setEnabled(jproj, true);

		fullBuild();
		final String[] expectedClasses = {"p.Trigger" };
		expectingUniqueCompiledClasses(expectedClasses);
		IPath path = proj.getLocation().append("bin/p/Trigger.class");
		File file = new File(path.toOSString());
		assertTrue("File should exist", file.exists());
		long lastModified = file.lastModified();
		Thread.sleep(1000);
		ClassFile[] classFiles = this.debugRequestor.getClassFiles();
		FilerTesterProc.classContent = classFiles[0].getBytes();
		env.addClass(projPath.append(".apt_generated"), "g", "Test",
				"package g;\n" +
				"import org.eclipse.jdt.apt.pluggable.tests.annotations.FilerTestTrigger;\n" +
				"@FilerTestTrigger(test = \"testCreateClass1\",arg0 = \"g\",arg1 = \"Test.java\") " +
				"public class Test { }"
		);

		incrementalBuild();
		assertEquals("should have triggered 5 rounds", 5, FilerTesterProc.roundNo);
		assertTrue("File should exist", file.exists());
		long lastModified2 = file.lastModified();
		assertTrue("file should have been overwritten", (lastModified2 > lastModified));
	}
	public void testCreateClass2() throws Exception {
		FilerTesterProc.roundNo = 0;
		ProcessorTestStatus.reset();
		IJavaProject jproj = createJavaProject(_projectName);
		disableJava5Factories(jproj);
		IProject proj = jproj.getProject();
		IPath projPath = proj.getFullPath();

		env.addClass(projPath.append("src"), "p", "Trigger",
				"package p;\n" +
				"import org.eclipse.jdt.apt.pluggable.tests.annotations.FilerTestTrigger;\n" +
				"@FilerTestTrigger(test = \"testCreateClass2\", arg0 = \"p\", arg1 = \"Test.java\")" +
				"public class Trigger {\n" +
				"}"
			);
		AptConfig.setEnabled(jproj, true);

		fullBuild();
		final String[] expectedClasses = {"p.Trigger" };
		expectingUniqueCompiledClasses(expectedClasses);
		IPath path = proj.getLocation().append("bin/p/Trigger.class");
		File file = new File(path.toOSString());
		assertTrue("File should exist", file.exists());
		long lastModified = file.lastModified();
		Thread.sleep(1000);
		ClassFile[] classFiles = this.debugRequestor.getClassFiles();
		FilerTesterProc.classContent = classFiles[0].getBytes();
		env.addClass(projPath.append(".apt_generated"), "g", "Test",
				"package g;\n" +
				"import org.eclipse.jdt.apt.pluggable.tests.annotations.FilerTestTrigger;\n" +
				"@FilerTestTrigger(test = \"testCreateClass2\",arg0 = \"g\",arg1 = \"Test.java\") " +
				"public class Test { }"
		);

		incrementalBuild();
		assertEquals("should have triggered 5 rounds", 5, FilerTesterProc.roundNo);
		assertTrue("File should exist", file.exists());
		long lastModified2 = file.lastModified();
		assertTrue("file should have been overwritten", (lastModified2 > lastModified));
	}
}
