/*******************************************************************************
 * Copyright (c) 2014, 2017 GK Software AG, and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Stephan Herrmann - initial API and implementation
 *******************************************************************************/
package org.eclipse.jdt.core.tests.model;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;

import junit.framework.Test;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.ILogListener;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jdt.core.IClasspathAttribute;
import org.eclipse.jdt.core.IClasspathContainer;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaModelMarker;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IType;
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.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.NodeFinder;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.tests.util.AbstractCompilerTest;
import org.eclipse.jdt.core.tests.util.Util;
import org.eclipse.jdt.core.util.ExternalAnnotationUtil;
import org.eclipse.jdt.core.util.ExternalAnnotationUtil.MergeStrategy;
import org.eclipse.jdt.internal.compiler.problem.ProblemSeverities;
import org.eclipse.jdt.internal.core.ClasspathAttribute;
import org.eclipse.jdt.internal.core.ClasspathEntry;
import org.osgi.framework.Bundle;

@SuppressWarnings({"rawtypes", "unchecked"})
public class ExternalAnnotations18Test extends ModifyingResourceTests {

	/** Bridge to hook the host JRE into the registered ContainerInitializer. */
	static class TestContainerInitializer implements ContainerInitializer.ITestInitializer {

		/** Use this container name in test projects. */
		private static final String TEST_CONTAINER_NAME = "org.eclipse.jdt.core.tests.model.TEST_CONTAINER";
		
		/** Simulate workspace-settings for rt.jar. */
		public static String RT_JAR_ANNOTATION_PATH = null;
		
		static class TestContainer implements IClasspathContainer {
			IPath path;
			IClasspathEntry[] entries;
			TestContainer(IPath path, IClasspathEntry[] entries){
				this.path = path;
				this.entries = entries;
			}
			public IPath getPath() { return this.path; }
			public IClasspathEntry[] getClasspathEntries() { return this.entries;	}
			public String getDescription() { return this.path.toString(); 	}
			public int getKind() { return 0; }
		}

		public void initialize(IPath containerPath, IJavaProject project) throws CoreException {
			String[] jars = Util.getJavaClassLibs();
			IClasspathEntry[] entries = new IClasspathEntry[jars.length];
			for (int i = 0; i < jars.length; i++) {
				IClasspathAttribute[] extraAttributes;
				if (RT_JAR_ANNOTATION_PATH != null && jars[i].endsWith("rt.jar"))
					extraAttributes = externalAnnotationExtraAttributes(RT_JAR_ANNOTATION_PATH);
				else
					extraAttributes = ClasspathEntry.NO_EXTRA_ATTRIBUTES;
				entries[i] = JavaCore.newLibraryEntry(new Path(jars[i]), null, null,
						ClasspathEntry.NO_ACCESS_RULES, extraAttributes, false/*not exported*/);
			}
			JavaCore.setClasspathContainer(
					new Path(TEST_CONTAINER_NAME),
					new IJavaProject[]{ project },
					new IClasspathContainer[] { new TestContainer(new Path(TEST_CONTAINER_NAME), entries) },
					null);
		}
		public boolean allowFailureContainer() {
			return false;
		}
	}

	static IClasspathAttribute[] externalAnnotationExtraAttributes(String path) {
		return new IClasspathAttribute[] {
				new ClasspathAttribute(IClasspathAttribute.EXTERNAL_ANNOTATION_PATH, path)	
		};
	}
	
	static class LogListener implements ILogListener {
    	List<IStatus> loggedStatus = new ArrayList<>();
        public void logging(IStatus status, String plugin) {
            this.loggedStatus.add(status);
        }
	}


	protected IJavaProject project;
	protected IPackageFragmentRoot root;
	protected String ANNOTATION_LIB;
	protected final String compliance;
	protected final String jclLib;

	protected static final String MY_MAP_CONTENT = 
			"package libs;\n" + 
			"\n" + 
			"public interface MyMap<K,V> {\n" + 
			"	V get(Object key);\n" + 
			"	V put(K key, V val);\n" + 
			"	V remove(Object key);\n" + 
			"}\n";

	public ExternalAnnotations18Test(String name) {
		this(name, "1.8", "JCL18_LIB");
	}
	
	protected ExternalAnnotations18Test(String name, String compliance, String jclLib) {
		super(name);
		this.compliance = compliance;
		this.jclLib = jclLib;
	}

	// Use this static initializer to specify subset for tests
// All specified tests which do not belong to the class are skipped...
	static {
		// Names of tests to run: can be "testBugXXXX" or "BugXXXX")
//		TESTS_PREFIX = "testLibsWithTypeParameters";
//		TESTS_NAMES = new String[] {"test3"};
//		TESTS_NUMBERS = new int[] { 23, 28, 38 };
//		TESTS_RANGE = new int[] { 21, 38 };
	}
	public static Test suite() {
		return buildModelTestSuite(ExternalAnnotations18Test.class, BYTECODE_DECLARATION_ORDER);
	}

	public void setUpSuite() throws Exception {
		super.setUpSuite();
		
		Bundle[] bundles = getAnnotationBundles();
		File bundleFile = FileLocator.getBundleFile(bundles[0]);
		this.ANNOTATION_LIB = bundleFile.isDirectory() ? bundleFile.getPath()+"/bin" : bundleFile.getPath();

		// set up class path container bridging to the host JRE:
		ContainerInitializer.setInitializer(new TestContainerInitializer());
	}

	/**
	 * @deprecated
	 */
	static int getJLS8() {
		return AST.JLS8;
	}
	@Deprecated
	static int getJSL9() {
		return AST.JLS9;
	}

	/**
	 * @deprecated indirectly uses deprecated class PackageAdmin
	 */
	protected Bundle[] getAnnotationBundles() {
		return org.eclipse.jdt.core.tests.Activator.getPackageAdmin().getBundles("org.eclipse.jdt.annotation", "[2.0.0,3.0.0)");
	}

	public void tearDownSuite() throws Exception {
		super.tearDownSuite();
		ContainerInitializer.setInitializer(null);
	}
	
	public String getSourceWorkspacePath() {
		// we read individual projects from within this folder:
		return super.getSourceWorkspacePath()+"/ExternalAnnotations18";
	}

	protected String getSourceWorkspacePathBase() {
		return super.getSourceWorkspacePath();
	}

	void setupJavaProject(String name) throws CoreException, IOException {
		setupJavaProject(name, false, true);
	}

	void setupJavaProject(String name, boolean useFullJCL, boolean addAnnotationLib) throws CoreException, IOException {
		this.project = setUpJavaProject(name, this.compliance, useFullJCL); //$NON-NLS-1$
		if(addAnnotationLib)
			addLibraryEntry(this.project, this.ANNOTATION_LIB, false);

		Map options = this.project.getOptions(true);
		options.put(JavaCore.COMPILER_ANNOTATION_NULL_ANALYSIS, JavaCore.ENABLED);
		this.project.setOptions(options);

		IPackageFragmentRoot[] roots = this.project.getAllPackageFragmentRoots();
		int count = 0;
		for (int i = 0, max = roots.length; i < max; i++) {
			final IPackageFragmentRoot packageFragmentRoot = roots[i];
			switch(packageFragmentRoot.getKind()) {
				case IPackageFragmentRoot.K_SOURCE :
					count++;
					if (this.root == null) {
						this.root = packageFragmentRoot;
					}
			}
		}
		assertEquals("Wrong value", 1, count); //$NON-NLS-1$
		assertNotNull("Should not be null", this.root); //$NON-NLS-1$
	}
	
	void myCreateJavaProject(String name) throws CoreException {
		this.project = createJavaProject(name, new String[]{"src"}, new String[]{this.jclLib}, null, null, "bin", null, null, null, this.compliance);
		addLibraryEntry(this.project, this.ANNOTATION_LIB, false);
		Map options = this.project.getOptions(true);
		options.put(JavaCore.COMPILER_ANNOTATION_NULL_ANALYSIS, JavaCore.ENABLED);
		this.project.setOptions(options);

		IPackageFragmentRoot[] roots = this.project.getAllPackageFragmentRoots();
		int count = 0;
		for (int i = 0, max = roots.length; i < max; i++) {
			final IPackageFragmentRoot packageFragmentRoot = roots[i];
			switch(packageFragmentRoot.getKind()) {
				case IPackageFragmentRoot.K_SOURCE :
					count++;
					if (this.root == null) {
						this.root = packageFragmentRoot;
					}
			}
		}
		assertEquals("Wrong value", 1, count); //$NON-NLS-1$
		assertNotNull("Should not be null", this.root); //$NON-NLS-1$
	}
	
	protected void tearDown() throws Exception {
		if (this.project != null)
			this.project.getProject().delete(true, true, null);
		this.project = null;
		this.root = null;
		super.tearDown();
	}

	protected void addLibraryWithExternalAnnotations(
			IJavaProject javaProject,
			String jarName,
			String externalAnnotationPath,
			String[] pathAndContents,
			Map options) throws CoreException, IOException
	{
		createLibrary(javaProject, jarName, "src.zip", pathAndContents, null, this.compliance, options);
		String jarPath = '/' + javaProject.getProject().getName() + '/' + jarName;
		IClasspathEntry entry = JavaCore.newLibraryEntry(
				new Path(jarPath),
				new Path('/'+javaProject.getProject().getName()+"/src.zip"),
				null/*src attach root*/,
				null/*access rules*/,
				externalAnnotationExtraAttributes(externalAnnotationPath),
				false/*exported*/);
		addClasspathEntry(this.project, entry);
	}

	protected void addProjectDependencyWithExternalAnnotations(
			IJavaProject javaProject,
			String referencedProjectName,
			String externalAnnotationPath,
			Map options) throws CoreException, IOException
	{
		IClasspathEntry entry = JavaCore.newProjectEntry(
				new Path(referencedProjectName),
				null/*access rules*/,
				false/*combine access rules*/,
				externalAnnotationExtraAttributes(externalAnnotationPath),
				false/*exported*/);
		addClasspathEntry(this.project, entry);
	}

	protected void addEeaToVariableEntry(String variableName, String annotationPath) throws JavaModelException {
		IClasspathEntry[] rawClasspath = this.project.getRawClasspath();
		boolean found = false;
		for (int i = 0; i < rawClasspath.length; i++) {
			IClasspathEntry entry = rawClasspath[i];
			if (entry.getPath().toString().equals(variableName)) {
				rawClasspath[i] = JavaCore.newVariableEntry(
						entry.getPath(),
						entry.getSourceAttachmentPath(),
						entry.getSourceAttachmentRootPath(),
						entry.getAccessRules(),
						new IClasspathAttribute[] {
							new ClasspathAttribute(IClasspathAttribute.EXTERNAL_ANNOTATION_PATH, annotationPath)	
						},
						entry.isExported());
				found = true;
				break;
			}
		}
		assertTrue("Should find classpath entry "+variableName, found);
		this.project.setRawClasspath(rawClasspath, new NullProgressMonitor());
	}

	protected void createFileInProject(String projectRelativeFolder, String fileName, String content) throws CoreException {
		String folderPath = this.project.getProject().getName()+'/'+projectRelativeFolder;
		createFolder(folderPath);
		createFile(folderPath+'/'+fileName, content);
	}

	protected void assertNoMarkers(IMarker[] markers) throws CoreException {
		for (int i = 0; i < markers.length; i++)
			System.err.println("Unexpected marker: "+markers[i].getAttributes().entrySet());
		assertEquals("Number of markers", 0, markers.length);
	}
	
	protected void assertNoProblems(IProblem[] problems) throws CoreException {
		for (int i = 0; i < problems.length; i++)
			System.err.println("Unexpected marker: "+problems[i]);
		assertEquals("Number of markers", 0, problems.length);
	}

	protected void assertProblems(IProblem[] problems, String[] messages, int[] lines) throws CoreException {
		int nMatch = 0;
		for (int i = 0; i < problems.length; i++) {
			for (int j = 0; j < messages.length; j++) {
				if (messages[j] == null) continue;
				if (problems[i].toString().equals(messages[j])
						&& problems[i].getSourceLineNumber() == lines[j]) {
					messages[j] = null;
					problems[i] = null;
					nMatch++;
					break;
				}
			}
		}
		for (int i = 0; i < problems.length; i++) {
			if (problems[i] != null)
				fail("Unexpected problem "+problems[i]+" at "+problems[i].getSourceLineNumber());
		}
		for (int i = 0; i < messages.length; i++) {
			if (messages[i] != null)
				System.err.println("Unmatched problem "+messages[i]);
		}
		assertEquals("Number of problems", messages.length, nMatch);
	}

	protected void assertProblems(IProblem[] problems, String[] messages, int[] lines, int[] severities) throws CoreException {
		int nMatch = 0;
		for (int i = 0; i < problems.length; i++) {
			for (int j = 0; j < messages.length; j++) {
				if (messages[j] == null) continue;
				if (problems[i].toString().equals(messages[j])) {
					if (problems[i].getSourceLineNumber() == lines[j]) {
						switch(severities[j] & ProblemSeverities.CoreSeverityMASK ) {
						case ProblemSeverities.Error:
							if (!problems[i].isError()) {
								System.err.println("Not an error as expected: "+messages[j]);					
								continue;
							}
							break;
						case ProblemSeverities.Warning:
							if (!problems[i].isWarning()) {
								System.err.println("Not a warning as expected: "+messages[j]);					
								continue;
							}
							break;
						case ProblemSeverities.Info:
							if (!problems[i].isInfo()) {
								System.err.println("Not an info as expected: "+messages[j]);					
								continue;
							}
							break;
						default:
							throw new IllegalArgumentException("Bad severity expected: "+severities[j]);
						}
						messages[j] = null;
						problems[i] = null;
						nMatch++;
						break;
					} else {
						System.err.println("Match at wrong line: "+problems[i].getSourceLineNumber()+" vs. "+lines[j]+": "+messages[j]);
					}
				}
			}
		}
		for (int i = 0; i < problems.length; i++) {
			if (problems[i] != null)
				fail("Unexpected problem "+problems[i]+" at "+problems[i].getSourceLineNumber());
		}
		for (int i = 0; i < messages.length; i++) {
			if (messages[i] != null)
				System.err.println("Unmatched problem "+messages[i]);
		}
		assertEquals("Number of problems", messages.length, nMatch);
	}
	protected boolean hasJRE18() {
		return ((AbstractCompilerTest.getPossibleComplianceLevels() & AbstractCompilerTest.F_1_8) != 0);
	}

