Bug 105372 - Builder to emulate classpath per source folder

This draft adds a builder that can be plugged on any Java project to
perform additional checks at build time. This builder uses additional
attributes in the .classpath definition to "restrict" the visibility of
classpath entries to specified source folders, and report an error if a
Java file inside a source folder reference a type that is from a
"restricted" classpath entry.

Change-Id: I9c69fad201c495f5c7b176024601e2463c44216b
Signed-off-by: Mickael Istria <mistria@redhat.com>
diff --git a/bundles/org.eclipse.e4.jdt.scope/.classpath b/bundles/org.eclipse.e4.jdt.scope/.classpath
new file mode 100644
index 0000000..eca7bdb
--- /dev/null
+++ b/bundles/org.eclipse.e4.jdt.scope/.classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
+	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/bundles/org.eclipse.e4.jdt.scope/.project b/bundles/org.eclipse.e4.jdt.scope/.project
new file mode 100644
index 0000000..ed8974f
--- /dev/null
+++ b/bundles/org.eclipse.e4.jdt.scope/.project
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>org.eclipse.e4.jdt.scope</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.ManifestBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.SchemaBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.pde.PluginNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/bundles/org.eclipse.e4.jdt.scope/.settings/org.eclipse.jdt.core.prefs b/bundles/org.eclipse.e4.jdt.scope/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..0c68a61
--- /dev/null
+++ b/bundles/org.eclipse.e4.jdt.scope/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,7 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
+org.eclipse.jdt.core.compiler.compliance=1.8
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.8
diff --git a/bundles/org.eclipse.e4.jdt.scope/META-INF/MANIFEST.MF b/bundles/org.eclipse.e4.jdt.scope/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..87a3dc5
--- /dev/null
+++ b/bundles/org.eclipse.e4.jdt.scope/META-INF/MANIFEST.MF
@@ -0,0 +1,12 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: Scope
+Bundle-SymbolicName: org.eclipse.e4.jdt.scope;singleton:=true
+Bundle-Version: 0.0.1.qualifier
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
+Require-Bundle: org.eclipse.jdt.core;bundle-version="3.12.0",
+ org.eclipse.core.resources;bundle-version="3.11.0",
+ org.eclipse.core.runtime;bundle-version="3.12.0",
+ org.eclipse.ui.editors;bundle-version="3.10.0"
+Export-Package: org.eclipse.e4.jdt.scope
+Bundle-Localization: plugin
diff --git a/bundles/org.eclipse.e4.jdt.scope/build.properties b/bundles/org.eclipse.e4.jdt.scope/build.properties
new file mode 100644
index 0000000..e9863e2
--- /dev/null
+++ b/bundles/org.eclipse.e4.jdt.scope/build.properties
@@ -0,0 +1,5 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .,\
+               plugin.xml
diff --git a/bundles/org.eclipse.e4.jdt.scope/plugin.properties b/bundles/org.eclipse.e4.jdt.scope/plugin.properties
new file mode 100644
index 0000000..773552c
--- /dev/null
+++ b/bundles/org.eclipse.e4.jdt.scope/plugin.properties
@@ -0,0 +1 @@
+scopeBuilder.label=Dependency Scope Validator
\ No newline at end of file
diff --git a/bundles/org.eclipse.e4.jdt.scope/plugin.xml b/bundles/org.eclipse.e4.jdt.scope/plugin.xml
new file mode 100644
index 0000000..1904d8a
--- /dev/null
+++ b/bundles/org.eclipse.e4.jdt.scope/plugin.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?eclipse version="3.4"?>
+<plugin>
+   <extension
+         id="scopeBuilder"
+         name="%scopeBuilder.label"
+         point="org.eclipse.core.resources.builders">
+      <builder>
+         <run
+               class="org.eclipse.e4.jdt.scope.ScopeValidator">
+         </run>
+      </builder>
+   </extension>
+   <extension
+         id="scope"
+         point="org.eclipse.core.resources.markers">
+      <super
+            type="org.eclipse.jdt.core.buildpath_problem">
+      </super>
+   </extension>
+
+</plugin>
diff --git a/bundles/org.eclipse.e4.jdt.scope/pom.xml b/bundles/org.eclipse.e4.jdt.scope/pom.xml
new file mode 100644
index 0000000..b50cea5
--- /dev/null
+++ b/bundles/org.eclipse.e4.jdt.scope/pom.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+ * Copyright (c) 2016 Red Hat Inc., and others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Mickael Istria (Red Hat Inc.) - initial API and implementation
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.eclipse.e4.ui</groupId>
+    <artifactId>e4-ui-aggregator</artifactId>
+    <version>0.17.0-SNAPSHOT</version>
+    <relativePath>../../</relativePath>
+  </parent>
+
+  <groupId>org.eclipse.e4</groupId>
+  <artifactId>org.eclipse.e4.jdt.scope</artifactId>
+  <version>0.2.0-SNAPSHOT</version>
+  <packaging>eclipse-plugin</packaging>
+  
+</project>
diff --git a/bundles/org.eclipse.e4.jdt.scope/src/org/eclipse/e4/jdt/scope/ScopeValidator.java b/bundles/org.eclipse.e4.jdt.scope/src/org/eclipse/e4/jdt/scope/ScopeValidator.java
new file mode 100644
index 0000000..b8b0284
--- /dev/null
+++ b/bundles/org.eclipse.e4.jdt.scope/src/org/eclipse/e4/jdt/scope/ScopeValidator.java
@@ -0,0 +1,258 @@
+/*******************************************************************************
+ * Copyright (C) 2016, Red Hat Inc.
+ *
+ * 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:
+ *   Mickael Istria (Red Hat Inc.) - initial implementation
+ *******************************************************************************/
+package org.eclipse.e4.jdt.scope;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Queue;
+import java.util.Set;
+
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IResourceDelta;
+import org.eclipse.core.resources.IncrementalProjectBuilder;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jdt.core.IClasspathAttribute;
+import org.eclipse.jdt.core.IClasspathEntry;
+import org.eclipse.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.core.IImportDeclaration;
+import org.eclipse.jdt.core.IJavaElement;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.IPackageFragmentRoot;
+import org.eclipse.jdt.core.IParent;
+import org.eclipse.jdt.core.IType;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jdt.core.search.SearchEngine;
+import org.eclipse.jdt.core.search.SearchMatch;
+import org.eclipse.jdt.core.search.SearchParticipant;
+import org.eclipse.jdt.core.search.SearchPattern;
+import org.eclipse.jdt.core.search.SearchRequestor;
+import org.eclipse.jdt.core.search.TypeDeclarationMatch;
+import org.eclipse.jdt.core.search.TypeReferenceMatch;
+import org.eclipse.jdt.internal.core.search.matching.TypeReferencePattern;
+
+/**
+ * This builder can be added on a Java project to implement the concept of scopes
+ * and put error markers in case a class access a classpath entry that is not available
+ * in its scope.
+ * The scope are defined as {@link IClasspathAttribute}, using SCOPE_ATTRIBUTE and SCOPE_TAG_ATTRIBUTE.
+ * @author mistria
+ *
+ */
+public class ScopeValidator extends IncrementalProjectBuilder {
+
+	private final class CreateMarkerForWrongScopeReferenceRequestor extends SearchRequestor {
+		private ICompilationUnit compilationUnit;
+		private Set<IClasspathEntry> relevantEntries;
+
+		public CreateMarkerForWrongScopeReferenceRequestor(ICompilationUnit cu, Set<IClasspathEntry> relevantEntries) {
+			this.compilationUnit = cu;
+			this.relevantEntries = relevantEntries;
+		}
+
+		@Override
+		public void acceptSearchMatch(SearchMatch match) throws CoreException {
+			IType resolvedType = null;
+			if (match instanceof TypeReferenceMatch) {
+				TypeReferenceMatch typeRefMatch = (TypeReferenceMatch) match;
+				String type = null;
+				if (typeRefMatch.getElement() instanceof IImportDeclaration) {
+					type = ((IImportDeclaration)match.getElement()).getElementName().toString();
+				} else {
+					char[] substring = new char[match.getLength()];
+					char[] content = match.getParticipant().getDocument(match.getResource().getFullPath().toString()).getCharContents();
+					System.arraycopy(content, match.getOffset(), substring, 0, match.getLength());
+					type = new String(substring).trim();
+				}
+				resolvedType = this.compilationUnit.getJavaProject().findType(type);
+			} else if (match instanceof TypeDeclarationMatch) {
+				TypeDeclarationMatch typeDeclationMatch = (TypeDeclarationMatch)match;
+				resolvedType = (IType)typeDeclationMatch.getElement();
+			}
+			if (resolvedType == null) {
+				// unknown type. Skipping?
+				return;
+			}
+			IPackageFragmentRoot resolvedTypePkgRoot = (IPackageFragmentRoot)resolvedType.getPackageFragment().getParent();
+			if (relevantEntries.contains(resolvedTypePkgRoot.getResolvedClasspathEntry())) {
+				return;
+			}
+			IMarker marker = this.compilationUnit.getResource().createMarker(MARKER_TYPE);
+			marker.setAttribute(IMarker.MESSAGE, "Type not in scope - " + resolvedType.getFullyQualifiedName());
+			marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
+			marker.setAttribute(IMarker.CHAR_START, match.getOffset());
+			marker.setAttribute(IMarker.CHAR_END, match.getOffset() + match.getLength());
+			marker.setAttribute(IMarker.LINE_NUMBER, 1);
+		}
+	}
+
+	private static final String BUNDLE_ID = "org.eclipse.e4.jdt.scope";
+	public static final String BUILDER_ID = BUNDLE_ID + ".scopeBuilder";
+	public static final String MARKER_TYPE = BUNDLE_ID + ".scope";
+	/**
+	 * This key is for a classpath entry attribute that is meant to be attached
+	 * to source folders to associate them a scope tag.
+	 */
+	public static final String SCOPE_TAG_ATTRIBUTE = "scopeTag"; //$NON-NLS-1$
+	/**
+	 * This key is for a classpath entry attribute that is used by the {@link ScopeValidator}
+	 * to identify to which source folders/scopes this entry is accessible.
+	 * The value of the attribute is meant to be a list of source folders or scope tags (see
+	 * SCOPE_TAG_ATTRIBUTE) that can access the content of the classpath entry.
+	 * If a classpath entry doesn't set it, it's interpreted as being accessible from
+	 * any source folder.
+	 */
+	public static final String SCOPE_ATTRIBUTE = "scope"; //$NON-NLS-1$
+	
+	public ScopeValidator() {
+		// TODO Auto-generated constructor stub
+	}
+
+	@Override
+	protected IProject[] build(int kind, Map<String, String> args, IProgressMonitor monitor) throws CoreException {
+		final IJavaProject javaProject = (IJavaProject) getProject().getNature(JavaCore.NATURE_ID);
+		
+		if (!projectUseScope(javaProject)) {
+			return new IProject[0]; 
+		}
+		Map<IFolder, Set<IClasspathEntry>> classpathForSrcFolder = buildScopeModel(javaProject);
+		
+		Set<ICompilationUnit> units = new HashSet<ICompilationUnit>();
+		if (getDelta(getProject()) != null && getDelta(getProject()).getResource() != javaProject.getResource()) {
+			getDelta(getProject()).accept(delta -> {
+				if (delta.getKind() == IResourceDelta.CHANGED) {
+					ICompilationUnit cu = delta.getResource().getAdapter(ICompilationUnit.class);
+					units.add(cu);
+				}
+				return false;
+			});
+		} else /* build whole project */ {
+			Queue<IJavaElement> toProcess = new LinkedList<>();
+			toProcess.addAll(Arrays.asList(javaProject.getPackageFragments()));
+			while (!toProcess.isEmpty()) {
+				IJavaElement current = toProcess.poll();
+				if (current.getElementType() == IJavaElement.COMPILATION_UNIT) {
+					units.add((ICompilationUnit)current);
+				} else if (current instanceof IParent) {
+					toProcess.addAll(Arrays.asList(((IParent)current).getChildren()));
+				}
+			}
+		}
+
+		final IClasspathEntry[] resolvedClasspath = javaProject.getResolvedClasspath(true);
+		final SearchEngine searchEngine = new SearchEngine(units.toArray(new ICompilationUnit[units.size()]));
+		SearchPattern pattern = new TypeReferencePattern(null, null, SearchPattern.R_PATTERN_MATCH);
+		for (ICompilationUnit cu : units) {
+			IResource resource = cu.getResource();
+			resource.deleteMarkers(MARKER_TYPE, true, IResource.DEPTH_INFINITE);
+			Set<IClasspathEntry> relevantEntries = null;
+			for (Entry<IFolder, Set<IClasspathEntry>> entry : classpathForSrcFolder.entrySet()) {
+				if (entry.getKey().getFullPath().isPrefixOf(resource.getFullPath())) {
+					relevantEntries = entry.getValue();
+				}
+			}
+			boolean hasScopeRestrictions = relevantEntries.size() != resolvedClasspath.length; 
+			if (hasScopeRestrictions) {
+				SearchRequestor requestor = new CreateMarkerForWrongScopeReferenceRequestor(cu, relevantEntries);
+				
+				/* This search detects the text area causing the error and place markers underlying it
+				 but it misses some references and requires some parsing */
+				searchEngine.search(pattern,
+						new SearchParticipant[] { SearchEngine.getDefaultSearchParticipant() },
+						SearchEngine.createJavaSearchScope(new IJavaElement[] { cu }),
+						requestor, monitor);
+				
+				/* This search is more exhaustive (checks all types) but doesn't assign ranges on markers
+				 so they appear on the file and on 1st line, without underlying the area in error */
+				//searchEngine.searchDeclarationsOfReferencedTypes(cu, requestor, monitor);
+			}
+		}
+		return new IProject[] { getProject() };
+	}
+
+	private Map<IFolder, Set<IClasspathEntry>> buildScopeModel(IJavaProject javaProject) throws JavaModelException {
+		final Map<IFolder, Set<IClasspathEntry>> classpathForSrcFolder= new HashMap<>();
+		final IClasspathEntry[] resolvedClasspath = javaProject.getResolvedClasspath(true);
+		final Map<String, Set<IFolder>> scopeTagToSrc = new HashMap<>();
+		for (IClasspathEntry entry : resolvedClasspath) {
+			if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
+				IFolder sourceFolder = ResourcesPlugin.getWorkspace().getRoot().getFolder(entry.getPath());
+				classpathForSrcFolder.put(sourceFolder, new HashSet<>());
+				String scopeTag = getAttribute(entry, SCOPE_TAG_ATTRIBUTE);
+				if (scopeTag != null) {
+					if (!scopeTagToSrc.containsKey(scopeTag)) {
+						scopeTagToSrc.put(scopeTag, new HashSet<>());
+					}
+					scopeTagToSrc.get(scopeTag).add(sourceFolder);
+				}
+			}
+		}
+		for (IClasspathEntry entry : resolvedClasspath) {
+			boolean isScopedCPEntry = false;
+			String scopeValue = getAttribute(entry, SCOPE_ATTRIBUTE);
+			if (scopeValue != null) {
+				String[] foldersOrTags = scopeValue.split(","); //$NON-NLS-1$
+				isScopedCPEntry = true;
+				for (String folderOrTag : foldersOrTags) {
+					IFolder folder = getProject().getFolder(folderOrTag);
+					if (folder.exists() && scopeTagToSrc.containsKey(folderOrTag)) {
+						// TODO: log ambiguous behavior, or put a marker
+					}
+					if (folder.exists()) {
+						classpathForSrcFolder.get(folder).add(entry);
+					}
+					if (scopeTagToSrc.containsKey(folderOrTag)) {
+						for (IFolder sourceFolder : scopeTagToSrc.get(folderOrTag)) {
+							classpathForSrcFolder.get(sourceFolder).add(entry);
+						}
+					}
+				}
+			}
+			if (!isScopedCPEntry) {
+				for (Entry<IFolder, Set<IClasspathEntry>> scope : classpathForSrcFolder.entrySet()) {
+					scope.getValue().add(entry);
+				}
+			}
+		}
+		return classpathForSrcFolder;
+	}
+
+	private boolean projectUseScope(final IJavaProject javaProject) throws JavaModelException {
+		final IClasspathEntry[] resolvedClasspath = javaProject.getResolvedClasspath(true);
+		for (IClasspathEntry entry : resolvedClasspath) {
+			String scopeValue = getAttribute(entry, SCOPE_ATTRIBUTE);
+			if (scopeValue != null) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	private String getAttribute(IClasspathEntry entry, String attributeKey) {
+		for (IClasspathAttribute att : entry.getExtraAttributes()) {
+			if (att.getName().equals(attributeKey)) {
+				return att.getValue();
+			}
+		}
+		return null;
+	}
+
+}
diff --git a/pom.xml b/pom.xml
index 352c268..55bb67d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -49,5 +49,9 @@
 
     <module>bundles/org.eclipse.e4.ui.macros</module>
     <module>bundles/org.eclipse.e4.ui.macros.jdt</module>
+
+    <module>bundles/org.eclipse.e4.jdt.scope</module>
+    <module>tests/org.eclipse.e4.jdt.scope.tests</module>
   </modules>
 </project>
+
diff --git a/tests/org.eclipse.e4.jdt.scope.tests/.classpath b/tests/org.eclipse.e4.jdt.scope.tests/.classpath
new file mode 100644
index 0000000..eca7bdb
--- /dev/null
+++ b/tests/org.eclipse.e4.jdt.scope.tests/.classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
+	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/tests/org.eclipse.e4.jdt.scope.tests/.project b/tests/org.eclipse.e4.jdt.scope.tests/.project
new file mode 100644
index 0000000..b256530
--- /dev/null
+++ b/tests/org.eclipse.e4.jdt.scope.tests/.project
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>org.eclipse.e4.jdt.scope.tests</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.ManifestBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.SchemaBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.pde.PluginNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/tests/org.eclipse.e4.jdt.scope.tests/.settings/org.eclipse.jdt.core.prefs b/tests/org.eclipse.e4.jdt.scope.tests/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..0c68a61
--- /dev/null
+++ b/tests/org.eclipse.e4.jdt.scope.tests/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,7 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
+org.eclipse.jdt.core.compiler.compliance=1.8
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.8
diff --git a/tests/org.eclipse.e4.jdt.scope.tests/META-INF/MANIFEST.MF b/tests/org.eclipse.e4.jdt.scope.tests/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..ed66d96
--- /dev/null
+++ b/tests/org.eclipse.e4.jdt.scope.tests/META-INF/MANIFEST.MF
@@ -0,0 +1,15 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: Tests & Example for Scope mechanism
+Bundle-SymbolicName: org.eclipse.e4.jdt.scope.tests
+Bundle-Version: 0.0.1.qualifier
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
+Require-Bundle: org.junit,
+ org.eclipse.ui.ide;bundle-version="3.12.0",
+ org.eclipse.jdt.core;bundle-version="3.12.0",
+ org.eclipse.jdt.junit;bundle-version="3.9.0",
+ org.eclipse.jdt.junit.core;bundle-version="3.8.0",
+ org.eclipse.jdt.junit.runtime;bundle-version="3.4.500",
+ org.eclipse.core.resources;bundle-version="3.11.0",
+ org.eclipse.core.runtime;bundle-version="3.12.0",
+ org.eclipse.e4.jdt.scope
diff --git a/tests/org.eclipse.e4.jdt.scope.tests/build.properties b/tests/org.eclipse.e4.jdt.scope.tests/build.properties
new file mode 100644
index 0000000..a0408e0
--- /dev/null
+++ b/tests/org.eclipse.e4.jdt.scope.tests/build.properties
@@ -0,0 +1,5 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               testScope/,\
+               .
diff --git a/tests/org.eclipse.e4.jdt.scope.tests/pom.xml b/tests/org.eclipse.e4.jdt.scope.tests/pom.xml
new file mode 100644
index 0000000..3ab54b4
--- /dev/null
+++ b/tests/org.eclipse.e4.jdt.scope.tests/pom.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+ * Copyright (c) 2016 Red Hat Inc., and others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Mickael Istria (Red Hat Inc.) - initial API and implementation
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.eclipse.e4.ui</groupId>
+    <artifactId>e4-ui-aggregator</artifactId>
+    <version>0.17.0-SNAPSHOT</version>
+    <relativePath>../../</relativePath>
+  </parent>
+
+  <groupId>org.eclipse.e4</groupId>
+  <artifactId>org.eclipse.e4.jdt.scope.tests</artifactId>
+  <version>0.0.1-SNAPSHOT</version>
+  <packaging>eclipse-test-plugin</packaging>
+  
+  <properties>
+  	<testSuite>${project.artifactId}</testSuite>
+  	<testClass>org.eclipse.e4.jdt.scope.tests.AllScopeTests</testClass>
+  </properties>
+  
+</project>
diff --git a/tests/org.eclipse.e4.jdt.scope.tests/src/org/eclipse/e4/jdt/scope/tests/AllScopeTests.java b/tests/org.eclipse.e4.jdt.scope.tests/src/org/eclipse/e4/jdt/scope/tests/AllScopeTests.java
new file mode 100644
index 0000000..3eb12e1
--- /dev/null
+++ b/tests/org.eclipse.e4.jdt.scope.tests/src/org/eclipse/e4/jdt/scope/tests/AllScopeTests.java
@@ -0,0 +1,74 @@
+/*******************************************************************************
+ * Copyright (C) 2016, Red Hat Inc.
+ *
+ * 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:
+ *   Mickael Istria (Red Hat Inc.) - initial implementation
+ *******************************************************************************/
+package org.eclipse.e4.jdt.scope.tests;
+
+import java.io.File;
+import java.net.URL;
+
+import org.eclipse.core.resources.ICommand;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IProjectDescription;
+import org.eclipse.core.resources.IncrementalProjectBuilder;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.FileLocator;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.e4.jdt.scope.ScopeValidator;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+import org.junit.runners.Suite.SuiteClasses;
+
+@RunWith(Suite.class)
+@SuiteClasses({ RestrictedScopeTest.class, VisibleScopeForFolderTest.class })
+public class AllScopeTests {
+	
+	public static final String TEST_PROJECT_NAME = "testScope"; //$NON-NLS-1$
+	
+	@BeforeClass
+	public static void provisionAndBuildTestProject() throws Exception {
+		URL url = FileLocator.toFileURL(AllScopeTests.class.getClassLoader().getResource("/" + TEST_PROJECT_NAME));
+		IProjectDescription description = ResourcesPlugin.getWorkspace().loadProjectDescription(new Path(url.getFile() + File.separator + IProjectDescription.DESCRIPTION_FILE_NAME));
+		description.setLocation(new Path(url.getFile()));
+		IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(description.getName());
+		project.create(description, null);
+		project.open(null);
+		//
+		IProjectDescription desc = project.getDescription();
+		ICommand[] commands = desc.getBuildSpec();
+		boolean builderAlreadySetup = false;
+		for (int i = 0; i < commands.length; ++i) {
+			if (!builderAlreadySetup && commands[i].getBuilderName().equals(ScopeValidator.BUILDER_ID)) {
+				builderAlreadySetup = true;
+			}
+		}
+		if (!builderAlreadySetup) {
+			ICommand command = desc.newCommand();
+			command.setBuilderName(ScopeValidator.BUILDER_ID);
+			ICommand[] nc = new ICommand[commands.length + 1];
+			// Add it before other builders.
+			System.arraycopy(commands, 0, nc, 0, commands.length);
+			nc[nc.length - 1] = command;
+			desc.setBuildSpec(nc);
+			project.setDescription(desc, null);
+		}
+		
+		project.build(IncrementalProjectBuilder.FULL_BUILD, new NullProgressMonitor());
+	}
+	
+	@AfterClass
+	public static void removeProject() throws Exception {
+		ResourcesPlugin.getWorkspace().getRoot().getProject(TEST_PROJECT_NAME).delete(false, false, new NullProgressMonitor());
+	}
+
+}
diff --git a/tests/org.eclipse.e4.jdt.scope.tests/src/org/eclipse/e4/jdt/scope/tests/RestrictedScopeTest.java b/tests/org.eclipse.e4.jdt.scope.tests/src/org/eclipse/e4/jdt/scope/tests/RestrictedScopeTest.java
new file mode 100644
index 0000000..7cc948d
--- /dev/null
+++ b/tests/org.eclipse.e4.jdt.scope.tests/src/org/eclipse/e4/jdt/scope/tests/RestrictedScopeTest.java
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * Copyright (C) 2016, Red Hat Inc.
+ *
+ * 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:
+ *   Mickael Istria (Red Hat Inc.) - initial implementation
+ *******************************************************************************/
+package org.eclipse.e4.jdt.scope.tests;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.e4.jdt.scope.ScopeValidator;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RestrictedScopeTest {
+	
+	@Parameters(name = "{0}") //$NON-NLS-1$
+	public static Collection<String> data() {
+		return Arrays.asList(new String[] { "KOImport.java", "KOMember.java",  "KOParameter.java", "KOReturnType.java", "KOWildcardImport.java"} );
+	}
+	
+	@Parameter
+	public String fileToCheck;
+
+	@Test
+	public void test() throws Exception {
+		IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(AllScopeTests.TEST_PROJECT_NAME);
+		IFile file = project.getFolder("restrict/testScope").getFile(this.fileToCheck); //$NON-NLS-1$
+		IMarker[] markers = file.findMarkers(ScopeValidator.MARKER_TYPE, true, IResource.DEPTH_INFINITE);
+		Assert.assertNotEquals("At least 1 marker should be found on " + file.getName(), 0, markers.length); //$NON-NLS-1$
+	}
+
+}
diff --git a/tests/org.eclipse.e4.jdt.scope.tests/src/org/eclipse/e4/jdt/scope/tests/VisibleScopeForFolderTest.java b/tests/org.eclipse.e4.jdt.scope.tests/src/org/eclipse/e4/jdt/scope/tests/VisibleScopeForFolderTest.java
new file mode 100644
index 0000000..2442a4e
--- /dev/null
+++ b/tests/org.eclipse.e4.jdt.scope.tests/src/org/eclipse/e4/jdt/scope/tests/VisibleScopeForFolderTest.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * Copyright (C) 2016, Red Hat Inc.
+ *
+ * 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:
+ *   Mickael Istria (Red Hat Inc.) - initial implementation
+ *******************************************************************************/
+package org.eclipse.e4.jdt.scope.tests;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.e4.jdt.scope.ScopeValidator;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class VisibleScopeForFolderTest {
+	
+	@Test
+	public void testPlainFolder() throws Exception {
+		IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(AllScopeTests.TEST_PROJECT_NAME);
+		IFile file = project.getFolder("seeAll/testScope").getFile("OK.java"); //$NON-NLS-1$
+		IMarker[] markers = file.findMarkers(ScopeValidator.MARKER_TYPE, true, IResource.DEPTH_INFINITE);
+		Assert.assertEquals("No scope marker should be found on " + file.getName(), 0, markers.length); //$NON-NLS-1$
+	}
+	
+
+	@Test
+	public void testTaggedFolder() throws Exception {
+		IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(AllScopeTests.TEST_PROJECT_NAME);
+		IFile file = project.getFolder("seeAllTagged/testScope").getFile("OK2.java"); //$NON-NLS-1$
+		IMarker[] markers = file.findMarkers(ScopeValidator.MARKER_TYPE, true, IResource.DEPTH_INFINITE);
+		Assert.assertEquals("No scope marker should be found on " + file.getName(), 0, markers.length); //$NON-NLS-1$
+	}
+
+
+}
diff --git a/tests/org.eclipse.e4.jdt.scope.tests/testScope/.classpath b/tests/org.eclipse.e4.jdt.scope.tests/testScope/.classpath
new file mode 100644
index 0000000..4784c7d
--- /dev/null
+++ b/tests/org.eclipse.e4.jdt.scope.tests/testScope/.classpath
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="seeAll"/>
+	<classpathentry kind="src" path="seeAllTag">
+		<attributes>
+			<attribute name="scopeTag" value="testTag"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="src" path="restrict"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4">
+		<attributes>
+			<attribute name="scope" value="seeAll,testTag"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/tests/org.eclipse.e4.jdt.scope.tests/testScope/.classpath_other b/tests/org.eclipse.e4.jdt.scope.tests/testScope/.classpath_other
new file mode 100644
index 0000000..cf6a673
--- /dev/null
+++ b/tests/org.eclipse.e4.jdt.scope.tests/testScope/.classpath_other
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="seeAll"/>
+	<classpathentry kind="src" path="restrict"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
+	<classpathentry kind="con" path="m2e full">
+		<attributes>
+			<attribute name=scope value=""/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="con" path="m2e-scope-compile"/>
+	<classpathentry kind="con" path="m2e-scope-test"/>
+		<attributes>
+			<attribute name="scope" value="src/test/java"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/tests/org.eclipse.e4.jdt.scope.tests/testScope/.project b/tests/org.eclipse.e4.jdt.scope.tests/testScope/.project
new file mode 100644
index 0000000..e2b7f3a
--- /dev/null
+++ b/tests/org.eclipse.e4.jdt.scope.tests/testScope/.project
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>testScope</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.jboss.tools.playground.scope.scopeBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/tests/org.eclipse.e4.jdt.scope.tests/testScope/.settings/org.eclipse.jdt.core.prefs b/tests/org.eclipse.e4.jdt.scope.tests/testScope/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..3a21537
--- /dev/null
+++ b/tests/org.eclipse.e4.jdt.scope.tests/testScope/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,11 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.8
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.8
diff --git a/tests/org.eclipse.e4.jdt.scope.tests/testScope/bin/testScope/KOImport.class b/tests/org.eclipse.e4.jdt.scope.tests/testScope/bin/testScope/KOImport.class
new file mode 100644
index 0000000..0f40334
--- /dev/null
+++ b/tests/org.eclipse.e4.jdt.scope.tests/testScope/bin/testScope/KOImport.class
Binary files differ
diff --git a/tests/org.eclipse.e4.jdt.scope.tests/testScope/bin/testScope/KOMember.class b/tests/org.eclipse.e4.jdt.scope.tests/testScope/bin/testScope/KOMember.class
new file mode 100644
index 0000000..803390c
--- /dev/null
+++ b/tests/org.eclipse.e4.jdt.scope.tests/testScope/bin/testScope/KOMember.class
Binary files differ
diff --git a/tests/org.eclipse.e4.jdt.scope.tests/testScope/bin/testScope/KOParameter.class b/tests/org.eclipse.e4.jdt.scope.tests/testScope/bin/testScope/KOParameter.class
new file mode 100644
index 0000000..54301a0
--- /dev/null
+++ b/tests/org.eclipse.e4.jdt.scope.tests/testScope/bin/testScope/KOParameter.class
Binary files differ
diff --git a/tests/org.eclipse.e4.jdt.scope.tests/testScope/bin/testScope/KOReturnType.class b/tests/org.eclipse.e4.jdt.scope.tests/testScope/bin/testScope/KOReturnType.class
new file mode 100644
index 0000000..23adf4c
--- /dev/null
+++ b/tests/org.eclipse.e4.jdt.scope.tests/testScope/bin/testScope/KOReturnType.class
Binary files differ
diff --git a/tests/org.eclipse.e4.jdt.scope.tests/testScope/bin/testScope/KOWildcardImport.class b/tests/org.eclipse.e4.jdt.scope.tests/testScope/bin/testScope/KOWildcardImport.class
new file mode 100644
index 0000000..d011746
--- /dev/null
+++ b/tests/org.eclipse.e4.jdt.scope.tests/testScope/bin/testScope/KOWildcardImport.class
Binary files differ
diff --git a/tests/org.eclipse.e4.jdt.scope.tests/testScope/bin/testScope/OK.class b/tests/org.eclipse.e4.jdt.scope.tests/testScope/bin/testScope/OK.class
new file mode 100644
index 0000000..50a2dfe
--- /dev/null
+++ b/tests/org.eclipse.e4.jdt.scope.tests/testScope/bin/testScope/OK.class
Binary files differ
diff --git a/tests/org.eclipse.e4.jdt.scope.tests/testScope/restrict/testScope/KOImport.java b/tests/org.eclipse.e4.jdt.scope.tests/testScope/restrict/testScope/KOImport.java
new file mode 100644
index 0000000..9bc41ce
--- /dev/null
+++ b/tests/org.eclipse.e4.jdt.scope.tests/testScope/restrict/testScope/KOImport.java
@@ -0,0 +1,12 @@
+package testScope;
+
+import org.junit.Assert;
+
+public class KOImport {
+	
+	public KOImport() {
+		System.err.println(0);
+		Assert.assertTrue(false);
+	}
+
+}
diff --git a/tests/org.eclipse.e4.jdt.scope.tests/testScope/restrict/testScope/KOMember.java b/tests/org.eclipse.e4.jdt.scope.tests/testScope/restrict/testScope/KOMember.java
new file mode 100644
index 0000000..d8ae37f
--- /dev/null
+++ b/tests/org.eclipse.e4.jdt.scope.tests/testScope/restrict/testScope/KOMember.java
@@ -0,0 +1,7 @@
+package testScope;
+
+public class KOMember {
+	
+	org.junit.Assert member;
+
+}
diff --git a/tests/org.eclipse.e4.jdt.scope.tests/testScope/restrict/testScope/KOParameter.java b/tests/org.eclipse.e4.jdt.scope.tests/testScope/restrict/testScope/KOParameter.java
new file mode 100644
index 0000000..f2948e6
--- /dev/null
+++ b/tests/org.eclipse.e4.jdt.scope.tests/testScope/restrict/testScope/KOParameter.java
@@ -0,0 +1,8 @@
+package testScope;
+
+public class KOParameter {
+	
+	public void koParameter(org.junit.Assert param) {
+	}
+
+}
diff --git a/tests/org.eclipse.e4.jdt.scope.tests/testScope/restrict/testScope/KOReturnType.java b/tests/org.eclipse.e4.jdt.scope.tests/testScope/restrict/testScope/KOReturnType.java
new file mode 100644
index 0000000..60f4256
--- /dev/null
+++ b/tests/org.eclipse.e4.jdt.scope.tests/testScope/restrict/testScope/KOReturnType.java
@@ -0,0 +1,9 @@
+package testScope;
+
+public class KOReturnType {
+	
+	public org.junit.Assert koReturnType() {
+		return null;
+	}
+
+}
diff --git a/tests/org.eclipse.e4.jdt.scope.tests/testScope/restrict/testScope/KOWildcardImport.java b/tests/org.eclipse.e4.jdt.scope.tests/testScope/restrict/testScope/KOWildcardImport.java
new file mode 100644
index 0000000..f7ae386
--- /dev/null
+++ b/tests/org.eclipse.e4.jdt.scope.tests/testScope/restrict/testScope/KOWildcardImport.java
@@ -0,0 +1,11 @@
+package testScope;
+
+import org.junit.*;
+
+public class KOWildcardImport {
+
+	public KOWildcardImport() {
+		Assert.assertTrue(true);
+	}
+	
+}
diff --git a/tests/org.eclipse.e4.jdt.scope.tests/testScope/seeAll/testScope/OK.java b/tests/org.eclipse.e4.jdt.scope.tests/testScope/seeAll/testScope/OK.java
new file mode 100644
index 0000000..359ed29
--- /dev/null
+++ b/tests/org.eclipse.e4.jdt.scope.tests/testScope/seeAll/testScope/OK.java
@@ -0,0 +1,10 @@
+package testScope;
+
+import org.junit.Assert;
+
+public class OK {
+
+	public OK() {
+		Assert.assertTrue(true);
+	}
+}
diff --git a/tests/org.eclipse.e4.jdt.scope.tests/testScope/seeAllTagged/testScope/OK2.java b/tests/org.eclipse.e4.jdt.scope.tests/testScope/seeAllTagged/testScope/OK2.java
new file mode 100644
index 0000000..cef6155
--- /dev/null
+++ b/tests/org.eclipse.e4.jdt.scope.tests/testScope/seeAllTagged/testScope/OK2.java
@@ -0,0 +1,10 @@
+package testScope;
+
+import org.junit.Assert;
+
+public class OK2 {
+
+	public OK2() {
+		Assert.assertTrue(true);
+	}
+}
diff --git a/tests/org.eclipse.e4.jdt.scope.tests/testScope/test.py b/tests/org.eclipse.e4.jdt.scope.tests/testScope/test.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/org.eclipse.e4.jdt.scope.tests/testScope/test.py