Bug 526820 - Polish: Warning for unknown nature referenced by project

Change-Id: Ib48a3f17c4f277a96c388f56885ad7cc680267fb
Signed-off-by: Mickael Istria <mistria@redhat.com>
diff --git a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/CheckMissingNaturesListener.java b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/CheckMissingNaturesListener.java
index 603ea60..d507c02 100644
--- a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/CheckMissingNaturesListener.java
+++ b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/CheckMissingNaturesListener.java
@@ -10,7 +10,11 @@
  *******************************************************************************/
 package org.eclipse.core.internal.resources;
 
+import java.io.*;
 import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.eclipse.core.internal.utils.FileUtil;
 import org.eclipse.core.internal.utils.Messages;
 import org.eclipse.core.resources.*;
 import org.eclipse.core.runtime.*;
@@ -145,7 +149,7 @@
 				}
 
 				final Set<IMarker> toRemove = new HashSet<>();
-				for (IMarker existingMarker : getRelatedMarkers(project)) {
+				for (IMarker existingMarker : getRelatedProjectMarkers(project)) {
 					String markerNature = existingMarker.getAttribute(NATURE_ID_ATTRIBUTE, ""); //$NON-NLS-1$
 					if (!missingNatures.contains(markerNature)) {
 						toRemove.add(existingMarker);
@@ -162,11 +166,18 @@
 							for (IMarker marker : toRemove) {
 								marker.delete();
 							}
+							IResource targetResource = project.getFile(IProjectDescription.DESCRIPTION_FILE_NAME);
+							if (!targetResource.isAccessible()) {
+								targetResource = project;
+							}
 							for (String natureId : missingNatures) {
-								IMarker marker = project.createMarker(MARKER_TYPE);
+								IMarker marker = targetResource.createMarker(MARKER_TYPE);
 								marker.setAttribute(IMarker.SEVERITY, getMissingNatureSeverity(project));
 								marker.setAttribute(IMarker.MESSAGE, NLS.bind(Messages.natures_missingNature, natureId));
 								marker.setAttribute(NATURE_ID_ATTRIBUTE, natureId);
+								if (targetResource.getType() == IResource.FILE) {
+									updateRange(marker, natureId, (IFile) targetResource);
+								}
 							}
 							return Status.OK_STATUS;
 						}
@@ -188,12 +199,54 @@
 		}
 	}
 