	String readFully(IFile file) throws IOException, CoreException {
		try (BufferedInputStream bs = new BufferedInputStream(file.getContents())) {
			int available = 0;
			StringBuilder buf = new StringBuilder();
			while ((available = bs.available()) > 0) {
				byte[] contents = new byte[available];
				bs.read(contents);
				buf.append(new String(contents));
			}
			return buf.toString();
		}
	}

	/** Perform full build. */
	public void test1FullBuild() throws Exception {
		setupJavaProject("Test1");
		addLibraryWithExternalAnnotations(this.project, "lib1.jar", "annots", new String[] {
				"/UnannotatedLib/libs/MyMap.java",
				MY_MAP_CONTENT
			}, null);
		this.project.getProject().build(IncrementalProjectBuilder.FULL_BUILD, null);
		IMarker[] markers = this.project.getProject().findMarkers(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE);
		assertNoMarkers(markers);
	}

	/** Perform full build, annotations are found relative to a variable. */
	public void test1FullBuildWithVariable() throws Exception {
		setupJavaProject("Test1");
		JavaCore.setClasspathVariable("MY_PRJ_ROOT", this.project.getProject().getLocation(), null);
		try {
			addLibraryWithExternalAnnotations(this.project, "lib1.jar", "MY_PRJ_ROOT/annots", new String[] {
					"/UnannotatedLib/libs/MyMap.java",
					MY_MAP_CONTENT
				}, null);
			this.project.getProject().build(IncrementalProjectBuilder.FULL_BUILD, null);
			IMarker[] markers = this.project.getProject().findMarkers(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE);
			assertNoMarkers(markers);
		} finally {
			JavaCore.removeClasspathVariable("MY_PRJ_ROOT", null);
		}
	}

	/** Reconcile an individual CU. */
	public void test1Reconcile() throws Exception {
		setupJavaProject("Test1");
		addLibraryWithExternalAnnotations(this.project, "lib1.jar", "annots", new String[] {
				"/UnannotatedLib/libs/MyMap.java",
				MY_MAP_CONTENT
			}, null);
		IPackageFragment fragment = this.root.getPackageFragment("test1");
		ICompilationUnit unit = fragment.getCompilationUnit("Test1.java").getWorkingCopy(new NullProgressMonitor());
		CompilationUnit reconciled = unit.reconcile(getJLS8(), true, null, new NullProgressMonitor());
		IProblem[] problems = reconciled.getProblems();
		assertNoProblems(problems);
	}
	
	public void testLibs1() throws Exception {
		myCreateJavaProject("TestLibs");
		addLibraryWithExternalAnnotations(this.project, "lib1.jar", "annots", new String[] {
				"/UnannotatedLib/libs/Lib1.java",
				"package libs;\n" + 
				"\n" + 
				"import java.util.Collection;\n" + 
				"import java.util.Iterator;\n" + 
				"\n" + 
				"public interface Lib1 {\n" + 
				"	<T> Iterator<T> unconstrainedTypeArguments1(Collection<T> in);\n" + 
				"	Iterator<String> unconstrainedTypeArguments2(Collection<String> in);\n" + 
				"	<T> Iterator<? extends T> constrainedWildcards(Collection<? extends T> in);\n" + 
				"	<T extends Collection<?>> T constrainedTypeParameter(T in);\n" + 
				"}\n"
			}, null);
		// annotations on type variables & class type in various positions:
		createFileInProject("annots/libs", "Lib1.eea",
				"class libs/Lib1\n" + 
				"\n" + 
				"unconstrainedTypeArguments1\n" + 
				" <T:Ljava/lang/Object;>(Ljava/util/Collection<TT;>;)Ljava/util/Iterator<TT;>;\n" + 
				" <T:Ljava/lang/Object;>(Ljava/util/Collection<T0T;>;)Ljava/util/Iterator<TT;>;\n" +  // position: type argument
				"\n" + 
				"unconstrainedTypeArguments2\n" + 
				" (Ljava/util/Collection<Ljava/lang/String;>;)Ljava/util/Iterator<Ljava/lang/String;>;\n" + 
				" (Ljava/util/Collection<Ljava/lang/String;>;)Ljava/util/Iterator<L1java/lang/String;>;\n" + // position: type argument bound (class type)
				"constrainedWildcards\n" +
				" <T:Ljava/lang/Object;>(Ljava/util/Collection<+TT;>;)Ljava/util/Iterator<+TT;>;\n" +
				" <T:Ljava/lang/Object;>(Ljava/util/Collection<+T0T;>;)Ljava/util/Iterator<+T1T;>;\n" + // positions: wildcard bound
				"constrainedTypeParameter\n" +
				" <T::Ljava/util/Collection<*>;>(TT;)TT;\n" +
				" <T::Ljava/util/Collection<*>;>(T0T;)T1T;\n"); // position: top-level type
		IPackageFragment fragment = this.project.getPackageFragmentRoots()[0].createPackageFragment("tests", true, null);
		ICompilationUnit unit = fragment.createCompilationUnit("Test1.java", 
				"package tests;\n" + 
				"import org.eclipse.jdt.annotation.*;\n" + 
				"\n" + 
				"import java.util.Collection;\n" + 
				"import java.util.Iterator;\n" + 
				"\n" + 
				"import libs.Lib1;\n" + 
				"\n" + 
				"public class Test1 {\n" + 
				"	Iterator<@NonNull String> test1(Lib1 lib, Collection<@Nullable String> coll) {\n" + 
				"		return lib.unconstrainedTypeArguments1(coll);\n" + 
				"	}\n" + 
				"	Iterator<@NonNull String> test2(Lib1 lib, Collection<@Nullable String> coll) {\n" + 
				"		return lib.unconstrainedTypeArguments2(coll);\n" + 
				"	}\n" +
				"	Iterator<? extends @NonNull String> test3(Lib1 lib, Collection<String> coll) {\n" +
				"		return lib.constrainedWildcards(coll);\n" +
				"	}\n" +
				"	@NonNull Collection<String> test4(Lib1 lib, @Nullable Collection<String> in) {\n" +
				"		return lib.constrainedTypeParameter(in);\n" +
				"	}\n" + 
				"}\n",
				true, new NullProgressMonitor()).getWorkingCopy(new NullProgressMonitor());
		CompilationUnit reconciled = unit.reconcile(getJLS8(), true, null, new NullProgressMonitor());
		IProblem[] problems = reconciled.getProblems();
		assertNoProblems(problems);
	}

	public void testLibsWithWildcards() throws Exception {
		myCreateJavaProject("TestLibs");
		addLibraryWithExternalAnnotations(this.project, "lib1.jar", "annots", new String[] {
				"/UnannotatedLib/libs/Lib1.java",
				"package libs;\n" + 
				"\n" + 
				"import java.util.Collection;\n" + 
				"import java.util.Iterator;\n" + 
				"\n" + 
				"public interface Lib1 {\n" + 
				"	Iterator<?> unconstrainedWildcard1(Collection<?> in);\n" + 
				"	Iterator<?> unconstrainedWildcard2(Collection<?> in);\n" + 
				"	Iterator<? extends CharSequence> constrainedWildcard1(Collection<? extends CharSequence> in);\n" + 
				"	Iterator<? super CharSequence> constrainedWildcard2(Collection<? super CharSequence> in);\n" + 
				"}\n"
			}, null);
		// annotations directly on a wildcard (*, +, -)
		createFileInProject("annots/libs", "Lib1.eea",
				"class libs/Lib1\n" + 
				"\n" + 
				"unconstrainedWildcard1\n" + 
				" (Ljava/util/Collection<*>;)Ljava/util/Iterator<*>;\n" + 
				" (Ljava/util/Collection<*>;)Ljava/util/Iterator<*1>;\n" +
				"\n" + 
				"unconstrainedWildcard2\n" + 
				" (Ljava/util/Collection<*>;)Ljava/util/Iterator<*>;\n" + 
				" (Ljava/util/Collection<*>;)Ljava/util/Iterator<*0>;\n" + 
				"\n" + 
				"constrainedWildcard1\n" + 
				" (Ljava/util/Collection<+Ljava/lang/CharSequence;>;)Ljava/util/Iterator<+Ljava/lang/CharSequence;>;\n" + 
				" (Ljava/util/Collection<+Ljava/lang/CharSequence;>;)Ljava/util/Iterator<+0Ljava/lang/CharSequence;>;\n" + 
				"\n" + 
				"constrainedWildcard2\n" + 
				" (Ljava/util/Collection<-Ljava/lang/CharSequence;>;)Ljava/util/Iterator<-Ljava/lang/CharSequence;>;\n" + 
				" (Ljava/util/Collection<-Ljava/lang/CharSequence;>;)Ljava/util/Iterator<-0Ljava/lang/CharSequence;>;\n" + 
				"\n" + 
				"\n");
		IPackageFragment fragment = this.project.getPackageFragmentRoots()[0].createPackageFragment("tests", true, null);
		ICompilationUnit unit = fragment.createCompilationUnit("Test1.java", 
				"package tests;\n" + 
				"import org.eclipse.jdt.annotation.*;\n" + 
				"\n" + 
				"import java.util.Collection;\n" + 
				"\n" + 
				"import libs.Lib1;\n" + 
				"\n" + 
				"public class Test1 {\n" + 
				"	@NonNull Object test1(Lib1 lib, Collection<@Nullable String> coll) {\n" + 
				"		 return lib.unconstrainedWildcard1(coll).next();\n" + // OK
				"	}\n" + 
				"	@NonNull Object test2(Lib1 lib, Collection<@Nullable String> coll) {\n" + 
				"		 return lib.unconstrainedWildcard2(coll).next();\n" + // return is nullable -> error
				"	}\n" + 
				"	@NonNull CharSequence test3(Lib1 lib, Collection<@Nullable String> coll) {\n" + 
				"		 return lib.constrainedWildcard1(coll).next();\n" + // '@Nullable ? extends CharSequence' -> error
				"	}\n" + 
				"	@NonNull Object test4(Lib1 lib, Collection<@Nullable CharSequence> coll) {\n" + 
				"		 return lib.constrainedWildcard2(coll).next();\n" + // return is '@Nullable ? super CharSequence' -> error
				"	}\n" + 
				"}\n",
				true, new NullProgressMonitor()).getWorkingCopy(new NullProgressMonitor());
		CompilationUnit reconciled = unit.reconcile(getJLS8(), true, null, new NullProgressMonitor());
		IProblem[] problems = reconciled.getProblems();
		assertProblems(problems, new String[] {
				"Pb(980) Unsafe interpretation of method return type as '@NonNull' based on the receiver type 'Iterator<@NonNull capture#of ?>'. Type 'Iterator<E>' doesn't seem to be designed with null type annotations in mind",
				"Pb(953) Null type mismatch (type annotations): required '@NonNull Object' but this expression has type '@Nullable capture#of ?'",
				"Pb(953) Null type mismatch (type annotations): required '@NonNull CharSequence' but this expression has type '@Nullable capture#of ? extends CharSequence'",
				"Pb(953) Null type mismatch (type annotations): required '@NonNull Object' but this expression has type '@Nullable capture#of ? super CharSequence'"
			}, new int[] { 10, 13, 16, 19 });
	}
	
	public void testLibsWithArrays() throws Exception {
		myCreateJavaProject("TestLibs");
		addLibraryWithExternalAnnotations(this.project, "lib1.jar", "annots", new String[] {
				"/UnannotatedLib/libs/Lib1.java",
				"package libs;\n" + 
				"\n" + 
				"import java.util.Collection;\n" + 
				"import java.util.Iterator;\n" + 
				"\n" + 
				"public interface Lib1 {\n" + 
				"	String[] constraintArrayTop(String[] in);\n" + 
				"	String[] constraintArrayFull(String[] in);\n" + 
				"	String[][] constraintDeep(String[][] in);\n" + 
				"}\n"
			}, null);
		createFileInProject("annots/libs", "Lib1.eea", 
				"class libs/Lib1\n" + 
				"\n" + 
				"constraintArrayTop\n" + 
				" ([Ljava/lang/String;)[Ljava/lang/String;\n" + 
				" ([0Ljava/lang/String;)[1Ljava/lang/String;\n" + 
				"\n" + 
				"constraintArrayFull\n" + 
				" ([Ljava/lang/String;)[Ljava/lang/String;\n" +
				" ([0L0java/lang/String;)[1L1java/lang/String;\n" +
				"\n" +
				"constraintDeep\n" +
				" ([[Ljava/lang/String;)[[Ljava/lang/String;\n" +
				" ([0[1L0java/lang/String;)[1[0L1java/lang/String;\n");
		IPackageFragment fragment = this.project.getPackageFragmentRoots()[0].createPackageFragment("tests", true, null);
		ICompilationUnit unit = fragment.createCompilationUnit("Test1.java", 
				"package tests;\n" + 
				"import org.eclipse.jdt.annotation.*;\n" + 
				"\n" + 
				"import libs.Lib1;\n" + 
				"\n" + 
				"public class Test1 {\n" + 
				"	String @NonNull[] test1(Lib1 lib, String @Nullable[] ok, String[] nok) {\n" + 
				"		lib.constraintArrayTop(nok);\n" + 
				"		return lib.constraintArrayTop(ok);\n" + 
				"	}\n" +
				"	@NonNull String @NonNull[] test2(Lib1 lib, @Nullable String @Nullable[] ok, String[] nok) {\n" + 
				"		lib.constraintArrayFull(nok);\n" + 
				"		return lib.constraintArrayFull(ok);\n" + 
				"	}\n" + 
				"	@NonNull String @NonNull[] @Nullable[] test3(Lib1 lib, @Nullable String @Nullable[] @NonNull[] ok, String[][] nok) {\n" + 
				"		lib.constraintDeep(nok);\n" + 
				"		return lib.constraintDeep(ok);\n" + 
				"	}\n" + 
				"}\n",
				true, new NullProgressMonitor()).getWorkingCopy(new NullProgressMonitor());
		CompilationUnit reconciled = unit.reconcile(getJLS8(), true, null, new NullProgressMonitor());
		IProblem[] problems = reconciled.getProblems();
		assertProblems(problems, new String[] {
			"Pb(955) Null type safety (type annotations): The expression of type 'String[]' needs unchecked conversion to conform to '@Nullable String @Nullable[]'",
			"Pb(955) Null type safety (type annotations): The expression of type 'String[][]' needs unchecked conversion to conform to '@Nullable String @Nullable[] @NonNull[]'",
		}, new int[] { 12, 16 });
	}

