Bug 567124: Disable QML Analyzer relatively quietly on Java 15

Instead of pop-up NPEs and hung UI, log once that QML Analyzer is
unsupported.

Change-Id: I4ad599e870bd73f5cbda8992dedb14405af545f4
(cherry picked from commit 6d76cc5839e435482c7c711c11db36e617738101)
diff --git a/qt/org.eclipse.cdt.qt.core/.settings/.api_filters b/qt/org.eclipse.cdt.qt.core/.settings/.api_filters
new file mode 100644
index 0000000..b7f2c7c
--- /dev/null
+++ b/qt/org.eclipse.cdt.qt.core/.settings/.api_filters
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<component id="org.eclipse.cdt.qt.core" version="2">
+    <resource path="src/org/eclipse/cdt/qt/core/IQMLAnalyzer.java" type="org.eclipse.cdt.qt.core.IQMLAnalyzer">
+        <filter id="404000815">
+            <message_arguments>
+                <message_argument value="org.eclipse.cdt.qt.core.IQMLAnalyzer"/>
+                <message_argument value="isSupported()"/>
+            </message_arguments>
+        </filter>
+    </resource>
+</component>
diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/QMLAnalyzer.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/QMLAnalyzer.java
index b347e54..5f04092 100644
--- a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/QMLAnalyzer.java
+++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/QMLAnalyzer.java
@@ -39,6 +39,7 @@
 
 	private QMLModuleResolver moduleResolver;
 	private ScriptEngine engine;
+	private Boolean supported;
 	private Invocable invoke;
 	private Object tern;
 
@@ -46,6 +47,14 @@
 	public void load() throws ScriptException, IOException, NoSuchMethodException {
 		moduleResolver = new QMLModuleResolver(this);
 		engine = new ScriptEngineManager().getEngineByName("nashorn");
+		if (engine == null) {
+			synchronized (this) {
+				supported = false;
+				notifyAll();
+			}
+			throw new ScriptException(
+					"Nashorn script engine is not available in Java 15 and above. The QML Analyzer is not supported.");
+		}
 		invoke = (Invocable) engine;
 
 		loadDep("/tern-qml/node_modules/acorn/dist/acorn.js");
@@ -100,6 +109,7 @@
 
 		synchronized (this) {
 			tern = invoke.invokeFunction("newTernServer", options);
+			supported = tern != null;
 			notifyAll();
 		}
 	}
@@ -126,16 +136,25 @@
 		}
 	}
 
