Bug 565553 - Improve performance of build command parsers with large number of files

Add more caches for methods in AbstractLanguageSettingsOutputScanner
that are IO heavy:
- getFilesystemLocation
- determineMappedURI
- resolvePathEntryInFilesystem (File.exists)

These cut down the execution time of command parsing by around 50% on
Windows, more so when considering Java >=12 when
File.getCanonicalPath/File caching is not manually enabled.
See also https://bugs.eclipse.org/bugs/show_bug.cgi?id=565553#c17
and https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8258036

Change-Id: I80828f969547f824d2e45e60b5f4459d03c70bb1
Signed-off-by: Marc-Andre Laperle <malaperle@gmail.com>
diff --git a/build/org.eclipse.cdt.managedbuilder.core/META-INF/MANIFEST.MF b/build/org.eclipse.cdt.managedbuilder.core/META-INF/MANIFEST.MF
index 83727b3..4b24692 100644
--- a/build/org.eclipse.cdt.managedbuilder.core/META-INF/MANIFEST.MF
+++ b/build/org.eclipse.cdt.managedbuilder.core/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %pluginName
 Bundle-SymbolicName: org.eclipse.cdt.managedbuilder.core; singleton:=true
-Bundle-Version: 9.1.0.qualifier
+Bundle-Version: 9.1.100.qualifier
 Bundle-Activator: org.eclipse.cdt.managedbuilder.core.ManagedBuilderCorePlugin
 Bundle-Vendor: %providerName
 Bundle-Localization: plugin
diff --git a/build/org.eclipse.cdt.managedbuilder.core/src/org/eclipse/cdt/managedbuilder/language/settings/providers/AbstractLanguageSettingsOutputScanner.java b/build/org.eclipse.cdt.managedbuilder.core/src/org/eclipse/cdt/managedbuilder/language/settings/providers/AbstractLanguageSettingsOutputScanner.java
index 8828187..ac877d9 100644
--- a/build/org.eclipse.cdt.managedbuilder.core/src/org/eclipse/cdt/managedbuilder/language/settings/providers/AbstractLanguageSettingsOutputScanner.java
+++ b/build/org.eclipse.cdt.managedbuilder.core/src/org/eclipse/cdt/managedbuilder/language/settings/providers/AbstractLanguageSettingsOutputScanner.java
@@ -100,6 +100,56 @@
 			FIND_RESOURCES_CACHE_SIZE);
 	private HashMap<IProject, LRUCache<IPath, List<IResource>>> findPathInProjectCache = new HashMap<>();
 
+	//String pathStr, URI baseURI -> URI
+	private static class MappedURIKey {
+		URI baseURI;
+		String pathStr;
+
+		public MappedURIKey(URI baseURI, String pathStr) {
+			super();
+			this.baseURI = baseURI;
+			this.pathStr = pathStr;
+		}
+
+		@Override
+		public int hashCode() {
+			final int prime = 31;
+			int result = 1;
+			result = prime * result + ((baseURI == null) ? 0 : baseURI.hashCode());
+			result = prime * result + ((pathStr == null) ? 0 : pathStr.hashCode());
+			return result;
+		}
+
+		@Override
+		public boolean equals(Object obj) {
+			if (this == obj)
+				return true;
+			if (obj == null)
+				return false;
+			if (getClass() != obj.getClass())
+				return false;
+			MappedURIKey other = (MappedURIKey) obj;
+			if (baseURI == null) {
+				if (other.baseURI != null)
+					return false;
+			} else if (!baseURI.equals(other.baseURI))
+				return false;
+			if (pathStr == null) {
+				if (other.pathStr != null)
+					return false;
+			} else if (!pathStr.equals(other.pathStr))
+				return false;
+			return true;
+		}
+	}
+
+	// Caches the result of determineMappedURI
+	private LRUCache<MappedURIKey, URI> mappedURICache = new LRUCache<>(FIND_RESOURCES_CACHE_SIZE);
+	// Caches the result of getFilesystemLocation
+	private LRUCache<URI, IPath> fileSystemLocationCache = new LRUCache<>(FIND_RESOURCES_CACHE_SIZE);
+	// Caches the result of new File(pathname).exists()
+	private LRUCache<IPath, Boolean> pathExistsCache = new LRUCache<>(FIND_RESOURCES_CACHE_SIZE);
+
 	/** @since 8.2 */
 	protected EFSExtensionProvider efsProvider = null;
 
@@ -498,6 +548,9 @@
 		workspaceRootFindContainersForLocationURICache.clear();
 		workspaceRootFindFilesForLocationURICache.clear();
 		findPathInProjectCache.clear();
+		mappedURICache.clear();
+		fileSystemLocationCache.clear();
+		pathExistsCache.clear();
 	}
 
 	@Override