	public void testLibsWithFields() throws Exception {
		myCreateJavaProject("TestLibs");
		addLibraryWithExternalAnnotations(this.project, "lib1.jar", "annots", new String[] {
				"/UnannotatedLib/libs/Lib1.java",
				"package libs;\n" + 
				"\n" +
				"public interface Lib1 {\n" + 
				"	String one = \"1\";\n" + 
				"	String none = null;\n" + 
				"}\n"
			}, null);
		createFileInProject("annots/libs", "Lib1.eea", 
				"class libs/Lib1\n" +
				"\n" + 
				"one\n" + 
				" Ljava/lang/String;\n" + 
				" L1java/lang/String;\n" + 
				"\n" + 
				"none\n" + 
				" Ljava/lang/String;\n" +
				" L0java/lang/String;\n" +
				"\n");
		IPackageFragment fragment = this.project.getPackageFragmentRoots()[0].createPackageFragment("tests", true, null);
		ICompilationUnit unit = fragment.createCompilationUnit("Test1.java", 
				"package tests;\n" + 
				"import org.eclipse.jdt.annotation.*;\n" + 
				"\n" + 
				"import libs.Lib1;\n" + 
				"\n" + 
				"public class Test1 {\n" + 
				"	@NonNull String test0() {\n" + 
				"		return Lib1.none;\n" + 
				"	}\n" +
				"	@NonNull String test1() {\n" + 
				"		return Lib1.one;\n" + 
				"	}\n" +
				"}\n",
				true, new NullProgressMonitor()).getWorkingCopy(new NullProgressMonitor());
		CompilationUnit reconciled = unit.reconcile(getJLS8(), true, null, new NullProgressMonitor());
		IProblem[] problems = reconciled.getProblems();
		assertProblems(problems, new String[] {
			"Pb(953) Null type mismatch (type annotations): required '@NonNull String' but this expression has type '@Nullable String'",
		}, new int[] { 8 });
	}

	public void testLibsWithFieldsZipped() throws Exception {
		myCreateJavaProject("TestLibs");
		addLibraryWithExternalAnnotations(this.project, "lib1.jar", "annots.zip", new String[] {
				"/UnannotatedLib/libs/Lib1.java",
				"package libs;\n" + 
				"\n" +
				"public interface Lib1 {\n" + 
				"	String one = \"1\";\n" + 
				"	String none = null;\n" + 
				"}\n"
			}, null);
		Util.createSourceZip(
			new String[] {
				"libs/Lib1.eea", 
				"class libs/Lib1\n" +
				"\n" + 
				"one\n" + 
				" Ljava/lang/String;\n" + 
				" L1java/lang/String;\n" + 
				"\n" + 
				"none\n" + 
				" Ljava/lang/String;\n" +
				" L0java/lang/String;\n" +
				"\n"
			},
			this.project.getProject().getLocation().toString()+"/annots.zip");
	    this.project.getProject().refreshLocal(IResource.DEPTH_INFINITE, null);

		IPackageFragment fragment = this.project.getPackageFragmentRoots()[0].createPackageFragment("tests", true, null);
		ICompilationUnit unit = fragment.createCompilationUnit("Test1.java", 
				"package tests;\n" + 
				"import org.eclipse.jdt.annotation.*;\n" + 
				"\n" + 
				"import libs.Lib1;\n" + 
				"\n" + 
				"public class Test1 {\n" + 
				"	@NonNull String test0() {\n" + 
				"		return Lib1.none;\n" + 
				"	}\n" +
				"	@NonNull String test1() {\n" + 
				"		return Lib1.one;\n" + 
				"	}\n" +
				"}\n",
				true, new NullProgressMonitor()).getWorkingCopy(new NullProgressMonitor());
		CompilationUnit reconciled = unit.reconcile(getJLS8(), true, null, new NullProgressMonitor());
		IProblem[] problems = reconciled.getProblems();
		assertProblems(problems, new String[] {
			"Pb(953) Null type mismatch (type annotations): required '@NonNull String' but this expression has type '@Nullable String'",
		}, new int[] { 8 });
	}

	public void testLibsWithFieldsExternalZipped() throws Exception {
		myCreateJavaProject("TestLibs");
		String zipPath = Util.getOutputDirectory() + '/' + "annots.zip";
		addLibraryWithExternalAnnotations(this.project, "lib1.jar", zipPath, new String[] {
				"/UnannotatedLib/libs/Lib1.java",
				"package libs;\n" + 
				"\n" +
				"public interface Lib1 {\n" + 
				"	String one = \"1\";\n" + 
				"	String none = null;\n" + 
				"}\n"
			}, null);
		Util.createSourceZip(
			new String[] {
				"libs/Lib1.eea", 
				"class libs/Lib1\n" +
				"\n" + 
				"one\n" + 
				" Ljava/lang/String;\n" + 
				" L1java/lang/String;\n" + 
				"\n" + 
				"none\n" + 
				" Ljava/lang/String;\n" +
				" L0java/lang/String;\n" +
				"\n"
			},
			zipPath);

		IPackageFragment fragment = this.project.getPackageFragmentRoots()[0].createPackageFragment("tests", true, null);
		ICompilationUnit unit = fragment.createCompilationUnit("Test1.java", 
				"package tests;\n" + 
				"import org.eclipse.jdt.annotation.*;\n" + 
				"\n" + 
				"import libs.Lib1;\n" + 
				"\n" + 
				"public class Test1 {\n" + 
				"	@NonNull String test0() {\n" + 
				"		return Lib1.none;\n" + 
				"	}\n" +
				"	@NonNull String test1() {\n" + 
				"		return Lib1.one;\n" + 
				"	}\n" +
				"}\n",
				true, new NullProgressMonitor()).getWorkingCopy(new NullProgressMonitor());
		CompilationUnit reconciled = unit.reconcile(getJLS8(), true, null, new NullProgressMonitor());
		IProblem[] problems = reconciled.getProblems();
		assertProblems(problems, new String[] {
			"Pb(953) Null type mismatch (type annotations): required '@NonNull String' but this expression has type '@Nullable String'",
		}, new int[] { 8 });
	}

	public void testLibsWithTypeParameters() throws Exception {
		myCreateJavaProject("TestLibs");
		addLibraryWithExternalAnnotations(this.project, "lib1.jar", "annots", new String[] {
				"/UnannotatedLib/libs/Lib1.java",
				"package libs;\n" + 
				"\n" +
				"public interface Lib1<U,V,W extends U> {\n" + 
				"	U getU();\n" + 
				"	V getV();\n" +
				"	W getW();\n" +
				"	<X,Y extends CharSequence> Y fun(X x);\n" + 
				"}\n"
			}, null);
		createFileInProject("annots/libs", "Lib1.eea", 
				"class libs/Lib1\n" +
				" <U:Ljava/lang/Object;V:Ljava/lang/Object;W:TU;>\n" +
				" <0U:Ljava/lang/Object;1V:Ljava/lang/Object;W:T1U;>\n" +
				"\n" +
				"fun\n" +
				" <X:Ljava/lang/Object;Y::Ljava/lang/CharSequence;>(TX;)TY;\n" +
				" <1X:Ljava/lang/Object;Y::L1java/lang/CharSequence;>(TX;)TY;\n" +
				"\n");
		IPackageFragment fragment = this.project.getPackageFragmentRoots()[0].createPackageFragment("tests", true, null);
		ICompilationUnit unit = fragment.createCompilationUnit("Test1.java", 
				"package tests;\n" + 
				"import org.eclipse.jdt.annotation.*;\n" + 
				"\n" + 
				"import libs.Lib1;\n" + 
				"\n" + 
				"public class Test1 {\n" + 
				"	@NonNull String test0(Lib1<@Nullable String,@NonNull String,@NonNull String> l) {\n" + 
				"		return l.getU();\n" + // mismatch: U is nullable 
				"	}\n" +
				"	@NonNull String test1(Lib1<@Nullable String,@NonNull String,@NonNull String> l) {\n" + 
				"		return l.getV();\n" + // OK: V is nonnull
				"	}\n" +
				"	@NonNull String test2(Lib1<@Nullable String,@NonNull String,@NonNull String> l) {\n" + 
				"		return l.getW();\n" + // OK: V is nonnull
				"	}\n" +
				"	Lib1<@NonNull String, @NonNull String, @NonNull String> f1;\n" + // mismatch at U
				"	Lib1<@Nullable String, String, @NonNull String> f2;\n" + // mismatch at V
				"	Lib1<@Nullable String, @NonNull String, @Nullable String> f3;\n" + // mismatch at W
				"	@Nullable String test3(Lib1<@Nullable String,@NonNull String,@NonNull String> l) {\n" +
				"		return l.<@Nullable String,@Nullable String>fun(\"\");\n" + // mismatches at X and Y
				"	}\n" +
				"}\n",
				true, new NullProgressMonitor()).getWorkingCopy(new NullProgressMonitor());
		CompilationUnit reconciled = unit.reconcile(getJLS8(), true, null, new NullProgressMonitor());
		IProblem[] problems = reconciled.getProblems();
		assertProblems(problems, new String[] {
			"Pb(953) Null type mismatch (type annotations): required '@NonNull String' but this expression has type '@Nullable String'",
			"Pb(964) Null constraint mismatch: The type '@NonNull String' is not a valid substitute for the type parameter '@Nullable U extends Object'",
			"Pb(964) Null constraint mismatch: The type 'String' is not a valid substitute for the type parameter '@NonNull V extends Object'",
			"Pb(964) Null constraint mismatch: The type '@Nullable String' is not a valid substitute for the type parameter 'W extends @NonNull U extends Object'",
			"Pb(964) Null constraint mismatch: The type '@Nullable String' is not a valid substitute for the type parameter '@NonNull X extends Object'",
			"Pb(964) Null constraint mismatch: The type '@Nullable String' is not a valid substitute for the type parameter 'Y extends @NonNull CharSequence'",
		}, new int[] { 8, 16, 17, 18, 20, 20 });
	}

	public void testLibsWithTypeArgOfSuper() throws Exception {
		myCreateJavaProject("TestLibs");
		addLibraryWithExternalAnnotations(this.project, "lib1.jar", "annots", new String[] {
				"/UnannotatedLib/libs/LibSuper.java",
				"package libs;\n" + 
				"\n" +
				"public interface LibSuper<T,U> {\n" + 
				"	U apply(T t);\n" + 
				"}\n",
				"/UnannotatedLib/libs/Lib1.java",
				"package libs;\n" + 
				"\n" +
				"public interface Lib1 extends LibSuper<String,Exception> {\n" + 
				"}\n"
			}, null);
		createFileInProject("annots/libs", "Lib1.eea", 
				"class libs/Lib1\n" +
				"super libs/LibSuper\n" +
				" <Ljava/lang/String;Ljava/lang/Exception;>\n" +
				" <L1java/lang/String;L0java/lang/Exception;>\n" +
				"\n");
		IPackageFragment fragment = this.project.getPackageFragmentRoots()[0].createPackageFragment("tests", true, null);
		ICompilationUnit unit = fragment.createCompilationUnit("Test1.java", 
				"package tests;\n" + 
				"import org.eclipse.jdt.annotation.*;\n" + 
				"\n" + 
				"import libs.Lib1;\n" + 
				"\n" + 
				"public class Test1 {\n" + 
				"	@NonNull Exception test0(Lib1 lib, @Nullable String str) {\n" + 
				"		return lib\n" +
				"				.apply(str);\n" + 
				"	}\n" +
				"}\n",
				true, new NullProgressMonitor()).getWorkingCopy(new NullProgressMonitor());
		CompilationUnit reconciled = unit.reconcile(getJLS8(), true, null, new NullProgressMonitor());
		IProblem[] problems = reconciled.getProblems();
		assertProblems(problems, new String[] {
			"Pb(953) Null type mismatch (type annotations): required '@NonNull Exception' but this expression has type '@Nullable Exception'",
			"Pb(953) Null type mismatch (type annotations): required '@NonNull String' but this expression has type '@Nullable String'",
		}, new int[] { 8, 9 });
	}

	/** Project with real JRE. */
	public void test2() throws Exception {
		// library type used: j.u.Map (no need for JRE8)
		Hashtable options = JavaCore.getOptions();
		try {
			setupJavaProject("Test2");
			this.project.getProject().build(IncrementalProjectBuilder.FULL_BUILD, null);
			IMarker[] markers = this.project.getProject().findMarkers(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE);
			assertNoMarkers(markers);
		} finally {
			// project using a full JRE container initializes global options to 1.8 -- must reset now:
			JavaCore.setOptions(options);
		}
	}