-	protected Collection<IMarker> getRelatedMarkers(IContainer workspaceRootOrProject) {
-		if (!workspaceRootOrProject.isAccessible()) {
+	protected void updateRange(IMarker marker, String natureId, IFile file) {
+		if (!file.isAccessible()) {
+			return;
+		}
+		Pattern pattern = Pattern.compile(".*<" + IModelObjectConstants.NATURE + ">\\s*(" + natureId.replace(".", "\\.") + ")\\s*</" + IModelObjectConstants.NATURE + ">.*", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$
+				Pattern.DOTALL);
+		try (InputStream input = file.getContents(); ByteArrayOutputStream output = new ByteArrayOutputStream();) {
+			FileUtil.transferStreams(input, output, file.getLocation().toString(), new NullProgressMonitor());
+			String content = output.toString();
+			Matcher matcher = pattern.matcher(content);
+			if (matcher.matches() && matcher.groupCount() > 0) {
+				marker.setAttribute(IMarker.CHAR_START, matcher.start(1));
+				marker.setAttribute(IMarker.CHAR_END, matcher.end(1) - 1);
+			}
+		} catch (IOException e) {
+			ResourcesPlugin.getPlugin().getLog().log(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, e.getMessage(), e));
+		} catch (CoreException e) {
+			ResourcesPlugin.getPlugin().getLog().log(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, e.getMessage(), e));
+		}
+	}
+
+	protected Collection<IMarker> getRelatedMarkers(IContainer rootOrProject) {
+		switch (rootOrProject.getType()) {
+			case IResource.ROOT :
+				return getRelatedRootMarkers((IWorkspaceRoot) rootOrProject);
+			case IResource.PROJECT :
+				return getRelatedProjectMarkers((IProject) rootOrProject);
+		}
+		return Collections.emptyList();
+	}
+
+	protected Collection<IMarker> getRelatedRootMarkers(IWorkspaceRoot root) {
+		if (!root.isAccessible()) {
+			return Collections.emptyList();
+		}
+		Set<IMarker> res = new HashSet<>();
+		for (IProject project : root.getProjects()) {
+			res.addAll(getRelatedProjectMarkers(project));
+		}
+		return res;
+	}
+
+	protected Collection<IMarker> getRelatedProjectMarkers(IProject project) {
+		if (!project.isAccessible()) {
 			return Collections.emptyList();
 		}
 		try {
-			return Arrays.asList(workspaceRootOrProject.findMarkers(MARKER_TYPE, true, workspaceRootOrProject.getType() == IResource.ROOT ? IResource.DEPTH_ONE : IResource.DEPTH_ZERO));
+			return Arrays.asList(project.findMarkers(MARKER_TYPE, true, IResource.DEPTH_ONE));
 		} catch (CoreException e) {
 			ResourcesPlugin.getPlugin().getLog().log(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, e.getMessage(), e));
 			return Collections.emptyList();
diff --git a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PreferenceInitializer.java b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PreferenceInitializer.java
index 8cd0510..5521dea 100644
--- a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PreferenceInitializer.java
+++ b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/PreferenceInitializer.java
@@ -11,6 +11,7 @@
  *******************************************************************************/
 package org.eclipse.core.internal.resources;
 
+import org.eclipse.core.resources.IMarker;
 import org.eclipse.core.resources.ResourcesPlugin;
 import org.eclipse.core.runtime.preferences.*;
 
@@ -41,11 +42,10 @@
 	public static final long PREF_DELTA_EXPIRATION_DEFAULT = 30 * 24 * 3600 * 1000l; // 30 days
 	/**
 	 * Default setting for {@value ResourcesPlugin#PREF_MISSING_NATURE_MARKER_SEVERITY}.
-	 * Currently -1/ignore, but very likely to change.
 	 *
 	 * @since 3.12
 	 */
-	public static final int PREF_MISSING_NATURE_MARKER_SEVERITY_DEFAULT = -1;
+	public static final int PREF_MISSING_NATURE_MARKER_SEVERITY_DEFAULT = IMarker.SEVERITY_WARNING;
 
 	public PreferenceInitializer() {
 		super();
diff --git a/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/NatureTest.java b/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/NatureTest.java
index e7f99b2..8ea9f9d 100644
--- a/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/NatureTest.java
+++ b/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/NatureTest.java
@@ -433,10 +433,39 @@
 		project.refreshLocal(IResource.DEPTH_INFINITE, getMonitor());
 		project.build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
 		Job.getJobManager().join(CheckMissingNaturesListener.MARKER_TYPE, getMonitor());
-		IMarker[] markers = project.findMarkers(CheckMissingNaturesListener.MARKER_TYPE, false, IResource.DEPTH_ZERO);
+		IMarker[] markers = project.findMarkers(CheckMissingNaturesListener.MARKER_TYPE, false, IResource.DEPTH_ONE);
 		Assert.assertEquals(1, markers.length);
 		IMarker marker = markers[0];
 		Assert.assertEquals(NATURE_MISSING, marker.getAttribute("natureId"));
+		Assert.assertNotEquals(-42, marker.getAttribute(IMarker.CHAR_START, -42));
+		Assert.assertNotEquals(-42, marker.getAttribute(IMarker.CHAR_END, -42));
+		try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); InputStream input = ((IFile) marker.getResource()).getContents()) {
+			FileUtil.transferStreams(input, bos, "whatever", getMonitor());
+			String marked = bos.toString().substring(marker.getAttribute(IMarker.CHAR_START, -42), marker.getAttribute(IMarker.CHAR_END, -42) + 1);
+			Assert.assertEquals(NATURE_MISSING, marked);
+		}
+	}
+
+	public void testMissingNatureWithWhitespacesSetChars() throws Exception {
+		ensureExistsInWorkspace(project, true);
+		InstanceScope.INSTANCE.getNode(ResourcesPlugin.PI_RESOURCES).putInt(ResourcesPlugin.PREF_MISSING_NATURE_MARKER_SEVERITY, IMarker.SEVERITY_WARNING);
+		InstanceScope.INSTANCE.getNode(ResourcesPlugin.PI_RESOURCES).flush();
+		IFile dotProjectFile = project.getFile(IProjectDescription.DESCRIPTION_FILE_NAME);
+		dotProjectFile.setContents(new ByteArrayInputStream(("<projectDescription><name>" + project.getName() + "</name><natures><nature> " + NATURE_MISSING + "  </nature></natures></projectDescription>").getBytes()), false, false, getMonitor());
+		project.refreshLocal(IResource.DEPTH_INFINITE, getMonitor());
+		project.build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
+		Job.getJobManager().join(CheckMissingNaturesListener.MARKER_TYPE, getMonitor());
+		IMarker[] markers = project.findMarkers(CheckMissingNaturesListener.MARKER_TYPE, false, IResource.DEPTH_ONE);
+		Assert.assertEquals(1, markers.length);
+		IMarker marker = markers[0];
+		Assert.assertEquals(NATURE_MISSING, marker.getAttribute("natureId"));
+		Assert.assertNotEquals(-42, marker.getAttribute(IMarker.CHAR_START, -42));
+		Assert.assertNotEquals(-42, marker.getAttribute(IMarker.CHAR_END, -42));
+		try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); InputStream input = ((IFile) marker.getResource()).getContents()) {
+			FileUtil.transferStreams(input, bos, "whatever", getMonitor());
+			String marked = bos.toString().substring(marker.getAttribute(IMarker.CHAR_START, -42), marker.getAttribute(IMarker.CHAR_END, -42) + 1);
+			Assert.assertEquals(NATURE_MISSING, marked);
+		}
 	}
 
 	public void testKnownNatureDoesntAddMarker() throws Exception {
@@ -449,7 +478,7 @@
 		project.refreshLocal(IResource.DEPTH_INFINITE, getMonitor());
 		project.build(IncrementalProjectBuilder.FULL_BUILD, getMonitor());
 		Job.getJobManager().join(CheckMissingNaturesListener.MARKER_TYPE, getMonitor());
-		Assert.assertEquals(0, project.findMarkers(CheckMissingNaturesListener.MARKER_TYPE, false, IResource.DEPTH_ZERO).length);
+		Assert.assertEquals(0, project.findMarkers(CheckMissingNaturesListener.MARKER_TYPE, false, IResource.DEPTH_ONE).length);
 	}
 
 	public void testListenToPreferenceChange() throws Exception {
@@ -458,20 +487,20 @@
 		InstanceScope.INSTANCE.getNode(ResourcesPlugin.PI_RESOURCES).putInt(ResourcesPlugin.PREF_MISSING_NATURE_MARKER_SEVERITY, IMarker.SEVERITY_INFO);
 		InstanceScope.INSTANCE.getNode(ResourcesPlugin.PI_RESOURCES).flush();
 		Job.getJobManager().join(CheckMissingNaturesListener.MARKER_TYPE, getMonitor());
-		IMarker[] markers = project.findMarkers(CheckMissingNaturesListener.MARKER_TYPE, false, IResource.DEPTH_ZERO);
+		IMarker[] markers = project.findMarkers(CheckMissingNaturesListener.MARKER_TYPE, false, IResource.DEPTH_ONE);
 		Assert.assertEquals(1, markers.length);
 		Assert.assertEquals(IMarker.SEVERITY_INFO, markers[0].getAttribute(IMarker.SEVERITY, -42));
 		// to IGNORE
 		InstanceScope.INSTANCE.getNode(ResourcesPlugin.PI_RESOURCES).putInt(ResourcesPlugin.PREF_MISSING_NATURE_MARKER_SEVERITY, -1);
 		InstanceScope.INSTANCE.getNode(ResourcesPlugin.PI_RESOURCES).flush();
 		Job.getJobManager().join(CheckMissingNaturesListener.MARKER_TYPE, getMonitor());
-		markers = project.findMarkers(CheckMissingNaturesListener.MARKER_TYPE, false, IResource.DEPTH_ZERO);
+		markers = project.findMarkers(CheckMissingNaturesListener.MARKER_TYPE, false, IResource.DEPTH_ONE);
 		Assert.assertEquals(0, markers.length);
 		// to ERROR
 		InstanceScope.INSTANCE.getNode(ResourcesPlugin.PI_RESOURCES).putInt(ResourcesPlugin.PREF_MISSING_NATURE_MARKER_SEVERITY, IMarker.SEVERITY_ERROR);
 		InstanceScope.INSTANCE.getNode(ResourcesPlugin.PI_RESOURCES).flush();
 		Job.getJobManager().join(CheckMissingNaturesListener.MARKER_TYPE, getMonitor());
-		markers = project.findMarkers(CheckMissingNaturesListener.MARKER_TYPE, false, IResource.DEPTH_ZERO);
+		markers = project.findMarkers(CheckMissingNaturesListener.MARKER_TYPE, false, IResource.DEPTH_ONE);
 		Assert.assertEquals(1, markers.length);
 		Assert.assertEquals(IMarker.SEVERITY_ERROR, markers[0].getAttribute(IMarker.SEVERITY, -42));
 	}