@@ -958,34 +1011,36 @@
 	 * @return {@link URI} of the resource
 	 */
 	private URI determineMappedURI(String pathStr, URI baseURI) {
-		URI uri = null;
+		return mappedURICache.computeIfAbsent(new MappedURIKey(baseURI, pathStr), key -> {
+			URI uri = null;
 
-		if (baseURI == null) {
-			if (new Path(pathStr).isAbsolute()) {
-				uri = resolvePathFromBaseLocation(pathStr, Path.ROOT);
-			}
-		} else if (baseURI.getScheme().equals(EFS.SCHEME_FILE)) {
-			// location on the local file-system
-			IPath baseLocation = org.eclipse.core.filesystem.URIUtil.toPath(baseURI);
-			// careful not to use Path here but 'pathStr' as String as we want to properly navigate symlinks
-			uri = resolvePathFromBaseLocation(pathStr, baseLocation);
-		} else {
-			// location on a remote file-system
-			IPath path = new Path(pathStr); // use canonicalized path here, in particular replace all '\' with '/' for Windows paths
-			URI remoteUri = efsProvider.append(baseURI, path.toString());
-			if (remoteUri != null) {
-				String localPath = efsProvider.getMappedPath(remoteUri);
-				if (localPath != null) {
-					uri = org.eclipse.core.filesystem.URIUtil.toURI(localPath);
+			if (baseURI == null) {
+				if (new Path(pathStr).isAbsolute()) {
+					uri = resolvePathFromBaseLocation(pathStr, Path.ROOT);
+				}
+			} else if (baseURI.getScheme().equals(EFS.SCHEME_FILE)) {
+				// location on the local file-system
+				IPath baseLocation = org.eclipse.core.filesystem.URIUtil.toPath(baseURI);
+				// careful not to use Path here but 'pathStr' as String as we want to properly navigate symlinks
+				uri = resolvePathFromBaseLocation(pathStr, baseLocation);
+			} else {
+				// location on a remote file-system
+				IPath path = new Path(pathStr); // use canonicalized path here, in particular replace all '\' with '/' for Windows paths
+				URI remoteUri = efsProvider.append(baseURI, path.toString());
+				if (remoteUri != null) {
+					String localPath = efsProvider.getMappedPath(remoteUri);
+					if (localPath != null) {
+						uri = org.eclipse.core.filesystem.URIUtil.toURI(localPath);
+					}
 				}
 			}
-		}
 
-		if (uri == null) {
-			// if everything fails just wrap string to URI
-			uri = org.eclipse.core.filesystem.URIUtil.toURI(pathStr);
-		}
-		return uri;
+			if (uri == null) {
+				// if everything fails just wrap string to URI
+				uri = org.eclipse.core.filesystem.URIUtil.toURI(pathStr);
+			}
+			return uri;
+		});
 	}
 
 	/**
@@ -1103,22 +1158,24 @@
 		if (uri == null)
 			return null;
 
-		String pathStr = efsProvider.getMappedPath(uri);
-		uri = org.eclipse.core.filesystem.URIUtil.toURI(pathStr);
+		return fileSystemLocationCache.computeIfAbsent(uri, (k) -> {
+			String pathStr = efsProvider.getMappedPath(uri);
+			URI resUri = org.eclipse.core.filesystem.URIUtil.toURI(pathStr);
 
-		if (uri != null && uri.isAbsolute()) {
-			try {
-				File file = new java.io.File(uri);
-				String canonicalPathStr = file.getCanonicalPath();
-				if (new Path(pathStr).getDevice() == null) {
-					return new Path(canonicalPathStr).setDevice(null);
+			if (resUri != null && resUri.isAbsolute()) {
+				try {
+					File file = new java.io.File(resUri);
+					String canonicalPathStr = file.getCanonicalPath();
+					if (new Path(pathStr).getDevice() == null) {
+						return new Path(canonicalPathStr).setDevice(null);
+					}
+					return new Path(canonicalPathStr);
+				} catch (Exception e) {
+					ManagedBuilderCorePlugin.log(e);
 				}
-				return new Path(canonicalPathStr);
-			} catch (Exception e) {
-				ManagedBuilderCorePlugin.log(e);
 			}
-		}
-		return null;
+			return null;
+		});
 	}
 
 	/**
@@ -1199,7 +1256,10 @@
 		IPath location = getFilesystemLocation(uri);
 		if (location != null) {
 			String loc = location.toString();
-			if (new File(loc).exists()) {
+			boolean exists = pathExistsCache.computeIfAbsent(location, (s) -> {
+				return new File(loc).exists();
+			});
+			if (exists) {
 				return optionParser.createEntry(loc, loc, flag);
 			}
 		}