	/** Project with real JRE8.
	 * More interesting work with generics
	 * .classpath uses var TESTWORK for path to external annotations.
	 */
	public void test3() throws Exception {
		if (!hasJRE18()) {
			System.out.println("Skipping ExternalAnnotations18Test.test3(), needs JRE8");
			return;
		}
		final String TESTWORK_VAR_NAME = "TESTWORK";
		JavaCore.setClasspathVariable(TESTWORK_VAR_NAME, new Path(getSourceWorkspacePath()), null);
		Hashtable options = JavaCore.getOptions();
		try {
			setupJavaProject("Test3");
			this.project.getProject().build(IncrementalProjectBuilder.FULL_BUILD, null);
			IMarker[] markers = this.project.getProject().findMarkers(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE);
			assertNoMarkers(markers);
		} finally {
			// project using a full JRE container initializes global options to 1.8 -- must reset now:
			JavaCore.setOptions(options);
			JavaCore.removeClasspathVariable(TESTWORK_VAR_NAME, null);
		}
	}

	// ===== Full round trip: detect problem - annotated - detect problem change =====

	public void testAnnotateFieldWithParameterizedType() throws Exception {
		myCreateJavaProject("TestLibs");
		String lib1Content =
				"package libs;\n" + 
				"\n" +
				"public abstract class Lib1<T> {\n" +
				"	public Lib1<T> one;\n" +
				"	public abstract T get();\n" +
				"}\n";
		addLibraryWithExternalAnnotations(this.project, "lib1.jar", "annots", new String[] {
				"/UnannotatedLib/libs/Lib1.java",
				lib1Content
			}, null);

		// type check sources:
		IPackageFragment fragment = this.project.getPackageFragmentRoots()[0].createPackageFragment("tests", true, null);
		ICompilationUnit cu = fragment.createCompilationUnit("Test1.java",
				"package tests;\n" + 
				"import org.eclipse.jdt.annotation.*;\n" + 
				"\n" + 
				"import libs.Lib1;\n" + 
				"\n" + 
				"public class Test1 {\n" + 
				"	@NonNull String test0(Lib1<String> stringLib) {\n" + 
				"		return stringLib.one.get();\n" + 
				"	}\n" +
				"}\n",
				true, new NullProgressMonitor()).getWorkingCopy(new NullProgressMonitor());
		CompilationUnit reconciled = cu.reconcile(getJLS8(), true, null, new NullProgressMonitor());
		assertProblems(reconciled.getProblems(), new String[] {
				"Pb(955) Null type safety (type annotations): The expression of type 'String' needs unchecked conversion to conform to '@NonNull String'",
		}, new int[] { 8 });

		// acquire library AST:
		IType type = this.project.findType("libs.Lib1");
		ICompilationUnit libWorkingCopy = type.getClassFile().getWorkingCopy(this.wcOwner, null);
		ASTParser parser = ASTParser.newParser(getJLS8());
		parser.setSource(libWorkingCopy);
		parser.setResolveBindings(true);
		parser.setStatementsRecovery(false);
		parser.setBindingsRecovery(false);
		CompilationUnit unit = (CompilationUnit) parser.createAST(null);
		libWorkingCopy.discardWorkingCopy();
		
		// find type binding:
		int start = lib1Content.indexOf("one");
		ASTNode name = NodeFinder.perform(unit, start, 0);
		assertTrue("should be simple name", name.getNodeType() == ASTNode.SIMPLE_NAME);
		IVariableBinding fieldBinding = (IVariableBinding) ((SimpleName)name).resolveBinding();
		
		// find annotation file (not yet existing):
		IFile annotationFile = ExternalAnnotationUtil.getAnnotationFile(this.project, fieldBinding.getDeclaringClass(), null);
		assertFalse("file should not exist", annotationFile.exists());
		assertEquals("file path", "/TestLibs/annots/libs/Lib1.eea", annotationFile.getFullPath().toString());

		// annotate:
		String originalSignature = ExternalAnnotationUtil.extractGenericTypeSignature(fieldBinding.getVariableDeclaration().getType());
		ExternalAnnotationUtil.annotateMember("libs/Lib1", annotationFile,
				"one", 
				originalSignature, 
				"Llibs/Lib1<T0T;>;", 
				MergeStrategy.OVERWRITE_ANNOTATIONS, null);
		assertTrue("file should exist", annotationFile.exists());

		// check that the error is even worse now:
		reconciled = cu.reconcile(getJLS8(), true, null, new NullProgressMonitor());
		assertProblems(reconciled.getProblems(), new String[] {
				"Pb(953) Null type mismatch (type annotations): required '@NonNull String' but this expression has type '@Nullable String'",
		}, new int[] { 8 });
	}
	

	public void testAnnotateMethodParameter() throws Exception {
		myCreateJavaProject("TestLibs");
		String lib1Content =
				"package libs;\n" + 
				"\n" +
				"public abstract class Lib1<T,U> {\n" +
				"	public abstract void take(Lib1<X,U> lx);\n" +
				"	public static class X {}\n" +
				"}\n";
		addLibraryWithExternalAnnotations(this.project, "lib1.jar", "annots", new String[] {
				"/UnannotatedLib/libs/Lib1.java",
				lib1Content
			}, null);

		// type check sources:
		IPackageFragment fragment = this.project.getPackageFragmentRoots()[0].createPackageFragment("tests", true, null);
		ICompilationUnit cu = fragment.createCompilationUnit("Test1.java",
				"package tests;\n" + 
				"import org.eclipse.jdt.annotation.*;\n" + 
				"import libs.Lib1;\n" + 
				"\n" + 
				"public class Test1 {\n" + 
				"	void test0(Lib1<Lib1.X,@Nullable String> xLib1, Lib1<Lib1.@Nullable X,@NonNull String> xLib2) {\n" + 
				"		xLib1.take(xLib2);\n" + 
				"	}\n" +
				"}\n",
				true, new NullProgressMonitor()).getWorkingCopy(new NullProgressMonitor());
		CompilationUnit reconciled = cu.reconcile(getJLS8(), true, null, new NullProgressMonitor());
		assertProblems(reconciled.getProblems(), new String[] {
				"Pb(953) Null type mismatch (type annotations): required 'Lib1<Lib1.X,@Nullable String>' but this expression has type 'Lib1<Lib1.@Nullable X,@NonNull String>'",
		}, new int[] { 7 });

		// acquire library AST:
		IType type = this.project.findType("libs.Lib1");
		ICompilationUnit libWorkingCopy = type.getClassFile().getWorkingCopy(this.wcOwner, null);
		ASTParser parser = ASTParser.newParser(getJLS8());
		parser.setSource(libWorkingCopy);
		parser.setResolveBindings(true);
		parser.setStatementsRecovery(false);
		parser.setBindingsRecovery(false);
		CompilationUnit unit = (CompilationUnit) parser.createAST(null);
		libWorkingCopy.discardWorkingCopy();
		
		// find type binding:
		int start = lib1Content.indexOf("take");
		ASTNode name = NodeFinder.perform(unit, start, 0);
		assertTrue("should be simple name", name.getNodeType() == ASTNode.SIMPLE_NAME);
		ASTNode method = name.getParent();
		IMethodBinding methodBinding = ((MethodDeclaration)method).resolveBinding();
		
		// find annotation file (not yet existing):
		IFile annotationFile = ExternalAnnotationUtil.getAnnotationFile(this.project, methodBinding.getDeclaringClass(), null);
		assertFalse("file should not exist", annotationFile.exists());
		assertEquals("file path", "/TestLibs/annots/libs/Lib1.eea", annotationFile.getFullPath().toString());

		// annotate:
		String originalSignature = ExternalAnnotationUtil.extractGenericSignature(methodBinding);
		ExternalAnnotationUtil.annotateMember("libs/Lib1", annotationFile,
				"take", 
				originalSignature, 
				"(Llibs/Lib1<L0libs/Lib1$X;T1U;>;)V", // <- two annotations: @Nullable X and @NonNull U
				MergeStrategy.OVERWRITE_ANNOTATIONS, null);
		assertTrue("file should exist", annotationFile.exists());

		// check that the error is resolved now:
		reconciled = cu.reconcile(getJLS8(), true, null, new NullProgressMonitor());
		assertNoProblems(reconciled.getProblems());
	}
	

	public void testAnnotateConstructorParameter() throws Exception {
		myCreateJavaProject("TestLibs");
		String lib1Content =
				"package libs;\n" + 
				"\n" +
				"public class Lib1<U> {\n" +
				"	public Lib1(int ignore, U string) {}\n" +
				"}\n";
		addLibraryWithExternalAnnotations(this.project, "lib1.jar", "annots", new String[] {
				"/UnannotatedLib/libs/Lib1.java",
				lib1Content
			}, null);

		// type check sources:
		IPackageFragment fragment = this.project.getPackageFragmentRoots()[0].createPackageFragment("tests", true, null);
		ICompilationUnit cu = fragment.createCompilationUnit("Test1.java",
				"package tests;\n" + 
				"import org.eclipse.jdt.annotation.*;\n" + 
				"import libs.Lib1;\n" + 
				"\n" + 
				"public class Test1 {\n" + 
				"	Object test0() {\n" + 
				"		Lib1<@NonNull String> lib = new Lib1<>(1, null);\n" +
				"		return lib;\n" + 
				"	}\n" +
				"}\n",
				true, new NullProgressMonitor()).getWorkingCopy(new NullProgressMonitor());
		CompilationUnit reconciled = cu.reconcile(getJLS8(), true, null, new NullProgressMonitor());
		assertProblems(reconciled.getProblems(), new String[] {
				"Pb(910) Null type mismatch: required '@NonNull String' but the provided value is null",
		}, new int[] { 7 });

		// acquire library AST:
		IType type = this.project.findType("libs.Lib1");
		ICompilationUnit libWorkingCopy = type.getClassFile().getWorkingCopy(this.wcOwner, null);
		ASTParser parser = ASTParser.newParser(getJLS8());
		parser.setSource(libWorkingCopy);
		parser.setResolveBindings(true);
		parser.setStatementsRecovery(false);
		parser.setBindingsRecovery(false);
		CompilationUnit unit = (CompilationUnit) parser.createAST(null);
		libWorkingCopy.discardWorkingCopy();
		
		// find type binding:
		int start = lib1Content.indexOf("U string");
		ASTNode name = NodeFinder.perform(unit, start, 0);
		assertTrue("should be simple name", name.getNodeType() == ASTNode.SIMPLE_NAME);
		ASTNode method = name.getParent().getParent().getParent();
		IMethodBinding methodBinding = ((MethodDeclaration)method).resolveBinding();
		
		// find annotation file (not yet existing):
		IFile annotationFile = ExternalAnnotationUtil.getAnnotationFile(this.project, methodBinding.getDeclaringClass(), null);
		assertFalse("file should not exist", annotationFile.exists());
		assertEquals("file path", "/TestLibs/annots/libs/Lib1.eea", annotationFile.getFullPath().toString());

		// annotate:
		String originalSignature = ExternalAnnotationUtil.extractGenericSignature(methodBinding);
		ExternalAnnotationUtil.annotateMember("libs/Lib1", annotationFile,
				"<init>", 
				originalSignature, 
				"(IT0U;)V", // <- @Nullable U
				MergeStrategy.OVERWRITE_ANNOTATIONS, null);
		assertTrue("file should exist", annotationFile.exists());

		// check that the error is resolved now:
		reconciled = cu.reconcile(getJLS8(), true, null, new NullProgressMonitor());
		assertNoProblems(reconciled.getProblems());
		
		// invert annotation:
		ExternalAnnotationUtil.annotateMethodParameterType("libs/Lib1", annotationFile,
				"<init>", 
				originalSignature, 
				"T1U;", // <- @NonNull U
				1, // position 
				MergeStrategy.OVERWRITE_ANNOTATIONS, null);
		assertTrue("file should exist", annotationFile.exists());

		// check that the error is back now:
		reconciled = cu.reconcile(getJLS8(), true, null, new NullProgressMonitor());
		assertProblems(reconciled.getProblems(), new String[] {
				"Pb(910) Null type mismatch: required '@NonNull String' but the provided value is null",
		}, new int[] { 7 });

		// check that the previous entry has been overwritten:
		assertEquals(
				"class libs/Lib1\n" + 
				"<init>\n" + 
				" (ITU;)V\n" + 
				" (IT1U;)V\n",
				readFully(annotationFile));
	}

	// ===== white box tests for ExternalAnnotationUtil =====

	public void testBug470666a() throws CoreException, IOException {
		myCreateJavaProject("TestAnnot");
		String lib1Content =
				"package libs;\n" + 
				"\n" +
				"interface Function<T,U> {}\n" +
				"interface Collector<T,A,R> {}\n" +
				"public class Collectors {\n" +
				"	 public static <T, U, A, R>\n" + 
				"    Collector<T, ?, R> mapping(Function<? super T, ? extends U> mapper,\n" + 
				"                               Collector<? super U, A, R> downstream) { return null; }\n" +
				"}\n";
		addLibraryWithExternalAnnotations(this.project, "lib1.jar", "annots", new String[] {
				"/UnannotatedLib/libs/Collectors.java",
				lib1Content
			}, null);

		// acquire library AST:
		IType type = this.project.findType("libs.Collectors");
		ICompilationUnit libWorkingCopy = type.getClassFile().getWorkingCopy(this.wcOwner, null);
		ASTParser parser = ASTParser.newParser(getJLS8());
		parser.setSource(libWorkingCopy);
		parser.setResolveBindings(true);
		parser.setStatementsRecovery(false);
		parser.setBindingsRecovery(false);
		CompilationUnit unit = (CompilationUnit) parser.createAST(null);
		libWorkingCopy.discardWorkingCopy();
		
		// find type binding:
		int start = lib1Content.indexOf("T, ? extends U>"); // bound of type param of method param
		ASTNode name = NodeFinder.perform(unit, start, 0);
		assertTrue("should be simple name", name.getNodeType() == ASTNode.SIMPLE_NAME);
		ASTNode method = name.getParent();
		while (!(method instanceof MethodDeclaration))
			method = method.getParent();
		IMethodBinding methodBinding = ((MethodDeclaration)method).resolveBinding();
		
		// find annotation file (not yet existing):
		IFile annotationFile = ExternalAnnotationUtil.getAnnotationFile(this.project, methodBinding.getDeclaringClass(), null);
		assertFalse("file should not exist", annotationFile.exists());
		assertEquals("file path", "/TestAnnot/annots/libs/Collectors.eea", annotationFile.getFullPath().toString());

		// annotate:
		String originalSignature = ExternalAnnotationUtil.extractGenericSignature(methodBinding);
		String[] annotatedSign = ExternalAnnotationUtil.annotateParameterType(
				originalSignature, 
				"Llibs/Function<-T1T;+TU;>;",  // <- @NonNull T
				0, MergeStrategy.OVERWRITE_ANNOTATIONS);
		assertEquals("dry-run result", "[<T:Ljava/lang/Object;U:Ljava/lang/Object;A:Ljava/lang/Object;R:Ljava/lang/Object;>(, " + 
				"Llibs/Function<-TT;+TU;>;, " + 
				"Llibs/Function<-T1T;+TU;>;, " +  // <- @NonNull T
				"Llibs/Collector<-TU;TA;TR;>;)Llibs/Collector<TT;*TR;>;]",
				Arrays.toString(annotatedSign));
	}

