/*******************************************************************************
 * Copyright (c) 2017 Red Hat Inc. and others.
 * All rights reserved. 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:
 *     Red Hat Inc. - initial API and implementation
 *******************************************************************************/
package org.eclipse.jdt.ls.core.internal.handlers;

import static org.eclipse.jdt.ls.core.internal.Lsp4jAssertions.assertRange;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import org.apache.commons.io.FileUtils;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.manipulation.CoreASTProvider;
import org.eclipse.jdt.ls.core.internal.JDTUtils;
import org.eclipse.jdt.ls.core.internal.JavaClientConnection;
import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin;
import org.eclipse.jdt.ls.core.internal.ProjectUtils;
import org.eclipse.jdt.ls.core.internal.ResourceUtils;
import org.eclipse.jdt.ls.core.internal.WorkspaceHelper;
import org.eclipse.jdt.ls.core.internal.managers.AbstractProjectsManagerBasedTest;
import org.eclipse.jdt.ls.core.internal.preferences.ClientPreferences;
import org.eclipse.jdt.ls.core.internal.preferences.Preferences;
import org.eclipse.jdt.ls.core.internal.preferences.Preferences.Severity;
import org.eclipse.lsp4j.CodeAction;
import org.eclipse.lsp4j.CodeActionContext;
import org.eclipse.lsp4j.CodeActionKind;
import org.eclipse.lsp4j.CodeActionParams;
import org.eclipse.lsp4j.Command;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.DidChangeTextDocumentParams;
import org.eclipse.lsp4j.DidCloseTextDocumentParams;
import org.eclipse.lsp4j.DidOpenTextDocumentParams;
import org.eclipse.lsp4j.DidSaveTextDocumentParams;
import org.eclipse.lsp4j.PublishDiagnosticsParams;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.TextDocumentContentChangeEvent;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.TextDocumentItem;
import org.eclipse.lsp4j.VersionedTextDocumentIdentifier;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public class DocumentLifeCycleHandlerTest extends AbstractProjectsManagerBasedTest {

	private CoreASTProvider sharedASTProvider;

	private DocumentLifeCycleHandler lifeCycleHandler;
	private JavaClientConnection javaClient;

	private File temp;

	@Mock
	private ClientPreferences clientPreferences;

	@Before
	public void setup() throws Exception {
		mockPreferences();

		sharedASTProvider = CoreASTProvider.getInstance();
		sharedASTProvider.disposeAST();
		//		sharedASTProvider.clearASTCreationCount();
		javaClient = new JavaClientConnection(client);
		lifeCycleHandler = new DocumentLifeCycleHandler(javaClient, preferenceManager, projectsManager, false);
		JavaLanguageServerPlugin.getNonProjectDiagnosticsState().setGlobalErrorLevel(true);
	}

	@After
	public void tearDown() throws Exception {
		JavaLanguageServerPlugin.getNonProjectDiagnosticsState().setGlobalErrorLevel(true);
		javaClient.disconnect();
		for (ICompilationUnit cu : JavaCore.getWorkingCopies(null)) {
			cu.discardWorkingCopy();
		}
		FileUtils.deleteQuietly(temp);
	}

	@Test
	public void testUnimplementedMethods() throws Exception {
		IJavaProject javaProject = newEmptyProject();
		IPackageFragmentRoot sourceFolder = javaProject.getPackageFragmentRoot(javaProject.getProject().getFolder("src"));
		IPackageFragment pack1 = sourceFolder.createPackageFragment("test1", false, null);

		StringBuilder buf = new StringBuilder();
		buf.append("package test1;\n");
		buf.append("public interface E {\n");
		buf.append("    void foo();\n");
		buf.append("}\n");
		pack1.createCompilationUnit("E.java", buf.toString(), false, null);

		buf = new StringBuilder();
		buf.append("package test1;\n");
		buf.append("public class F implements E {\n");
		buf.append("}\n");
		ICompilationUnit cu = pack1.createCompilationUnit("F.java", buf.toString(), false, null);
		openDocument(cu, cu.getSource(), 1);

		List<Either<Command, CodeAction>> codeActions = getCodeActions(cu);
		assertEquals(codeActions.size(), 1);
		assertEquals(codeActions.get(0).getRight().getKind(), CodeActionKind.QuickFix);
	}

	@Test
	public void testRemoveDeadCodeAfterIf() throws Exception {
		IJavaProject javaProject = newEmptyProject();
		IPackageFragmentRoot sourceFolder = javaProject.getPackageFragmentRoot(javaProject.getProject().getFolder("src"));
		IPackageFragment pack1 = sourceFolder.createPackageFragment("test1", false, null);

		StringBuilder buf = new StringBuilder();
		buf.append("package test1;\n");
		buf.append("public class E {\n");
		buf.append("    public boolean foo(boolean b1) {\n");
		buf.append("        if (false) {\n");
		buf.append("            return true;\n");
		buf.append("        }\n");
		buf.append("        return false;\n");
		buf.append("    }\n");
		buf.append("}\n");
		ICompilationUnit cu = pack1.createCompilationUnit("E.java", buf.toString(), false, null);

		List<Either<Command, CodeAction>> codeActions = getCodeActions(cu);
		assertEquals(codeActions.size(), 1);
		assertEquals(codeActions.get(0).getRight().getKind(), CodeActionKind.QuickFix);
	}

	protected List<Either<Command, CodeAction>> getCodeActions(ICompilationUnit cu) throws JavaModelException {

		CompilationUnit astRoot = CoreASTProvider.getInstance().getAST(cu, CoreASTProvider.WAIT_YES, null);
		IProblem[] problems = astRoot.getProblems();

		Range range = getRange(cu, problems);

		CodeActionParams parms = new CodeActionParams();

		TextDocumentIdentifier textDocument = new TextDocumentIdentifier();
		textDocument.setUri(JDTUtils.toURI(cu));
		parms.setTextDocument(textDocument);
		parms.setRange(range);
		CodeActionContext context = new CodeActionContext();
		context.setDiagnostics(DiagnosticsHandler.toDiagnosticsArray(cu, Arrays.asList(problems), true));
		context.setOnly(Arrays.asList(CodeActionKind.QuickFix));
		parms.setContext(context);

		return new CodeActionHandler(this.preferenceManager).getCodeActionCommands(parms, new NullProgressMonitor());
	}

	private Range getRange(ICompilationUnit cu, IProblem[] problems) throws JavaModelException {
		IProblem problem = problems[0];
		return JDTUtils.toRange(cu, problem.getSourceStart(), 0);
	}

	private Preferences mockPreferences() {
		Preferences mockPreferences = Mockito.mock(Preferences.class);
		Mockito.when(preferenceManager.getPreferences()).thenReturn(mockPreferences);
		Mockito.when(preferenceManager.getPreferences(Mockito.any())).thenReturn(mockPreferences);
		Mockito.when(mockPreferences.getIncompleteClasspathSeverity()).thenReturn(Severity.ignore);
		when(this.preferenceManager.getClientPreferences()).thenReturn(clientPreferences);
		when(clientPreferences.isSupportedCodeActionKind(CodeActionKind.QuickFix)).thenReturn(true);
		return mockPreferences;
	}

	@Test
	public void testBasicBufferLifeCycle() throws Exception {
		IJavaProject javaProject = newEmptyProject();
		IPackageFragmentRoot sourceFolder = javaProject.getPackageFragmentRoot(javaProject.getProject().getFolder("src"));
		IPackageFragment pack1 = sourceFolder.createPackageFragment("test1", false, null);

		StringBuilder buf = new StringBuilder();
		buf.append("package test1;\n");
		buf.append("public class E123 {\n");
		buf.append("}\n");
		ICompilationUnit cu1 = pack1.createCompilationUnit("E123.java", buf.toString(), false, null);

		assertEquals(false, cu1.isWorkingCopy());
		assertEquals(false, cu1.hasUnsavedChanges());
		assertNewProblemReported();
		assertEquals(0, getCacheSize());
		assertNewASTsCreated(0);

		openDocument(cu1, cu1.getSource(), 1);

		assertEquals(true, cu1.isWorkingCopy());
		assertEquals(false, cu1.hasUnsavedChanges());
		assertNewProblemReported(new ExpectedProblemReport(cu1, 0));
		assertEquals(1, getCacheSize());
		assertNewASTsCreated(1);

		buf = new StringBuilder();
		buf.append("package test1;\n");
		buf.append("public class E123 {\n");
		buf.append("  X x;\n");
		buf.append("}\n");

		changeDocumentFull(cu1, buf.toString(), 2);

		assertEquals(true, cu1.isWorkingCopy());
		assertEquals(true, cu1.hasUnsavedChanges());
		assertNewProblemReported(new ExpectedProblemReport(cu1, 1));
		assertEquals(1, getCacheSize());
		assertNewASTsCreated(1);

		saveDocument(cu1);

		assertEquals(true, cu1.isWorkingCopy());
		assertEquals(false, cu1.hasUnsavedChanges());
		assertNewProblemReported();
		assertEquals(1, getCacheSize());
		assertNewASTsCreated(0);

		closeDocument(cu1);

		assertEquals(false, cu1.isWorkingCopy());
		assertEquals(false, cu1.hasUnsavedChanges());
		assertNewProblemReported();
		assertEquals(0, getCacheSize());
		assertNewASTsCreated(0);
	}

	@Test
	public void testBasicBufferLifeCycleWithoutSave() throws Exception {
		IJavaProject javaProject = newEmptyProject();
		IPackageFragmentRoot sourceFolder = javaProject.getPackageFragmentRoot(javaProject.getProject().getFolder("src"));
		IPackageFragment pack1 = sourceFolder.createPackageFragment("test1", false, null);

		StringBuilder buf = new StringBuilder();
		buf.append("package test1;\n");
		buf.append("public class E123 {\n");
		buf.append("    public boolean foo() {\n");
		buf.append("        return x;\n");
		buf.append("    }\n");
		buf.append("}\n");
		ICompilationUnit cu1 = pack1.createCompilationUnit("E123.java", buf.toString(), false, null);

		openDocument(cu1, cu1.getSource(), 1);

		assertEquals(true, cu1.isWorkingCopy());
		assertEquals(false, cu1.hasUnsavedChanges());
		assertNewProblemReported(new ExpectedProblemReport(cu1, 1));
		assertEquals(1, getCacheSize());
		assertNewASTsCreated(1);

		buf = new StringBuilder();
		buf.append("package test1;\n");
		buf.append("public class E123 {\n");
		buf.append("    public boolean foo() {\n");
		buf.append("        return true;\n");
		buf.append("    }\n");
		buf.append("}\n");

		changeDocumentFull(cu1, buf.toString(), 2);

		assertEquals(true, cu1.isWorkingCopy());
		assertEquals(true, cu1.hasUnsavedChanges());
		assertNewProblemReported(new ExpectedProblemReport(cu1, 0));
		assertEquals(1, getCacheSize());
		assertNewASTsCreated(1);

		closeDocument(cu1);

		assertEquals(false, cu1.isWorkingCopy());
		assertEquals(false, cu1.hasUnsavedChanges());
		assertNewProblemReported(new ExpectedProblemReport(cu1, 1));
		assertEquals(0, getCacheSize());
		assertNewASTsCreated(0);
	}

	@Test
	public void testReconcile() throws Exception {
		IJavaProject javaProject = newEmptyProject();
		IPackageFragmentRoot sourceFolder = javaProject.getPackageFragmentRoot(javaProject.getProject().getFolder("src"));
		IPackageFragment pack1 = sourceFolder.createPackageFragment("test1", false, null);
		StringBuilder buf = new StringBuilder();
		buf.append("package test1;\n");
		buf.append("public class E123 {\n");
		buf.append("    public void testing() {\n");
		buf.append("        int someIntegerChanged = 5;\n");
		buf.append("        int i = someInteger + 5\n");
		buf.append("    }\n");
		buf.append("}\n");
		ICompilationUnit cu1 = pack1.createCompilationUnit("E123.java", buf.toString(), false, null);
		openDocument(cu1, cu1.getSource(), 1);
		assertEquals(true, cu1.isWorkingCopy());
		assertEquals(false, cu1.hasUnsavedChanges());
		List<PublishDiagnosticsParams> diagnosticsParams = getClientRequests("publishDiagnostics");
		assertEquals(1, diagnosticsParams.size());
		PublishDiagnosticsParams diagnosticsParam = diagnosticsParams.get(0);
		List<Diagnostic> diagnostics = diagnosticsParam.getDiagnostics();
		assertEquals(2, diagnostics.size());
		diagnosticsParams.clear();
		closeDocument(cu1);
	}

	@Test
	public void testIncrementalChangeDocument() throws Exception {
		IJavaProject javaProject = newEmptyProject();
		IPackageFragmentRoot sourceFolder = javaProject.getPackageFragmentRoot(javaProject.getProject().getFolder("src"));
		IPackageFragment pack1 = sourceFolder.createPackageFragment("test1", false, null);

		StringBuilder buf = new StringBuilder();
		String BEGIN_PART = "package test1;\n";
		String TO_BE_CHANGED_PART = "public class E123 {\n";
		String END_PART = "}\n";
		buf.append(BEGIN_PART);
		buf.append(TO_BE_CHANGED_PART);
		buf.append(END_PART);
		ICompilationUnit cu1 = pack1.createCompilationUnit("E123.java", buf.toString(), false, null);

		assertEquals(false, cu1.isWorkingCopy());
		assertEquals(false, cu1.hasUnsavedChanges());
		assertNewProblemReported();
		assertEquals(0, getCacheSize());
		assertNewASTsCreated(0);

		openDocument(cu1, cu1.getSource(), 1);

		assertEquals(true, cu1.isWorkingCopy());
		assertEquals(false, cu1.hasUnsavedChanges());
		assertNewProblemReported(new ExpectedProblemReport(cu1, 0));
		assertEquals(1, getCacheSize());
		assertNewASTsCreated(1);

		buf = new StringBuilder();
		buf.append(TO_BE_CHANGED_PART);
		buf.append("  X x;\n");

		changeDocumentIncrementally(cu1, buf.toString(), 2, BEGIN_PART.length(), TO_BE_CHANGED_PART.length());

		assertEquals(true, cu1.isWorkingCopy());
		assertEquals(true, cu1.hasUnsavedChanges());
		assertNewProblemReported(new ExpectedProblemReport(cu1, 1));
		assertEquals(1, getCacheSize());
		assertNewASTsCreated(1);

		saveDocument(cu1);

		assertEquals(true, cu1.isWorkingCopy());
		assertEquals(false, cu1.hasUnsavedChanges());
		assertNewProblemReported();
		assertEquals(1, getCacheSize());
		assertNewASTsCreated(0);

		closeDocument(cu1);

		assertEquals(false, cu1.isWorkingCopy());
		assertEquals(false, cu1.hasUnsavedChanges());
		assertNewProblemReported();
		assertEquals(0, getCacheSize());
		assertNewASTsCreated(0);
	}

	@Test
	public void testFixInDependencyScenario() throws Exception {
		IJavaProject javaProject = newEmptyProject();
		IPackageFragmentRoot sourceFolder = javaProject.getPackageFragmentRoot(javaProject.getProject().getFolder("src"));
		IPackageFragment pack1 = sourceFolder.createPackageFragment("test1", false, null);

		StringBuilder buf = new StringBuilder();
		buf.append("package test1;\n");
		buf.append("public class F123 {\n");
		buf.append("}\n");
		ICompilationUnit cu1 = pack1.createCompilationUnit("F123.java", buf.toString(), false, null);

		buf = new StringBuilder();
		buf.append("package test1;\n");
		buf.append("public class F456 {\n");
		buf.append("  { F123.foo(); }\n");
		buf.append("}\n");
		ICompilationUnit cu2 = pack1.createCompilationUnit("F456.java", buf.toString(), false, null);

		assertEquals(false, cu1.isWorkingCopy());
		assertEquals(false, cu1.hasUnsavedChanges());
		assertEquals(false, cu2.isWorkingCopy());
		assertEquals(false, cu2.hasUnsavedChanges());
		assertNewProblemReported();
		assertEquals(0, getCacheSize());
		assertNewASTsCreated(0);

		openDocument(cu2, cu2.getSource(), 1);

		assertEquals(false, cu1.isWorkingCopy());
		assertEquals(false, cu1.hasUnsavedChanges());
		assertEquals(true, cu2.isWorkingCopy());
		assertEquals(false, cu2.hasUnsavedChanges());
		assertNewProblemReported(new ExpectedProblemReport(cu2, 1));
		assertEquals(1, getCacheSize());
		assertNewASTsCreated(1);

		openDocument(cu1, cu1.getSource(), 1);

		assertEquals(true, cu1.isWorkingCopy());
		assertEquals(false, cu1.hasUnsavedChanges());
		assertEquals(true, cu2.isWorkingCopy());
		assertEquals(false, cu2.hasUnsavedChanges());
		assertNewProblemReported(new ExpectedProblemReport(cu2, 1), new ExpectedProblemReport(cu1, 0));
		assertEquals(1, getCacheSize());
		assertNewASTsCreated(2);

		buf = new StringBuilder();
		buf.append("package test1;\n");
		buf.append("public class F123 {\n");
		buf.append("  public static void foo() {}\n");
		buf.append("}\n");

		changeDocumentFull(cu1, buf.toString(), 2);

		assertEquals(true, cu1.isWorkingCopy());
		assertEquals(true, cu1.hasUnsavedChanges());
		assertEquals(true, cu2.isWorkingCopy());
		assertEquals(false, cu2.hasUnsavedChanges());
		assertNewProblemReported(new ExpectedProblemReport(cu2, 0), new ExpectedProblemReport(cu1, 0));
		assertEquals(1, getCacheSize());
		assertNewASTsCreated(2);

		saveDocument(cu1);

		assertEquals(true, cu1.isWorkingCopy());
		assertEquals(false, cu1.hasUnsavedChanges());
		assertEquals(true, cu2.isWorkingCopy());
		assertEquals(false, cu2.hasUnsavedChanges());
		assertNewProblemReported();
		assertEquals(1, getCacheSize());
		assertNewASTsCreated(0);

		closeDocument(cu1);

		assertEquals(false, cu1.isWorkingCopy());
		assertEquals(false, cu1.hasUnsavedChanges());
		assertEquals(true, cu2.isWorkingCopy());
		assertEquals(false, cu2.hasUnsavedChanges());
		assertNewProblemReported();
		assertEquals(0, getCacheSize());
		assertNewASTsCreated(0);

		closeDocument(cu2);

		assertEquals(false, cu1.isWorkingCopy());
		assertEquals(false, cu1.hasUnsavedChanges());
		assertEquals(false, cu2.isWorkingCopy());
		assertEquals(false, cu2.hasUnsavedChanges());
		assertNewProblemReported();
		assertEquals(0, getCacheSize());
		assertNewASTsCreated(0);
	}

	@Test
	public void testDidOpenStandaloneFile() throws Exception {
		IJavaProject javaProject = newDefaultProject();
		IPackageFragmentRoot sourceFolder = javaProject.getPackageFragmentRoot(javaProject.getProject().getFolder("src"));
		IPackageFragment pack1 = sourceFolder.createPackageFragment("java", false, null);

		// @formatter:off
		String standaloneFileContent =
				"package java;\n"+
				"public class Foo extends UnknownType {"+
				"	public void method1(){\n"+
				"		super.whatever();"+
				"	}\n"+
				"}";
		// @formatter:on
		ICompilationUnit cu1 = pack1.createCompilationUnit("Foo.java", standaloneFileContent, false, null);

		openDocument(cu1, cu1.getSource(), 1);

		List<PublishDiagnosticsParams> diagnosticReports = getClientRequests("publishDiagnostics");
		assertEquals(1, diagnosticReports.size());
		PublishDiagnosticsParams diagParam = diagnosticReports.get(0);
		assertEquals(1, diagParam.getDiagnostics().size());
		Diagnostic d = diagParam.getDiagnostics().get(0);
		assertEquals("Foo.java is a non-project file, only syntax errors are reported", d.getMessage());
	}

	@Test
	public void testDidOpenNotOnClasspath() throws Exception {
		importProjects("eclipse/hello");
		IProject project = WorkspaceHelper.getProject("hello");
		URI uri = project.getFile("nopackage/Test2.java").getRawLocationURI();
		ICompilationUnit cu = JDTUtils.resolveCompilationUnit(uri);
		String source = FileUtils.readFileToString(FileUtils.toFile(uri.toURL()));
		openDocument(cu, source, 1);
		Job.getJobManager().join(DocumentLifeCycleHandler.DOCUMENT_LIFE_CYCLE_JOBS, monitor);
		assertEquals(project, cu.getJavaProject().getProject());
		assertEquals(source, cu.getSource());
		List<PublishDiagnosticsParams> diagnosticReports = getClientRequests("publishDiagnostics");
		assertEquals(1, diagnosticReports.size());
		PublishDiagnosticsParams diagParam = diagnosticReports.get(0);
		assertEquals(2, diagParam.getDiagnostics().size());
		closeDocument(cu);
		Job.getJobManager().join(DocumentLifeCycleHandler.DOCUMENT_LIFE_CYCLE_JOBS, monitor);
		diagnosticReports = getClientRequests("publishDiagnostics");
		assertEquals(2, diagnosticReports.size());
		diagParam = diagnosticReports.get(1);
		assertEquals(0, diagParam.getDiagnostics().size());
	}

	@Test
	public void testDidOpenStandaloneFileWithSyntaxError() throws Exception {
		IJavaProject javaProject = newDefaultProject();
		IPackageFragmentRoot sourceFolder = javaProject.getPackageFragmentRoot(javaProject.getProject().getFolder("src"));
		IPackageFragment pack1 = sourceFolder.createPackageFragment("java", false, null);

		// @formatter:off
		String standaloneFileContent =
				"package java;\n"+
				"public class Foo extends UnknownType {\n"+
				"	public void method1(){\n"+
				"		super.whatever()\n"+
				"	}\n"+
				"}";
		// @formatter:on

		ICompilationUnit cu1 = pack1.createCompilationUnit("Foo.java", standaloneFileContent, false, null);

		openDocument(cu1, cu1.getSource(), 1);

		List<PublishDiagnosticsParams> diagnosticReports = getClientRequests("publishDiagnostics");
		assertEquals(1, diagnosticReports.size());
		PublishDiagnosticsParams diagParam = diagnosticReports.get(0);
		assertEquals("Unexpected number of errors " + diagParam.getDiagnostics(), 2, diagParam.getDiagnostics().size());
		Diagnostic d = diagParam.getDiagnostics().get(0);
		assertEquals("Foo.java is a non-project file, only syntax errors are reported", d.getMessage());
		assertRange(0, 0, 1, d.getRange());
		d = diagParam.getDiagnostics().get(1);
		assertEquals("Syntax error, insert \";\" to complete BlockStatements", d.getMessage());
		assertRange(3, 17, 18, d.getRange());
	}

	@Test
	public void testDidOpenStandaloneFileWithNonSyntaxErrors() throws Exception {
		IJavaProject javaProject = newDefaultProject();
		IPackageFragmentRoot sourceFolder = javaProject.getPackageFragmentRoot(javaProject.getProject().getFolder("src"));
		IPackageFragment pack1 = sourceFolder.createPackageFragment("java", false, null);

		// @formatter:off
		String standaloneFileContent =
				"package java;\n"+
				"public class Foo {\n"+
				"	public static void notThis(){\n"+
				"		System.out.println(this);\n"+
				"	}\n"+
				"	public void method1(){\n"+
				"	}\n"+
				"	public void method1(){\n"+
				"	}\n"+
				"}";
		// @formatter:on

		ICompilationUnit cu1 = pack1.createCompilationUnit("Foo.java", standaloneFileContent, false, null);

		openDocument(cu1, cu1.getSource(), 1);

		List<PublishDiagnosticsParams> diagnosticReports = getClientRequests("publishDiagnostics");
		assertEquals(1, diagnosticReports.size());
		PublishDiagnosticsParams diagParam = diagnosticReports.get(0);

		assertEquals("Unexpected number of errors " + diagParam.getDiagnostics(), 4, diagParam.getDiagnostics().size());
		Diagnostic d = diagParam.getDiagnostics().get(0);
		assertEquals("Foo.java is a non-project file, only syntax errors are reported", d.getMessage());
		assertRange(0, 0, 1, d.getRange());
		
		d = diagParam.getDiagnostics().get(1);
		assertEquals("Cannot use this in a static context", d.getMessage());
		assertRange(3, 21, 25, d.getRange());

		d = diagParam.getDiagnostics().get(2);
		assertEquals("Duplicate method method1() in type Foo", d.getMessage());
		assertRange(5, 13, 22, d.getRange());

		d = diagParam.getDiagnostics().get(3);
		assertEquals("Duplicate method method1() in type Foo", d.getMessage());
		assertRange(7, 13, 22, d.getRange());
	}

	@Test
	public void testDidOpenLazyLoadingInvisibleProject() throws Exception {
		File standaloneFolder = copyFiles("singlefile/lesson1", true);
		IPath rootPath = org.eclipse.core.runtime.Path.fromOSString(standaloneFolder.getAbsolutePath());
		preferences.setRootPaths(Collections.singletonList(rootPath));
		when(preferenceManager.getPreferences()).thenReturn(preferences);
		IPath triggerFile = rootPath.append("src/org/samples/HelloWorld.java");
		URI fileURI = triggerFile.toFile().toURI();
		String projectName = ProjectUtils.getWorkspaceInvisibleProjectName(rootPath);

		assertFalse(ProjectUtils.getProject(projectName).exists());

		openDocument(fileURI.toString(), ResourceUtils.getContent(fileURI), 1);
		Job.getJobManager().join(DocumentLifeCycleHandler.DOCUMENT_LIFE_CYCLE_JOBS, monitor);

		assertTrue(ProjectUtils.getProject(projectName).exists());
		ICompilationUnit cu = JDTUtils.resolveCompilationUnit(fileURI);
		assertNotNull(cu);
		assertEquals(projectName, cu.getJavaProject().getProject().getName());
	}

	@Test
	public void testNotExpectedPackage() throws Exception {
		newDefaultProject();
		// @formatter:off
		String content =
				"package org;\n"+
				"public class Foo {"+
				"}";
		// @formatter:on
		temp = createTempFolder();
		File file = createTempFile(temp, "Foo.java", content);
		URI uri = file.toURI();
		ICompilationUnit cu = JDTUtils.resolveCompilationUnit(uri);
		openDocument(cu, cu.getSource(), 1);
		CompilationUnit astRoot = CoreASTProvider.getInstance().getAST(cu, CoreASTProvider.WAIT_YES, new NullProgressMonitor());
		IProblem[] problems = astRoot.getProblems();
		assertEquals("Unexpected number of errors", 0, problems.length);
		String source = cu.getSource();
		int length = source.length();
		source = source.replace("org", "org.eclipse");
		changeDocument(cu, source, 2, JDTUtils.toRange(cu, 0, source.length()), length);
		FileUtils.writeStringToFile(file, source);
		saveDocument(cu);
		cu = JDTUtils.resolveCompilationUnit(uri);
		astRoot = CoreASTProvider.getInstance().getAST(cu, CoreASTProvider.WAIT_YES, new NullProgressMonitor());
		problems = astRoot.getProblems();
		assertEquals("Unexpected number of errors", 0, problems.length);
	}

	@Test
	public void testCreateCompilationUnit() throws Exception {
		IJavaProject javaProject = newEmptyProject();
		// @formatter:off
		String fooContent =
				"package org;\n"+
				"public class Foo {"+
				"}\n";
		String barContent =
				"package org;\n"+
				"public class Bar {\n"+
				"  Foo test() { return null; }\n" +
				"}\n";
		// @formatter:on
		IFolder src = javaProject.getProject().getFolder("src");
		javaProject.getPackageFragmentRoot(src);
		File sourceDirectory = src.getRawLocation().makeAbsolute().toFile();
		File org = new File(sourceDirectory, "org");
		org.mkdir();
		File file = new File(org, "Bar.java");
		file.createNewFile();
		FileUtils.writeStringToFile(file, barContent);
		ICompilationUnit bar = JDTUtils.resolveCompilationUnit(file.toURI());
		bar.getResource().refreshLocal(IResource.DEPTH_ONE, null);
		assertNotNull("Bar doesn't exist", javaProject.findType("org.Bar"));
		file = new File(org, "Foo.java");
		file.createNewFile();
		URI uri = file.toURI();
		ICompilationUnit unit = JDTUtils.resolveCompilationUnit(uri);
		openDocument(unit, "", 1);
		FileUtils.writeStringToFile(file, fooContent);
		changeDocumentFull(unit, fooContent, 1);
		saveDocument(unit);
		closeDocument(unit);
		CompilationUnit astRoot = sharedASTProvider.getAST(bar, CoreASTProvider.WAIT_YES, null);
		IProblem[] problems = astRoot.getProblems();
		assertEquals("Unexpected number of errors", 0, problems.length);
	}

	@Test
	public void testNotExpectedPackage2() throws Exception {
		newDefaultProject();
		// @formatter:off
		String content =
				"package org;\n"+
				"public class Foo {"+
				"}";
		// @formatter:on
		temp = createTempFolder();
		Path path = Paths.get(temp.getAbsolutePath(), "org", "eclipse");
		File file = createTempFile(path.toFile(), "Foo.java", content);
		URI uri = file.toURI();
		ICompilationUnit cu = JDTUtils.resolveCompilationUnit(uri);
		openDocument(cu, cu.getSource(), 1);
		CompilationUnit astRoot = CoreASTProvider.getInstance().getAST(cu, CoreASTProvider.WAIT_YES, new NullProgressMonitor());
		IProblem[] problems = astRoot.getProblems();
		assertEquals("Unexpected number of errors", 0, problems.length);

		String source = cu.getSource();
		int length = source.length();
		source = source.replace("org", "org.eclipse");
		changeDocument(cu, source, 2, JDTUtils.toRange(cu, 0, source.length()), length);
		FileUtils.writeStringToFile(file, source);
		saveDocument(cu);
		cu = JDTUtils.resolveCompilationUnit(uri);
		astRoot = CoreASTProvider.getInstance().getAST(cu, CoreASTProvider.WAIT_YES, new NullProgressMonitor());
		problems = astRoot.getProblems();
		assertEquals("Unexpected number of errors", 0, problems.length);

		source = cu.getSource();
		length = source.length();
		source = source.replace("org.eclipse", "org.eclipse.toto");
		changeDocument(cu, source, 3, JDTUtils.toRange(cu, 0, source.length()), length);
		FileUtils.writeStringToFile(file, source);
		saveDocument(cu);
		cu = JDTUtils.resolveCompilationUnit(uri);
		astRoot = CoreASTProvider.getInstance().getAST(cu, CoreASTProvider.WAIT_YES, new NullProgressMonitor());
		problems = astRoot.getProblems();
		assertEquals("Unexpected number of errors", 1, problems.length);
	}

	@Test
	public void testCloseMissingResource() throws Exception {
		IJavaProject javaProject = newEmptyProject();
		IPackageFragmentRoot sourceFolder = javaProject.getPackageFragmentRoot(javaProject.getProject().getFolder("src"));
		IPackageFragment pack1 = sourceFolder.createPackageFragment("test1", false, null);

		StringBuilder buf = new StringBuilder();
		buf.append("package test1;\n");
		buf.append("public class E123 {\n");
		buf.append("    public boolean foo() {\n");
		buf.append("        return x;\n");
		buf.append("    }\n");
		buf.append("}\n");
		ICompilationUnit cu1 = pack1.createCompilationUnit("E123.java", buf.toString(), false, null);

		openDocument(cu1, cu1.getSource(), 1);

		assertEquals(true, cu1.isWorkingCopy());
		assertEquals(false, cu1.hasUnsavedChanges());
		assertNewProblemReported(new ExpectedProblemReport(cu1, 1));
		assertEquals(1, getCacheSize());
		assertNewASTsCreated(1);

		StringBuilder buf2 = new StringBuilder();
		buf2.append("package test1;\n");
		buf2.append("public class E123 {\n");
		buf2.append("    public boolean foo() {\n");
		buf2.append("        return true;\n");
		buf2.append("    }\n");
		buf2.append("}\n");
		changeDocumentFull(cu1, buf2.toString(), 2);
		File file = cu1.getResource().getRawLocation().toFile();
		boolean deleted = file.delete();
		assertTrue(file.getAbsolutePath() + " hasn't been deleted", deleted);
		closeDocument(cu1);

		assertEquals(false, cu1.isWorkingCopy());
		assertEquals(false, cu1.hasUnsavedChanges());
		assertNewProblemReported(new ExpectedProblemReport(cu1, 0));
		assertEquals(0, getCacheSize());
		assertNewASTsCreated(0);
	}

	private File createTempFile(File parent, String fileName, String content) throws IOException {
		parent.mkdirs();
		File file = new File(parent, fileName);
		file.deleteOnExit();
		file.createNewFile();
		FileUtils.writeStringToFile(file, content);
		return file;
	}

	private File createTempFolder() throws IOException {
		File temp;
		temp = File.createTempFile("temp", Long.toString(System.nanoTime()));
		temp.delete();
		temp.mkdirs();
		temp.deleteOnExit();
		return temp;
	}

	@SuppressWarnings("unchecked")
	private <T> List<T> getClientRequests(String name) {
		List<?> requests = clientRequests.get(name);
		return requests != null ? (List<T>) requests : Collections.emptyList();
	}

	private void openDocument(ICompilationUnit cu, String content, int version) {
		openDocument(JDTUtils.toURI(cu), content, version);
	}

	private void openDocument(String uri, String content, int version) {
		DidOpenTextDocumentParams openParms = new DidOpenTextDocumentParams();
		TextDocumentItem textDocument = new TextDocumentItem();
		textDocument.setLanguageId("java");
		textDocument.setText(content);
		textDocument.setUri(uri);
		textDocument.setVersion(version);
		openParms.setTextDocument(textDocument);
		lifeCycleHandler.didOpen(openParms);
	}

	private void changeDocumentIncrementally(ICompilationUnit cu, String content, int version, int offset, int length) throws JavaModelException {
		Range range = JDTUtils.toRange(cu, offset, length);
		changeDocument(cu, content, version, range, length);
	}

	private void changeDocumentFull(ICompilationUnit cu, String content, int version) throws JavaModelException {
		changeDocument(cu, content, version, null, 0);
	}

	private void changeDocument(ICompilationUnit cu, String content, int version, Range range, int length) throws JavaModelException {
		DidChangeTextDocumentParams changeParms = new DidChangeTextDocumentParams();
		VersionedTextDocumentIdentifier textDocument = new VersionedTextDocumentIdentifier();
		textDocument.setUri(JDTUtils.toURI(cu));
		textDocument.setVersion(version);
		changeParms.setTextDocument(textDocument);
		TextDocumentContentChangeEvent event = new TextDocumentContentChangeEvent();
		if (range != null) {
			event.setRange(range);
			event.setRangeLength(length);
		}
		event.setText(content);
		List<TextDocumentContentChangeEvent> contentChanges = new ArrayList<>();
		contentChanges.add(event);
		changeParms.setContentChanges(contentChanges);
		lifeCycleHandler.didChange(changeParms);
	}

	private void saveDocument(ICompilationUnit cu) throws Exception {
		DidSaveTextDocumentParams saveParms = new DidSaveTextDocumentParams();
		TextDocumentIdentifier textDocument = new TextDocumentIdentifier();
		textDocument.setUri(JDTUtils.toURI(cu));
		saveParms.setTextDocument(textDocument);
		saveParms.setText(cu.getSource());
		lifeCycleHandler.didSave(saveParms);
		waitForBackgroundJobs();
	}

	private void closeDocument(ICompilationUnit cu) {
		DidCloseTextDocumentParams closeParms = new DidCloseTextDocumentParams();
		TextDocumentIdentifier textDocument = new TextDocumentIdentifier();
		textDocument.setUri(JDTUtils.toURI(cu));
		closeParms.setTextDocument(textDocument);
		lifeCycleHandler.didClose(closeParms);
	}

	class ExpectedProblemReport {
		ICompilationUnit cu;
		int problemCount;

		ExpectedProblemReport(ICompilationUnit cu, int problemCount) {
			this.cu = cu;
			this.problemCount = problemCount;
		}

	}

	private void assertNewProblemReported(ExpectedProblemReport... expectedReports) {
		List<PublishDiagnosticsParams> diags = getClientRequests("publishDiagnostics");
		assertEquals(expectedReports.length, diags.size());

		for (int i = 0; i < expectedReports.length; i++) {
			PublishDiagnosticsParams diag = diags.get(i);
			ExpectedProblemReport expected = expectedReports[i];
			assertEquals(JDTUtils.toURI(expected.cu), diag.getUri());
			if (expected.problemCount != diag.getDiagnostics().size()) {
				String message = "";
				for (Diagnostic d : diag.getDiagnostics()) {
					message += d.getMessage() + ", ";
				}
				assertEquals(message, expected.problemCount, diag.getDiagnostics().size());
			}

		}
		diags.clear();
	}

	private void assertNewASTsCreated(int expected) {
		//		assertEquals(expected, sharedASTProvider.getASTCreationCount());
		//		sharedASTProvider.clearASTCreationCount();
	}

	private int getCacheSize() {
		return (sharedASTProvider.getCachedAST() != null) ? 1 : 0;
	}
}