-	private void waitUntilLoaded() {
+	@Override
+	public boolean isSupported() {
 		synchronized (this) {
-			while (tern == null) {
+			while (supported == null) {
 				try {
 					wait();
 				} catch (InterruptedException e) {
 					Activator.log(e);
-					return;
+					return false;
 				}
 			}
+			return supported;
+		}
+	}
+
+	private void waitUntilLoaded() throws ScriptException {
+		if (!isSupported()) {
+			throw new ScriptException(
+					"Nashorn script engine is not available in Java 15 and above. The QML Analyzer is not supported.");
 		}
 	}
 
diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/IQMLAnalyzer.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/IQMLAnalyzer.java
index 88f7b2a..6a4e559 100644
--- a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/IQMLAnalyzer.java
+++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/IQMLAnalyzer.java
@@ -24,6 +24,14 @@
 
 public interface IQMLAnalyzer {
 
+	/**
+	 * Added in CDT 10.0.1 with no version bump. See Bug 567124.
+	 * @since 2.3
+	 */
+	default boolean isSupported() {
+		return true;
+	}
+
 	void addFile(String fileName, String code) throws NoSuchMethodException, ScriptException;
 
 	void deleteFile(String fileName) throws NoSuchMethodException, ScriptException;
diff --git a/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/editor/QMLContentAssistProcessor.java b/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/editor/QMLContentAssistProcessor.java
index 1a6fa9c..1d7dde7 100644
--- a/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/editor/QMLContentAssistProcessor.java
+++ b/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/editor/QMLContentAssistProcessor.java
@@ -41,6 +41,9 @@
 
 	@Override
 	public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) {
+		if (analyzer == null || !analyzer.isSupported()) {
+			return NO_COMPLETIONS;
+		}
 		IDocument document = viewer.getDocument();
 		String prefix = lastWord(document, offset);
 		// Save the file
diff --git a/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/editor/QMLEditor.java b/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/editor/QMLEditor.java
index a076895..5a50183 100644
--- a/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/editor/QMLEditor.java
+++ b/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/editor/QMLEditor.java
@@ -81,13 +81,15 @@
 		String fileName = new File(fileInput.getFile().getLocationURI()).getAbsolutePath();
 		IDocument document = getSourceViewer().getDocument();
 
-		try {
-			analyzer.deleteFile(fileName);
-			analyzer.addFile(fileName, document.get());
-		} catch (NoSuchMethodException e) {
-			Activator.log(e);
-		} catch (ScriptException e) {
-			Activator.log(e);
+		if (analyzer != null && analyzer.isSupported()) {
+			try {
+				analyzer.deleteFile(fileName);
+				analyzer.addFile(fileName, document.get());
+			} catch (NoSuchMethodException e) {
+				Activator.log(e);
+			} catch (ScriptException e) {
+				Activator.log(e);
+			}
 		}
 		super.doSave(progressMonitor);
 	}
diff --git a/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/editor/QMLHyperlink.java b/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/editor/QMLHyperlink.java
index 49f5c62..3b76d00 100644
--- a/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/editor/QMLHyperlink.java
+++ b/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/editor/QMLHyperlink.java
@@ -61,6 +61,10 @@
 	@Override
 	public void open() {
 		IQMLAnalyzer analyzer = Activator.getService(IQMLAnalyzer.class);
+		if (analyzer == null || !analyzer.isSupported()) {
+			return;
+		}
+
 		try {
 			IDocument document = viewer.getDocument();
 			String selected = document.get(region.getOffset(), region.getLength());
diff --git a/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/editor/QMLHyperlinkDetector.java b/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/editor/QMLHyperlinkDetector.java
index de8a3cb..a5174e6 100644
--- a/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/editor/QMLHyperlinkDetector.java
+++ b/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/editor/QMLHyperlinkDetector.java
@@ -10,6 +10,8 @@
  *******************************************************************************/
 package org.eclipse.cdt.internal.qt.ui.editor;
 
+import org.eclipse.cdt.internal.qt.core.Activator;
+import org.eclipse.cdt.qt.core.IQMLAnalyzer;
 import org.eclipse.jface.text.IRegion;
 import org.eclipse.jface.text.ITextViewer;
 import org.eclipse.jface.text.hyperlink.AbstractHyperlinkDetector;
@@ -20,6 +22,11 @@
 
 	@Override
 	public IHyperlink[] detectHyperlinks(ITextViewer textViewer, IRegion region, boolean canShowMultipleHyperlinks) {
+		IQMLAnalyzer analyzer = Activator.getService(IQMLAnalyzer.class);
+		if (analyzer == null || !analyzer.isSupported()) {
+			return null;
+		}
+
 		// TODO is length of region ever > 0?
 		IRegion wordRegion = QMLEditor.findWord(textViewer.getDocument(), region.getOffset());
 		if (wordRegion != null) {
diff --git a/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/resources/QMLTernFileUpdateJob.java b/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/resources/QMLTernFileUpdateJob.java
index c0c6c7c..26eac65 100644
--- a/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/resources/QMLTernFileUpdateJob.java
+++ b/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/resources/QMLTernFileUpdateJob.java
@@ -42,6 +42,9 @@
 
 	@Override
 	protected IStatus run(IProgressMonitor monitor) {
+		if (analyzer == null || !analyzer.isSupported()) {
+			return Status.OK_STATUS;
+		}
 		for (IResourceDelta delta : deltaList) {
 			IResource resource = delta.getResource();
 			String fileName = resource.getFullPath().toString().substring(1);