	public void testBug470666b() throws CoreException, IOException {
		myCreateJavaProject("TestAnnot");
		String lib1Content =
				"package libs;\n" + 
				"\n" +
				"interface Function<T,U> {}\n" +
				"interface Collector<T,A,R> {}\n" +
				"public class Collectors {\n" +
				"	 public static <T, U, A, R>\n" + 
				"    Collector<T, ?, R> mapping(Function<? super T, ? extends U> mapper,\n" + 
				"                               Collector<? super U, A, R> downstream) { return null; }\n" +
				"}\n";
		addLibraryWithExternalAnnotations(this.project, "lib1.jar", "annots", new String[] {
				"/UnannotatedLib/libs/Collectors.java",
				lib1Content
			}, null);

		// acquire library AST:
		IType type = this.project.findType("libs.Collectors");
		ICompilationUnit libWorkingCopy = type.getClassFile().getWorkingCopy(this.wcOwner, null);
		ASTParser parser = ASTParser.newParser(getJLS8());
		parser.setSource(libWorkingCopy);
		parser.setResolveBindings(true);
		parser.setStatementsRecovery(false);
		parser.setBindingsRecovery(false);
		CompilationUnit unit = (CompilationUnit) parser.createAST(null);
		libWorkingCopy.discardWorkingCopy();
		
		// find type binding:
		int start = lib1Content.indexOf("T, ?, R>"); // bound of return type
		ASTNode name = NodeFinder.perform(unit, start, 0);
		assertTrue("should be simple name", name.getNodeType() == ASTNode.SIMPLE_NAME);
		ASTNode method = name.getParent();
		while (!(method instanceof MethodDeclaration))
			method = method.getParent();
		IMethodBinding methodBinding = ((MethodDeclaration)method).resolveBinding();
		
		// find annotation file (not yet existing):
		IFile annotationFile = ExternalAnnotationUtil.getAnnotationFile(this.project, methodBinding.getDeclaringClass(), null);
		assertFalse("file should not exist", annotationFile.exists());
		assertEquals("file path", "/TestAnnot/annots/libs/Collectors.eea", annotationFile.getFullPath().toString());

		// annotate:
		String originalSignature = ExternalAnnotationUtil.extractGenericSignature(methodBinding);
		String[] annotatedSign = ExternalAnnotationUtil.annotateReturnType(
				originalSignature, 
				"Llibs/Collector<T1T;*TR;>;",  // <- @NonNull T
				MergeStrategy.OVERWRITE_ANNOTATIONS);
		assertEquals("dry-run result", "[<T:Ljava/lang/Object;U:Ljava/lang/Object;A:Ljava/lang/Object;R:Ljava/lang/Object;>(Llibs/Function<-TT;+TU;>;Llibs/Collector<-TU;TA;TR;>;), " + 
				"Llibs/Collector<TT;*TR;>;, " +
				"Llibs/Collector<T1T;*TR;>;, " +  // <- @NonNull T
				"]",
				Arrays.toString(annotatedSign));
	}

	public void testBug464081() throws CoreException, IOException {
		myCreateJavaProject("TestAnnot");
		String lib1Content =
				"package libs;\n" + 
				"\n" +
				"interface List<T> {}\n" +
				"public class Collections {\n" +
				"	 public static <T> List<T> unmodifiableList(List<? extends T> list) { return null; }\n" +
				"}\n";
		addLibraryWithExternalAnnotations(this.project, "lib1.jar", "annots", new String[] {
				"/UnannotatedLib/libs/Collections.java",
				lib1Content
			}, null);

		// acquire library AST:
		IType type = this.project.findType("libs.Collections");
		ICompilationUnit libWorkingCopy = type.getClassFile().getWorkingCopy(this.wcOwner, null);
		ASTParser parser = ASTParser.newParser(getJLS8());
		parser.setSource(libWorkingCopy);
		parser.setResolveBindings(true);
		parser.setStatementsRecovery(false);
		parser.setBindingsRecovery(false);
		CompilationUnit unit = (CompilationUnit) parser.createAST(null);
		libWorkingCopy.discardWorkingCopy();
		
		// find type binding:
		int start = lib1Content.indexOf("List<? extends T>");
		ASTNode name = NodeFinder.perform(unit, start, 0);
		assertTrue("should be simple name", name.getNodeType() == ASTNode.SIMPLE_NAME);
		ASTNode method = name.getParent();
		while (!(method instanceof MethodDeclaration))
			method = method.getParent();
		IMethodBinding methodBinding = ((MethodDeclaration)method).resolveBinding();
		
		// find annotation file (not yet existing):
		IFile annotationFile = ExternalAnnotationUtil.getAnnotationFile(this.project, methodBinding.getDeclaringClass(), null);
		assertFalse("file should not exist", annotationFile.exists());
		assertEquals("file path", "/TestAnnot/annots/libs/Collections.eea", annotationFile.getFullPath().toString());

		// annotate:
		String originalSignature = ExternalAnnotationUtil.extractGenericSignature(methodBinding);
		String[] annotatedSign = ExternalAnnotationUtil.annotateParameterType(
				originalSignature, 
				"Llibs/List<+T1T;>;",  // <- @NonNull T
				0, MergeStrategy.OVERWRITE_ANNOTATIONS);
		assertEquals("dry-run result",
				"[<T:Ljava/lang/Object;>(, " +
				"Llibs/List<+TT;>;, " + 
				"Llibs/List<+T1T;>;, " +  // <- @NonNull T
				")Llibs/List<TT;>;]",
				Arrays.toString(annotatedSign));
	}

	public void testBug471352() throws CoreException, IOException {
		myCreateJavaProject("TestAnnot");
		String lib1Content =
				"package libs;\n" + 
				"\n" +
				"interface List<T> {}\n" +
				"class Random {}\n" +
				"public class Collections {\n" +
				"	 public static void shuffle(List<?> list, Random rnd) { }\n" +
				"}\n";
		addLibraryWithExternalAnnotations(this.project, "lib1.jar", "annots", new String[] {
				"/UnannotatedLib/libs/Collections.java",
				lib1Content
			}, null);

		// acquire library AST:
		IType type = this.project.findType("libs.Collections");
		ICompilationUnit libWorkingCopy = type.getClassFile().getWorkingCopy(this.wcOwner, null);
		ASTParser parser = ASTParser.newParser(getJLS8());
		parser.setSource(libWorkingCopy);
		parser.setResolveBindings(true);
		parser.setStatementsRecovery(false);
		parser.setBindingsRecovery(false);
		CompilationUnit unit = (CompilationUnit) parser.createAST(null);
		libWorkingCopy.discardWorkingCopy();
		
		// find type binding:
		int start = lib1Content.indexOf("Random rnd");
		ASTNode name = NodeFinder.perform(unit, start, 0);
		assertTrue("should be simple name", name.getNodeType() == ASTNode.SIMPLE_NAME);
		ASTNode method = name.getParent();
		while (!(method instanceof MethodDeclaration))
			method = method.getParent();
		IMethodBinding methodBinding = ((MethodDeclaration)method).resolveBinding();
		
		// find annotation file (not yet existing):
		IFile annotationFile = ExternalAnnotationUtil.getAnnotationFile(this.project, methodBinding.getDeclaringClass(), null);
		assertFalse("file should not exist", annotationFile.exists());
		assertEquals("file path", "/TestAnnot/annots/libs/Collections.eea", annotationFile.getFullPath().toString());

		// annotate:
		String originalSignature = ExternalAnnotationUtil.extractGenericSignature(methodBinding);
		String[] annotatedSign = ExternalAnnotationUtil.annotateParameterType(
				originalSignature, 
				"L1libs/Random;",  // <- @NonNull Random
				1, MergeStrategy.OVERWRITE_ANNOTATIONS);
		assertEquals("dry-run result",
				"[(Llibs/List<*>;, " +
				"Llibs/Random;, " + 
				"L1libs/Random;, " +  // <- @NonNull Random
				")V]",
				Arrays.toString(annotatedSign));
	}

	// array content
	public void testBug471034a() throws CoreException, IOException {
		myCreateJavaProject("TestAnnot");
		String lib1Content =
				"package libs;\n" + 
				"\n" +
				"interface List<T> {}\n" +
				"class Random {}\n" +
				"public class Thread {\n" +
				"	 public static int enumerate(Thread tarray[]) { return 1; }\n" + 
				"}\n";
		addLibraryWithExternalAnnotations(this.project, "lib1.jar", "annots", new String[] {
				"/UnannotatedLib/libs/Thread.java",
				lib1Content
			}, null);

		// acquire library AST:
		IType type = this.project.findType("libs.Thread");
		ICompilationUnit libWorkingCopy = type.getClassFile().getWorkingCopy(this.wcOwner, null);
		ASTParser parser = ASTParser.newParser(getJLS8());
		parser.setSource(libWorkingCopy);
		parser.setResolveBindings(true);
		parser.setStatementsRecovery(false);
		parser.setBindingsRecovery(false);
		CompilationUnit unit = (CompilationUnit) parser.createAST(null);
		libWorkingCopy.discardWorkingCopy();
		
		// find type binding:
		int start = lib1Content.indexOf("Thread tarray[]");
		ASTNode name = NodeFinder.perform(unit, start, 0);
		assertTrue("should be simple name", name.getNodeType() == ASTNode.SIMPLE_NAME);
		ASTNode method = name.getParent();
		while (!(method instanceof MethodDeclaration))
			method = method.getParent();
		IMethodBinding methodBinding = ((MethodDeclaration)method).resolveBinding();
		
		// find annotation file (not yet existing):
		IFile annotationFile = ExternalAnnotationUtil.getAnnotationFile(this.project, methodBinding.getDeclaringClass(), null);
		assertFalse("file should not exist", annotationFile.exists());
		assertEquals("file path", "/TestAnnot/annots/libs/Thread.eea", annotationFile.getFullPath().toString());

		// annotate:
		String originalSignature = ExternalAnnotationUtil.extractGenericSignature(methodBinding);
		String[] annotatedSign = ExternalAnnotationUtil.annotateParameterType(
				originalSignature, 
				"[L1libs/Thread;",  // <- @NonNull Thread
				0, MergeStrategy.OVERWRITE_ANNOTATIONS);
		assertEquals("dry-run result",
				"[(, " +
				"[Llibs/Thread;, " + 
				"[L1libs/Thread;, " +  // <- @NonNull Thread
				")I]",
				Arrays.toString(annotatedSign));
	}

	// array dimension
	public void testBug471034b() throws CoreException, IOException {
		myCreateJavaProject("TestAnnot");
		String lib1Content =
				"package libs;\n" + 
				"\n" +
				"interface List<T> {}\n" +
				"class Random {}\n" +
				"public class Thread {\n" +
				"	 public static int enumerate(Thread tarray[][]) { return 1; }\n" + 
				"}\n";
		addLibraryWithExternalAnnotations(this.project, "lib1.jar", "annots", new String[] {
				"/UnannotatedLib/libs/Thread.java",
				lib1Content
			}, null);

		// acquire library AST:
		IType type = this.project.findType("libs.Thread");
		ICompilationUnit libWorkingCopy = type.getClassFile().getWorkingCopy(this.wcOwner, null);
		ASTParser parser = ASTParser.newParser(getJLS8());
		parser.setSource(libWorkingCopy);
		parser.setResolveBindings(true);
		parser.setStatementsRecovery(false);
		parser.setBindingsRecovery(false);
		CompilationUnit unit = (CompilationUnit) parser.createAST(null);
		libWorkingCopy.discardWorkingCopy();
		
		// find type binding:
		int start = lib1Content.indexOf("[][]");
		ASTNode name = NodeFinder.perform(unit, start, 0);
		assertTrue("should be dimension", name.getNodeType() == ASTNode.DIMENSION);
		ASTNode method = name.getParent();
		while (!(method instanceof MethodDeclaration))
			method = method.getParent();
		IMethodBinding methodBinding = ((MethodDeclaration)method).resolveBinding();
		
		// find annotation file (not yet existing):
		IFile annotationFile = ExternalAnnotationUtil.getAnnotationFile(this.project, methodBinding.getDeclaringClass(), null);
		assertFalse("file should not exist", annotationFile.exists());
		assertEquals("file path", "/TestAnnot/annots/libs/Thread.eea", annotationFile.getFullPath().toString());

		// annotate:
		String originalSignature = ExternalAnnotationUtil.extractGenericSignature(methodBinding);
		String[] annotatedSign = ExternalAnnotationUtil.annotateParameterType(
				originalSignature, 
				"[1[Llibs/Thread;",  // <- @NonNull array
				0, MergeStrategy.OVERWRITE_ANNOTATIONS);
		assertEquals("dry-run result",
				"[(, " +
				"[[Llibs/Thread;, " + 
				"[1[Llibs/Thread;, " +  // <- @NonNull array
				")I]",
				Arrays.toString(annotatedSign));
	}

	// varargs
	public void testBug471034c() throws CoreException, IOException {
		myCreateJavaProject("TestAnnot");
		String lib1Content =
				"package libs;\n" + 
				"\n" +
				"interface List<T> {}\n" +
				"class Random {}\n" +
				"public class Thread {\n" +
				"	 public static int enumerate(Thread ... tarray) { return 1; }\n" + 
				"}\n";
		addLibraryWithExternalAnnotations(this.project, "lib1.jar", "annots", new String[] {
				"/UnannotatedLib/libs/Thread.java",
				lib1Content
			}, null);

		// acquire library AST:
		IType type = this.project.findType("libs.Thread");
		ICompilationUnit libWorkingCopy = type.getClassFile().getWorkingCopy(this.wcOwner, null);
		ASTParser parser = ASTParser.newParser(getJLS8());
		parser.setSource(libWorkingCopy);
		parser.setResolveBindings(true);
		parser.setStatementsRecovery(false);
		parser.setBindingsRecovery(false);
		CompilationUnit unit = (CompilationUnit) parser.createAST(null);
		libWorkingCopy.discardWorkingCopy();
		
		// find type binding:
		int start = lib1Content.indexOf("...");
		ASTNode name = NodeFinder.perform(unit, start, 0);
		assertTrue("should be variable", name.getNodeType() == ASTNode.SINGLE_VARIABLE_DECLARATION);
		ASTNode method = name.getParent();
		while (!(method instanceof MethodDeclaration))
			method = method.getParent();
		IMethodBinding methodBinding = ((MethodDeclaration)method).resolveBinding();
		
		// find annotation file (not yet existing):
		IFile annotationFile = ExternalAnnotationUtil.getAnnotationFile(this.project, methodBinding.getDeclaringClass(), null);
		assertFalse("file should not exist", annotationFile.exists());
		assertEquals("file path", "/TestAnnot/annots/libs/Thread.eea", annotationFile.getFullPath().toString());

		// annotate:
		String originalSignature = ExternalAnnotationUtil.extractGenericSignature(methodBinding);
		String[] annotatedSign = ExternalAnnotationUtil.annotateParameterType(
				originalSignature, 
				"[1Llibs/Thread;",  // <- @NonNull array
				0, MergeStrategy.OVERWRITE_ANNOTATIONS);
		assertEquals("dry-run result",
				"[(, " +
				"[Llibs/Thread;, " + 
				"[1Llibs/Thread;, " +  // <- @NonNull array
				")I]",
				Arrays.toString(annotatedSign));
	}

	public void testBrokenConfig1() throws Exception {
		LogListener listener = new LogListener();
		try {
			Platform.addLogListener(listener);
	
			myCreateJavaProject("TestBrokenConfig1");
			addLibraryWithExternalAnnotations(this.project, "lib1.jar", "/NoProject", new String[] {
					"/UnannotatedLib/libs/Lib1.java",
					"package libs;\n" + 
					"\n" + 
					"import java.util.Collection;\n" + 
					"import java.util.Iterator;\n" + 
					"\n" + 
					"public interface Lib1 {\n" + 
					"	<T> Iterator<T> unconstrainedTypeArguments1(Collection<T> in);\n" + 
					"	Iterator<String> unconstrainedTypeArguments2(Collection<String> in);\n" + 
					"	<T> Iterator<? extends T> constrainedWildcards(Collection<? extends T> in);\n" + 
					"	<T extends Collection<?>> T constrainedTypeParameter(T in);\n" + 
					"}\n",
					"/UnannotatedLib/libs/Lib2.java",
					"package libs;\n" +
					"public interface Lib2 {\n" +
					"	String test(String s);\n" +
					"}\n"
				}, null);
			IPackageFragment fragment = this.project.getPackageFragmentRoots()[0].createPackageFragment("tests", true, null);
			ICompilationUnit unit = fragment.createCompilationUnit("Test1.java", 
					"package tests;\n" + 
					"import org.eclipse.jdt.annotation.*;\n" + 
					"\n" + 
					"import java.util.Collection;\n" + 
					"import java.util.Iterator;\n" + 
					"\n" + 
					"import libs.Lib1;\n" + 
					"\n" + 
					"public class Test1 {\n" + 
					"	Iterator<@NonNull String> test1(Lib1 lib, Collection<@Nullable String> coll) {\n" + 
					"		return lib.unconstrainedTypeArguments1(coll);\n" + 
					"	}\n" + 
					"	Iterator<@NonNull String> test2(Lib1 lib, Collection<@Nullable String> coll) {\n" + 
					"		return lib.unconstrainedTypeArguments2(coll);\n" + 
					"	}\n" +
					"	Iterator<? extends @NonNull String> test3(Lib1 lib, Collection<String> coll) {\n" +
					"		return lib.constrainedWildcards(coll);\n" +
					"	}\n" +
					"	@NonNull Collection<String> test4(Lib1 lib, @Nullable Collection<String> in) {\n" +
					"		return lib.constrainedTypeParameter(in);\n" +
					"	}\n" + 
					"}\n",
					true, new NullProgressMonitor()).getWorkingCopy(new NullProgressMonitor());
			CompilationUnit reconciled = unit.reconcile(getJLS8(), true, null, new NullProgressMonitor());
			IProblem[] problems = reconciled.getProblems();
			assertEquals("number of problems", 4, problems.length);

			// second class to test if problem is logged more than once
			ICompilationUnit unit2 = fragment.createCompilationUnit("Test2.java", 
					"package tests;\n" + 
					"import libs.Lib2;\n" + 
					"\n" + 
					"public class Test2 {\n" + 
					"	void test1(Lib2 lib) {\n" +
					"		lib.test(null);\n" + 
					"	}\n" + 
					"}\n",
					true, new NullProgressMonitor()).getWorkingCopy(new NullProgressMonitor());
			CompilationUnit reconciled2 = unit2.reconcile(getJLS8(), true, null, new NullProgressMonitor());
			assertNoProblems(reconciled2.getProblems());
			
			assertEquals("number of log entries", 0, listener.loggedStatus.size());
		} finally {
			Platform.removeLogListener(listener);
		}
	}

	/** Lib exists as workspace project. Perform full build. */
	public void testProjectDependencyFullBuild() throws Exception {
		try {
			setupJavaProject("Lib");
			this.project.getProject().build(IncrementalProjectBuilder.FULL_BUILD, null);
	
			setupJavaProject("Test1");
			addProjectDependencyWithExternalAnnotations(this.project, "/Lib", "annots", null);
			this.project.getProject().build(IncrementalProjectBuilder.FULL_BUILD, null);
			IMarker[] markers = this.project.getProject().findMarkers(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE);
			assertNoMarkers(markers);
		} finally {
			deleteProject("Lib");
		}
	}

	/** Lib exists as workspace project. Reconcile an individual CU. */
	public void testProjectDependencyReconcile1() throws Exception {
		try {
			setupJavaProject("Lib");
			this.project.getProject().build(IncrementalProjectBuilder.FULL_BUILD, null);
			this.root = null; // prepare to get the root from project Test1
	
			setupJavaProject("Test1");
			addProjectDependencyWithExternalAnnotations(this.project, "/Lib", "annots", null);
			IPackageFragment fragment = this.root.getPackageFragment("test1");
			ICompilationUnit unit = fragment.getCompilationUnit("Test1.java").getWorkingCopy(new NullProgressMonitor());
			CompilationUnit reconciled = unit.reconcile(getJLS8(), true, null, new NullProgressMonitor());
			IProblem[] problems = reconciled.getProblems();
			assertNoProblems(problems);
		} finally {
			deleteProject("Lib");
		}
	}

	/** Lib exists as workspace project. Type-Annotations in zip file. Reconcile an individual CU. */
	public void testProjectDependencyReconcile2() throws Exception {
		try {
			setupJavaProject("Lib");
			this.project.getProject().build(IncrementalProjectBuilder.FULL_BUILD, null);
			this.root = null; // prepare to get the root from project Test1
	
			setupJavaProject("Test3b");
			Util.createSourceZip(
				new String[] {
					"libs/MyFunction.eea", 
					"class libs/MyFunction\n" + 
					" <T:R:>\n" + 
					"\n" + 
					"compose\n" + 
					" <V:Ljava/lang/Object;>(Llibs/MyFunction<-TV;+TT;>;)Llibs/MyFunction<TV;TR;>;\n" + 
					" <V:Ljava/lang/Object;>(Llibs/MyFunction<-TV;+T0T;>;)Llibs/MyFunction<TV;TR;>;\n" + 
					"\n",
					"libs/Arrays.eea", 
					"class libs/Arrays\n" + 
					"\n" +
					"array\n" +
					" [Ljava/lang/String;\n" +
					" [1L0java/lang/String;\n" +
					"\n" + 
					"getArray\n" +
					" ()[[Ljava/lang/String;\n" +
					" ()[0[1L0java/lang/String;\n"
				},
				this.project.getProject().getLocation().toString()+"/annots.zip");
			this.project.getProject().refreshLocal(1, new NullProgressMonitor());

			addProjectDependencyWithExternalAnnotations(this.project, "/Lib", "annots.zip", null);
			IPackageFragment fragment = this.root.getPackageFragment("test1");
			ICompilationUnit unit = fragment.getCompilationUnit("Reconcile2.java").getWorkingCopy(new NullProgressMonitor());
			CompilationUnit reconciled = unit.reconcile(getJLS8(), true, null, new NullProgressMonitor());
			IProblem[] problems = reconciled.getProblems();
			assertNoProblems(problems);
		} finally {
			deleteProject("Lib");
		}
	}

	/** Lib exists as workspace project. Invocations conflict with type parameter constraints. Reconcile an individual CU. */
	public void testProjectDependencyReconcile3() throws Exception {
		try {
			setupJavaProject("Lib");
			this.project.getProject().build(IncrementalProjectBuilder.FULL_BUILD, null);
			this.root = null; // prepare to get the root from project Test1
	
			setupJavaProject("Test3b");
			Util.createSourceZip(
				new String[] {
					"libs/MyFunction.eea", 
					"class libs/MyFunction\n" + 
					" <T:R:>\n" + 
					" <T:1R:>\n" + 
					"\n" + 
					"compose\n" + 
					" <V:Ljava/lang/Object;>(Llibs/MyFunction<-TV;+TT;>;)Llibs/MyFunction<TV;TR;>;\n" + 
					" <1V:Ljava/lang/Object;>(Llibs/MyFunction<-TV;+TT;>;)Llibs/MyFunction<TV;TR;>;\n" + 
					"\n",
				},
				this.project.getProject().getLocation().toString()+"/annots.zip");
			this.project.getProject().refreshLocal(1, new NullProgressMonitor());

			addProjectDependencyWithExternalAnnotations(this.project, "/Lib", "annots.zip", null);
			IPackageFragment fragment = this.root.getPackageFragment("test1");
			ICompilationUnit unit = fragment.getCompilationUnit("Reconcile3.java").getWorkingCopy(new NullProgressMonitor());
			CompilationUnit reconciled = unit.reconcile(getJLS8(), true, null, new NullProgressMonitor());
			assertProblems(reconciled.getProblems(), new String[] {
					"Pb(964) Null constraint mismatch: The type '@Nullable B' is not a valid substitute for the type parameter '@NonNull R'",
					"Pb(964) Null constraint mismatch: The type '@Nullable String' is not a valid substitute for the type parameter '@NonNull V'",
			}, new int[] { 12, 17 });
		} finally {
			deleteProject("Lib");
		}
	}
	
	public void testFreeTypeVariableReturn() throws Exception {
		myCreateJavaProject("TestLibs");
		addLibraryWithExternalAnnotations(this.project, "lib1.jar", "annots", new String[] {
				"/UnannotatedLib/libs/Lib1.java",
				"package libs;\n" + 
				"\n" + 
				"public interface Lib1<T> {\n" + 
				"	T get();\n" + 
				"}\n"
			}, null);
		IPackageFragment fragment = this.project.getPackageFragmentRoots()[0].createPackageFragment("tests", true, null);
		ICompilationUnit unit = fragment.createCompilationUnit("Test1.java", 
				"package tests;\n" + 
				"import org.eclipse.jdt.annotation.*;\n" + 
				"\n" + 
				"import libs.Lib1;\n" + 
				"\n" + 
				"public class Test1 {\n" + 
				"	@NonNull String test0(Lib1<@Nullable String> lib) {\n" + 
				"		return lib.get();\n" + 
				"	}\n" + 
				"	@NonNull String test1(Lib1<@NonNull String> lib) {\n" + 
				"		return lib.get();\n" + 
				"	}\n" + 
				"}\n",
				true, new NullProgressMonitor()).getWorkingCopy(new NullProgressMonitor());
		CompilationUnit reconciled = unit.reconcile(getJLS8(), true, null, new NullProgressMonitor());
		assertProblems(reconciled.getProblems(), new String[] {
				"Pb(953) Null type mismatch (type annotations): required '@NonNull String' but this expression has type '@Nullable String'",
				"Pb(980) Unsafe interpretation of method return type as '@NonNull' based on the receiver type 'Lib1<@NonNull String>'. Type 'Lib1<T>' doesn't seem to be designed with null type annotations in mind",
		}, new int[] { 8, 11 });
		// just mark that Lib1 now has null annotations:
		createFileInProject("annots/libs", "Lib1.eea",
				"class libs/Lib1\n" +
				" <T:Ljava/lang/Object;>\n" + 
				" <T:Ljava/lang/Object;>\n" + 
				"\n");
		reconciled = unit.reconcile(getJLS8(), true, null, new NullProgressMonitor());
		assertProblems(reconciled.getProblems(), new String[] {
				"Pb(953) Null type mismatch (type annotations): required '@NonNull String' but this expression has type '@Nullable String'",
		}, new int[] { 8 });
	}
	
	public void testFreeTypeVariableReturnSeverities() throws Exception {
		myCreateJavaProject("TestLibs");
		addLibraryWithExternalAnnotations(this.project, "lib1.jar", "annots", new String[] {
				"/UnannotatedLib/libs/Lib1.java",
				"package libs;\n" + 
				"\n" + 
				"public interface Lib1<T> {\n" + 
				"	T get();\n" + 
				"}\n"
			}, null);
		this.currentProject = this.project;
		addLibrary("lib2.jar", null, new String[] {
				"/UnanntatedLib2/libs/Lib2.java",
				"package libs;\n" + 
				"\n" + 
				"public interface Lib2<T> {\n" + 
				"	T get();\n" + 
				"}\n"				
		}, "1.8");
		IPackageFragment fragment = this.project.getPackageFragmentRoots()[0].createPackageFragment("tests", true, null);
		fragment.createCompilationUnit("Lib3.java", 
				"package tests;\n" +
				"public interface Lib3<T> {\n" +
				"	T get();\n" +
				"}\n",
				true, new NullProgressMonitor());
		this.project.getProject().build(IncrementalProjectBuilder.FULL_BUILD, new NullProgressMonitor());
		ICompilationUnit unit = fragment.createCompilationUnit("Test1.java", 
				"package tests;\n" + 
				"import org.eclipse.jdt.annotation.*;\n" + 
				"\n" + 
				"import libs.Lib1;\n" + 
				"import libs.Lib2;\n" + 
				"import tests.Lib3;\n" + 
				"\n" + 
				"public class Test1 {\n" + 
				"	@NonNull String test1(Lib1<@NonNull String> lib) {\n" + 
				"		return lib.get();\n" + // legacy, prepared for .eea but still not annotated (=> Warning)
				"	}\n" + 
				"	@NonNull String test2(Lib2<@NonNull String> lib) {\n" + 
				"		return lib.get();\n" + // legacy, not prepared for .eea (=> Info)
				"	}\n" + 
				"	@NonNull String test3(Lib3<@NonNull String> lib) {\n" + 
				"		return lib.get();\n" + // not legacy, is from the same project
				"	}\n" + 
				"}\n",
				true, new NullProgressMonitor()).getWorkingCopy(new NullProgressMonitor());
		CompilationUnit reconciled = unit.reconcile(getJLS8(), true, null, new NullProgressMonitor());
		assertProblems(reconciled.getProblems(), new String[] {
				"Pb(980) Unsafe interpretation of method return type as '@NonNull' based on the receiver type 'Lib1<@NonNull String>'. Type 'Lib1<T>' doesn't seem to be designed with null type annotations in mind",
				"Pb(980) Unsafe interpretation of method return type as '@NonNull' based on the receiver type 'Lib2<@NonNull String>'. Type 'Lib2<T>' doesn't seem to be designed with null type annotations in mind",
		}, new int[] { 10, 13 }, new int[] { ProblemSeverities.Warning, ProblemSeverities.Info } );
	}

	public void testBug490343() throws Exception {
		myCreateJavaProject("TestLibs");
		addLibraryWithExternalAnnotations(this.project, "lib1.jar", "annots", new String[] {
				"/UnannotatedLibs/libs/Map.java",
				"package libs;\n" +
				"\n" +
				"interface Comparator<T> {}\n" +
				"interface Comparable<T> {}\n" +
				"\n" +
				"public interface Map<K, V> {\n" +
				"    interface Entry<K, V> {\n" +
				"        K getKey();\n" +
				"\n" +
				"        public static <K extends Comparable<? super K>, V> Comparator<Map.Entry<K, V>> comparingByKey() {\n" +
				"            throw new RuntimeException();\n" +
				"        }\n" +
				"    }\n" +
				"}\n"
		}, null);
		createFileInProject("annots/libs", "Map$Entry.eea",
				"class libs/Map$Entry\n" + 
				"comparingByKey\n" +
				 " <K::Llibs/Comparable<-TK;>;V:Ljava/lang/Object;>()Llibs/Comparator<Llibs/Map$Entry<TK;TV;>;>;\n" +
				 " <K::Llibs/Comparable<-TK;>;V:Ljava/lang/Object;>()L1libs/Comparator<Llibs/Map$Entry<TK;TV;>;>;\n"
		);
		IPackageFragment fragment = this.project.getPackageFragmentRoots()[0].createPackageFragment("tests", true, null);
		ICompilationUnit unit = fragment.createCompilationUnit("Test.java", 
				"package tests;\n" +
				"\n" +
				"import libs.Map;\n" +
				"\n" +
				"public class Test {\n" +
				"    static boolean f() {\n" +
				"        if(Map.Entry.comparingByKey() == null) {\n" +
				"            return false;\n" +
				"        }\n" +
				"        return true;\n" +
				"    }\n" +
				"}\n" +
				"",
				true, new NullProgressMonitor()).getWorkingCopy(new NullProgressMonitor());
		CompilationUnit reconciled = unit.reconcile(getJLS8(), true, null, new NullProgressMonitor());
		IProblem[] problems = reconciled.getProblems();
		assertProblems(problems, new String[] {
				"Pb(149) Dead code"
			}, new int[] { 7 });
	}
	@SuppressWarnings("deprecation")
	public void testBug507256() throws Exception {
		myCreateJavaProject("TestLibs");
		addLibraryWithExternalAnnotations(this.project, "lib1.jar", "annots", new String[] {
				"/UnannotatedLib/libs/Lib1.java",
				"package libs;\n" + 
				"\n" + 
				"public interface Lib1 {\n" + 
				"	void methodWithParamAfterWildcard(Class<?> c, Object s);\n" + 
				"}\n"
			}, null);
		// annotations directly on a wildcard (*, +, -)
		createFileInProject("annots/libs", "Lib1.eea",
				"class libs/Lib1\n" + 
				"\n" + 
				"methodWithParamAfterWildcard\n" + 
				" (Ljava/lang/Class<*>;Ljava/lang/Object;)V\n" + 
				" (L1java/lang/Class<*>;L1java/lang/Object;)V\n" +
				"\n" + 
				"\n");
		IPackageFragment fragment = this.project.getPackageFragmentRoots()[0].createPackageFragment("tests", true, null);
		ICompilationUnit unit = fragment.createCompilationUnit("Test1.java", 
				"package tests;\n" + 
				"import libs.Lib1;\n" + 
				"\n" + 
				"public class Test1 {\n" + 
				"	void test1(Lib1 lib) {\n" + 
				"		 lib.methodWithParamAfterWildcard(Object.class, null);\n" + // error, second param must not be null
				"	}\n" + 
				"}\n",
				true, new NullProgressMonitor()).getWorkingCopy(new NullProgressMonitor());
		CompilationUnit reconciled = unit.reconcile(AST.JLS8, true, null, new NullProgressMonitor());
		IProblem[] problems = reconciled.getProblems();
		assertProblems(problems, new String[] {
				"Pb(910) Null type mismatch: required '@NonNull Object' but the provided value is null"
			}, new int[] { 6 });
	}
	
	/** assert that per-workspace configuration re rt.jar is overridden by per-project configuration for JRE container. */
	public void testBug465296() throws Exception {
		// library type used: j.u.Map (no need for JRE8)
		Hashtable options = JavaCore.getOptions();
		TestContainerInitializer.RT_JAR_ANNOTATION_PATH = "/MissingPrj/missing";
		try {
			setupJavaProject("Test2");
			this.project.getProject().build(IncrementalProjectBuilder.FULL_BUILD, null);
			IMarker[] markers = this.project.getProject().findMarkers(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE);
			assertNoMarkers(markers);
		} finally {
			// project using a full JRE container initializes global options to 1.8 -- must reset now:
			JavaCore.setOptions(options);
			TestContainerInitializer.RT_JAR_ANNOTATION_PATH = null;
		}
	}

	/**
	 * Assert that external annotations configured for project A's library are considered also while compiling dependent project B.
	 * Full build.
	 */
	public void testBug509715fullBuild() throws Exception {
		Hashtable options = JavaCore.getOptions();
		try {
			setupJavaProject("Bug509715ProjA");
			this.project.getProject().build(IncrementalProjectBuilder.FULL_BUILD, null);
	
			setupJavaProject("Bug509715ProjB");
			// local eea should not shadow those configured in ProjA:
			addProjectDependencyWithExternalAnnotations(this.project, "/Bug509715ProjA", "/Bug509715ProjB/eea", null);
			
			this.project.getProject().build(IncrementalProjectBuilder.FULL_BUILD, null);
			IMarker[] markers = this.project.getProject().findMarkers(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE);
			assertNoMarkers(markers);
		} finally {
			deleteProject("Bug509715ProjA");
			deleteProject("Bug509715ProjB");
			JavaCore.setOptions(options);
		}
	}

	/**
	 * Assert that external annotations configured for project A's library are considered also while compiling dependent project B. 
	 * Reconcile.
	 */
	@SuppressWarnings("deprecation")
	public void testBug509715reconcile() throws Exception {
		try {
			setupJavaProject("Bug509715ProjA");
			this.project.getProject().build(IncrementalProjectBuilder.FULL_BUILD, null);
	
			setupJavaProject("Bug509715ProjB");
			// local eea should not shadow those configured in ProjA:
			addProjectDependencyWithExternalAnnotations(this.project, "/Bug509715ProjA", "/Bug509715ProjB/eea", null);

			IPackageFragment fragment = this.root.getPackageFragment("b");
			ICompilationUnit unit = fragment.getCompilationUnit("User.java").getWorkingCopy(new NullProgressMonitor());
			CompilationUnit reconciled = unit.reconcile(AST.JLS8, true, null, new NullProgressMonitor());
			IProblem[] problems = reconciled.getProblems();
			assertNoProblems(problems);
		} finally {
			deleteProject("Bug509715ProjA");
			deleteProject("Bug509715ProjB");
		}
	}

	@SuppressWarnings("deprecation")
	public void testBug500024dir() throws CoreException, IOException {
		try {
			String projectName = "Bug500024";
			setupJavaProject(projectName, true, true);
			
			addEeaToVariableEntry("JCL18_FULL", "/"+projectName+"/annots");
			IPackageFragment fragment = this.project.getPackageFragmentRoots()[0].createPackageFragment("test1", true, null);
			ICompilationUnit unit = fragment.getCompilationUnit("Test1.java").getWorkingCopy(new NullProgressMonitor());
			CompilationUnit reconciled = unit.reconcile(AST.JLS8, true, null, new NullProgressMonitor());
			IProblem[] problems = reconciled.getProblems();
			assertProblems(problems, 
					new String[] {
						"Pb(980) Unsafe interpretation of method return type as \'@NonNull\' based on the receiver type \'@NonNull Map<@NonNull String,@NonNull Test1>\'. Type \'Map<K,V>\' doesn\'t seem to be designed with null type annotations in mind",
						"Pb(149) Dead code", 
						"Pb(915) Illegal redefinition of parameter other, inherited method from Object declares this parameter as @Nullable" 
					},
					new int[] {9, 11, 13},
					new int[] { ProblemSeverities.Warning, ProblemSeverities.Warning, ProblemSeverities.Error });
			
			this.project.getProject().build(IncrementalProjectBuilder.FULL_BUILD, null);
			IMarker[] markers = this.project.getProject().findMarkers(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE);
			sortMarkers(markers);
			assertMarkers("Markers after full build", 
					"Dead code\n" +
					"Illegal redefinition of parameter other, inherited method from Object declares this parameter as @Nullable\n" +
					"Unsafe interpretation of method return type as \'@NonNull\' based on the receiver type \'@NonNull Map<@NonNull String,@NonNull Test1>\'. Type \'Map<K,V>\' doesn\'t seem to be designed with null type annotations in mind",
					markers);
			int[] severities = new int[] { IMarker.SEVERITY_WARNING, IMarker.SEVERITY_ERROR, IMarker.SEVERITY_WARNING };
			for (int i = 0; i < markers.length; i++) {
				IMarker marker = markers[i];
				assertEquals("severity of "+marker.getAttribute(IMarker.MESSAGE),
						severities[i], marker.getAttribute(IMarker.SEVERITY));
			}
		} finally {
			deleteProject("Bug500024");
		}
	}

	@SuppressWarnings("deprecation")
	public void testBug500024jar() throws CoreException, IOException {
		try {
			String projectName = "Bug500024";
			setupJavaProject(projectName, true, true);
			
			String projectLoc = this.project.getResource().getLocation().toString();
			String annotsZip = "/annots.zip";
			String zipFile = projectLoc + annotsZip;
			String tmpFolder = projectLoc+"/annots";
			Util.zip(new File(tmpFolder), zipFile);
			Util.delete(tmpFolder);
			this.project.getProject().refreshLocal(1, new NullProgressMonitor());
			addEeaToVariableEntry("JCL18_FULL", "/"+projectName+annotsZip);
			IPackageFragment fragment = this.project.getPackageFragmentRoots()[0].createPackageFragment("test1", true, null);
			ICompilationUnit unit = fragment.getCompilationUnit("Test1.java").getWorkingCopy(new NullProgressMonitor());
			
			CompilationUnit reconciled = unit.reconcile(AST.JLS8, true, null, new NullProgressMonitor());
			IProblem[] problems = reconciled.getProblems();
			assertProblems(problems, 
					new String[] {
						"Pb(980) Unsafe interpretation of method return type as \'@NonNull\' based on the receiver type \'@NonNull Map<@NonNull String,@NonNull Test1>\'. Type \'Map<K,V>\' doesn\'t seem to be designed with null type annotations in mind",
						"Pb(149) Dead code", 
						"Pb(915) Illegal redefinition of parameter other, inherited method from Object declares this parameter as @Nullable" 
					},
					new int[] {9, 11, 13},
					new int[] { ProblemSeverities.Warning, ProblemSeverities.Warning, ProblemSeverities.Error });
			
			this.project.getProject().build(IncrementalProjectBuilder.FULL_BUILD, null);
			IMarker[] markers = this.project.getProject().findMarkers(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE);
			sortMarkers(markers);
			assertMarkers("Markers after full build", 
					"Dead code\n" +
					"Illegal redefinition of parameter other, inherited method from Object declares this parameter as @Nullable\n" +
					"Unsafe interpretation of method return type as \'@NonNull\' based on the receiver type \'@NonNull Map<@NonNull String,@NonNull Test1>\'. Type \'Map<K,V>\' doesn\'t seem to be designed with null type annotations in mind",
					markers);
			int[] severities = new int[] { IMarker.SEVERITY_WARNING, IMarker.SEVERITY_ERROR, IMarker.SEVERITY_WARNING };
			for (int i = 0; i < markers.length; i++) {
				IMarker marker = markers[i];
				assertEquals("severity of "+marker.getAttribute(IMarker.MESSAGE),
						severities[i], marker.getAttribute(IMarker.SEVERITY));
			}
		} finally {
			deleteProject("Bug500024");
		}
	}

		@SuppressWarnings("deprecation")
	public void testBug508955() throws CoreException, IOException {
		myCreateJavaProject("TestLibs");
		addLibraryWithExternalAnnotations(this.project, "lib1.jar", "annots",
			new String[] {
				"/UnannotatedLib/libs/Collectors.java",
				"package libs;\n" + 
				"public interface Collectors {\n" + 
				"    Collector<CharSequence, ?, String> joining(CharSequence delimiter);\n" + 
				"\n" + 
				"}\n",
				"/UnannotatedLib/libs/Stream.java",
				"package libs;\n" +
				"public interface Stream<T> {\n" +
				"    <R, A> R collect(Collector<? super T, A, R> collector);\n" + 
				"}\n",
				"/UnannotatedLib/libs/List.java",
				"package libs;\n" +
				"public interface List<T> extends java.util.Collection<T> {\n" +
				"	Stream<T> stream();\n" +
				"}\n",
				"Collector.java",
				"package libs;\n" +
				"public interface Collector<T, A, R> { }\n"
			}, null);
		createFileInProject("annots/libs", "Collectors.eea",
				"class libs/Collectors\n" + 
				"\n" + 
				"joining\n" + 
				" (Ljava/lang/CharSequence;)Llibs/Collector<Ljava/lang/CharSequence;*Ljava/lang/String;>;\n" + 
				" (Ljava/lang/CharSequence;)L1libs/Collector<L1java/lang/CharSequence;*1L1java/lang/String;>;\n" +
				"\n" + 
				"\n");
		createFile(this.project.getElementName()+"/annots/libs/Stream.eea",
				"class libs/Stream\n" + 
				"\n");
		IPackageFragment fragment = this.project.getPackageFragmentRoots()[0].createPackageFragment("tests", true, null);
		ICompilationUnit unit = fragment.createCompilationUnit("Example.java", 
				"package tests;\n" + 
				"import libs.*;\n" +
				"@org.eclipse.jdt.annotation.NonNullByDefault\n" + 
				"public class Example {\n" + 
				"	public String func(List<String> list, Collectors collectors){\n" + 
				"		return list.stream().collect(collectors.joining(\",\"));\n" + 
				"	}\n" + 
				"}\n",
				true, new NullProgressMonitor()).getWorkingCopy(new NullProgressMonitor());
		CompilationUnit reconciled = unit.reconcile(AST.JLS8, true, null, new NullProgressMonitor());
		IProblem[] problems = reconciled.getProblems();
		assertNoProblems(problems);
	}

	@SuppressWarnings("deprecation")
	public void testBug508955b() throws CoreException, IOException {
		myCreateJavaProject("TestLibs");
		addLibraryWithExternalAnnotations(this.project, "lib1.jar", "annots",
			new String[] {
				"/UnannotatedLib/libs/Collectors.java",
				"package libs;\n" + 
				"public interface Collectors {\n" + 
				"    Collector<CharSequence, ? extends Object, String> joining(CharSequence delimiter);\n" + 
				"\n" + 
				"}\n",
				"/UnannotatedLib/libs/Stream.java",
				"package libs;\n" +
				"public interface Stream<T> {\n" +
				"    <R, A> R collect(Collector<? super T, A, R> collector);\n" + 
				"}\n",
				"/UnannotatedLib/libs/List.java",
				"package libs;\n" +
				"public interface List<T> extends java.util.Collection<T> {\n" +
				"	Stream<T> stream();\n" +
				"}\n",
				"Collector.java",
				"package libs;\n" +
				"public interface Collector<T, A, R> { }\n"
			}, null);
		createFileInProject("annots/libs", "Collectors.eea",
				"class libs/Collectors\n" + 
				"\n" + 
				"joining\n" + 
				" (Ljava/lang/CharSequence;)Llibs/Collector<Ljava/lang/CharSequence;+Ljava/lang/Object;Ljava/lang/String;>;\n" + 
				" (Ljava/lang/CharSequence;)L1libs/Collector<L1java/lang/CharSequence;+1L1java/lang/Object;L1java/lang/String;>;\n" +
				"\n" + 
				"\n");
		createFile(this.project.getElementName()+"/annots/libs/Stream.eea",
				"class libs/Stream\n" + 
				"\n");
		IPackageFragment fragment = this.project.getPackageFragmentRoots()[0].createPackageFragment("tests", true, null);
		ICompilationUnit unit = fragment.createCompilationUnit("Example.java", 
				"package tests;\n" + 
				"import libs.*;\n" +
				"@org.eclipse.jdt.annotation.NonNullByDefault\n" + 
				"public class Example {\n" + 
				"	public String func(List<String> list, Collectors collectors){\n" + 
				"		return list.stream().collect(collectors.joining(\",\"));\n" + 
				"	}\n" + 
				"}\n",
				true, new NullProgressMonitor()).getWorkingCopy(new NullProgressMonitor());
		CompilationUnit reconciled = unit.reconcile(AST.JLS8, true, null, new NullProgressMonitor());
		IProblem[] problems = reconciled.getProblems();
		assertNoProblems(problems);
	}
	/** .eaa present, but null annotations not on classpath */
	public void testBug525649() throws Exception {
		setupJavaProject("Bug525649", false, false);
		addLibraryWithExternalAnnotations(this.project, "lib1.jar", "annots", new String[] {
				"/UnannotatedLib/libs/MyMap.java",
				MY_MAP_CONTENT
			}, null);
		this.project.getProject().build(IncrementalProjectBuilder.FULL_BUILD, null);
		IMarker[] markers = this.project.getProject().findMarkers(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE);
		assertNoMarkers(markers);
	}
	public void testBug525715() throws Exception {
		myCreateJavaProject("TestLibs");
		String lib1Content =
				"package libs;\n" + 
				"\n" +
				"public abstract class Lib1<T,U> {\n" +
				"	public abstract Lib1<T,U> take(Lib1<T,U> x, Object y);\n" +
				"}\n";
		addLibraryWithExternalAnnotations(this.project, "lib1.jar", "annots", new String[] {
				"/UnannotatedLib/libs/Lib1.java",
				lib1Content								
			}, null);

		// type check sources:
		IPackageFragment fragment = this.project.getPackageFragmentRoots()[0].createPackageFragment("tests", true, null);
		ICompilationUnit cu = fragment.createCompilationUnit("Test1.java",
				"package tests;\n" + 
				"import org.eclipse.jdt.annotation.*;\n" + 
				"import libs.Lib1;\n" + 
				"\n" + 
				"@NonNullByDefault\n" + 
				"public abstract class Test1 extends Lib1<String,String> {\n" + 
				"	public abstract Lib1<String,String> take(Lib1<String,String> x, Object y);\n" +
				"}\n",
				true, new NullProgressMonitor()).getWorkingCopy(new NullProgressMonitor());
		CompilationUnit reconciled = cu.reconcile(getJSL9(), true, null, new NullProgressMonitor());
		assertProblems(reconciled.getProblems(), new String[] {
				"Pb(916) Illegal redefinition of parameter x, inherited method from Lib1<String,String> does not constrain this parameter",
				"Pb(916) Illegal redefinition of parameter y, inherited method from Lib1<String,String> does not constrain this parameter"
		}, new int[] { 7, 7 });

		// acquire library AST:
		IType type = this.project.findType("libs.Lib1");
		ICompilationUnit libWorkingCopy = type.getClassFile().getWorkingCopy(this.wcOwner, null);
		ASTParser parser = ASTParser.newParser(getJSL9());
		parser.setSource(libWorkingCopy);
		parser.setResolveBindings(true);
		parser.setStatementsRecovery(false);
		parser.setBindingsRecovery(false);
		CompilationUnit unit = (CompilationUnit) parser.createAST(null);
		libWorkingCopy.discardWorkingCopy();
		
		// find type binding:
		int start = lib1Content.indexOf("take");
		ASTNode name = NodeFinder.perform(unit, start, 0);
		assertTrue("should be simple name", name.getNodeType() == ASTNode.SIMPLE_NAME);
		ASTNode method = name.getParent();
		IMethodBinding methodBinding = ((MethodDeclaration)method).resolveBinding();
		
		// find annotation file (not yet existing):
		IFile annotationFile = ExternalAnnotationUtil.getAnnotationFile(this.project, methodBinding.getDeclaringClass(), null);
		assertFalse("file should not exist", annotationFile.exists());
		assertEquals("file path", "/TestLibs/annots/libs/Lib1.eea", annotationFile.getFullPath().toString());

		// annotate:
		String originalSignature = ExternalAnnotationUtil.extractGenericSignature(methodBinding);
		ExternalAnnotationUtil.annotateMember("libs/Lib1", annotationFile,
				"take", 
				originalSignature, 
				"(L1libs/Lib1<TT;TU;>;L1java/lang/Object;)Llibs/Lib1<TT;TU;>;",
				MergeStrategy.OVERWRITE_ANNOTATIONS, null);
		assertTrue("file should exist", annotationFile.exists());

		// check that the error is resolved now:
		reconciled = cu.reconcile(getJSL9(), true, null, new NullProgressMonitor());
		assertNoProblems(reconciled.getProblems());
	}
	public void testBug517275() throws Exception {
		myCreateJavaProject("TestLibs");
		String lib1Content =
				"package libs;\n" + 
				"import java.util.List;\n" +
				"public abstract class Collections {\n" +
				"   public static final <T> List<T> emptyList() {\n" + 
				"		return null;\n" +
				"	}\n" +
				"}\n";
		addLibraryWithExternalAnnotations(this.project, "lib1.jar", "annots", new String[] {
				"/UnannotatedLib/libs/Collections.java",
				lib1Content								
			}, null);
		createFileInProject("annots/libs", "Collections.eea",
				"class libs/Collections\n" + 
				"\n" + 
				"emptyList\n" + 
				" <T:Ljava/lang/Object;>()Ljava/util/List<TT;>;\n" + 
				" <T:Ljava/lang/Object;>()L1java/util/List<T1T;>;\n" + 
				"\n");

		// type check sources:
		IPackageFragment fragment = this.project.getPackageFragmentRoots()[0].createPackageFragment("tests", true, null);
		ICompilationUnit cu = fragment.createCompilationUnit("Test1.java",
				"package tests;\n" +
				"import libs.Collections;\n" + 
				"import java.util.List;\n" + 
				"\n" + 
				"import org.eclipse.jdt.annotation.NonNull;\n" + 
				"import org.eclipse.jdt.annotation.NonNullByDefault;\n" + 
				"\n" + 
				"@NonNullByDefault\n" + 
				"public class Test1 {\n" + 
				"  List<@NonNull String> strings;\n" + 
				"\n" + 
				"  public Test1() {\n" + 
				"    strings = Collections.emptyList();      // <<<<< WARNING\n" + 
				"  }\n" + 
				"}\n",
				true, new NullProgressMonitor()).getWorkingCopy(new NullProgressMonitor());
		CompilationUnit reconciled = cu.reconcile(getJSL9(), true, null, new NullProgressMonitor());
		assertNoProblems(reconciled.getProblems());
	}

	// Bug 522377 - [null] String.format(""...) shows warning
	public void testVargs() throws CoreException, IOException {
		myCreateJavaProject("TestLibs");
		String lib1Content =
				"package libs;\n" + 
				"public abstract class MyString {\n" +
				"   public static String format(String format, Object... args) {\n" + 
				"		return null;\n" +
				"	}\n" +
				"}\n";
		addLibraryWithExternalAnnotations(this.project, "lib1.jar", "annots", new String[] {
				"/UnannotatedLib/libs/MyString.java",
				lib1Content								
			}, null);
		createFileInProject("annots/libs", "MyString.eea",
				"class libs/MyString\n" + 
				"\n" + 
				"format\n" + 
				" (Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;\n" + 
				" (Ljava/lang/String;[Ljava/lang/Object;)L1java/lang/String;\n" + 
				"\n");

		// type check sources:
		IPackageFragment fragment = this.project.getPackageFragmentRoots()[0].createPackageFragment("tests", true, null);
		ICompilationUnit cu = fragment.createCompilationUnit("Test1.java",
				"package tests;\n" +
				"import libs.MyString;\n" + 
				"\n" + 
				"import org.eclipse.jdt.annotation.*;\n" + 
				"\n" + 
				"public class Test1 {\n" + 
				"  public @NonNull String test(int var) {\n" + 
				"    return MyString.format(\"que%03d\", var);\n" + 
				"  }\n" + 
				"}\n",
				true, new NullProgressMonitor()).getWorkingCopy(new NullProgressMonitor());
		CompilationUnit reconciled = cu.reconcile(getJSL9(), true, null, new NullProgressMonitor());
		assertNoProblems(reconciled.getProblems());
	}
}
