Bug 563501 - ClosedFileSystemException concerning ZipFileSystem
containing ct.sym path

Do not hide exceptions on creation of CtSym, and re-create CTSym in case
somebody closed underlined FileSystem.

Change-Id: Iaf066944069e09489bc4d186a3616cae281d5725
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/RunBug563501Tests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/RunBug563501Tests.java
new file mode 100644
index 0000000..3fd7f17
--- /dev/null
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/RunBug563501Tests.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * Copyright (c) 2020 IBM Corporation and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jdt.core.tests;
+
+import org.eclipse.jdt.core.tests.builder.Bug549646Test;
+import org.eclipse.jdt.core.tests.compiler.regression.ModuleCompilationTests;
+import org.eclipse.jdt.core.tests.model.ModuleBuilderTests;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Runs minimal suite for reproducing bug 563501.
+ * This is not part of the regular test suite.
+ */
+public class RunBug563501Tests extends TestCase {
+	public RunBug563501Tests(String name) {
+		super(name);
+	}
+	public static Test suite() {
+		org.eclipse.jdt.core.tests.junit.extension.TestCase.TESTS_NAMES = new String[] {
+					"testCompilerRegression", "testReleaseOption10", "testConvertToModule"
+				};
+		TestSuite suite = new TestSuite(RunBug563501Tests.class.getName());
+		suite.addTest(Bug549646Test.suite());
+		suite.addTest(ModuleCompilationTests.suite());
+		suite.addTest(ModuleBuilderTests.suite());
+		return suite;
+	}
+}
+
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/util/CtSym.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/util/CtSym.java
index 8a04b70..2dc554d 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/util/CtSym.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/util/CtSym.java
@@ -13,6 +13,7 @@
  *******************************************************************************/
 package org.eclipse.jdt.internal.compiler.util;
 
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.net.URI;
 import java.nio.file.DirectoryStream;
@@ -108,52 +109,42 @@
 	 */
 	private final Map<String, Map<String, Path>> allReleasesPaths = new ConcurrentHashMap<>();
 
-	CtSym(Path jdkHome) {
+	CtSym(Path jdkHome) throws IOException {
 		this.jdkHome = jdkHome;
 		this.ctSymFile = jdkHome.resolve("lib/ct.sym"); //$NON-NLS-1$
-		FileSystem fst = null;
+		init();
+	}
+
+	private void init() throws IOException {
 		boolean exists = Files.exists(this.ctSymFile);
-		if (exists) {
-			URI uri = URI.create("jar:file:" + this.ctSymFile.toUri().getRawPath()); //$NON-NLS-1$
+		if (!exists) {
+			throw new FileNotFoundException("File " + this.ctSymFile + " does not exist"); //$NON-NLS-1$//$NON-NLS-2$
+		}
+		FileSystem fst = null;
+		URI uri = URI.create("jar:file:" + this.ctSymFile.toUri().getRawPath()); //$NON-NLS-1$
+		try {
+			fst = FileSystems.getFileSystem(uri);
+		} catch (Exception fne) {
+			// Ignore and move on
+		}
+		if (fst == null) {
 			try {
+				fst = FileSystems.newFileSystem(uri, new HashMap<>());
+			} catch (FileSystemAlreadyExistsException e) {
 				fst = FileSystems.getFileSystem(uri);
-			} catch (Exception fne) {
-				// Ignore and move on
-			}
-			if (fst == null) {
-				try {
-					fst = FileSystems.newFileSystem(uri, new HashMap<>());
-				} catch (FileSystemAlreadyExistsException e) {
-					fst = FileSystems.getFileSystem(uri);
-				} catch (IOException e) {
-					//
-				}
 			}
 		}
 		this.fs = fst;
 		if (fst == null) {
-			this.root = null;
-			this.isJRE12Plus = false;
+			throw new IOException("Failed to create ct.sym file system for " + this.ctSymFile); //$NON-NLS-1$
 		} else {
-			try {
-				this.root = fst.getPath("/"); //$NON-NLS-1$
-				this.isJRE12Plus = isCurrentRelease12plus();
-			} catch (IOException e) {
-				this.root = null;
-			}
+			this.root = fst.getPath("/"); //$NON-NLS-1$
+			this.isJRE12Plus = isCurrentRelease12plus();
 		}
-
 	}
 
 	/**
-	 * @return true if ct.sym file exists and can be read
-	 */
-	public boolean exists() {
-		return this.fs != null;
-	}
-
-	/**
-	 * @return may return null if ct.sym file doesn't exist
+	 * @return never null
 	 */
 	public FileSystem getFs() {
 		return this.fs;
@@ -168,7 +159,7 @@
 	}
 
 	/**
-	 * @return may return null if ct.sym file doesn't exist
+	 * @return never null
 	 */
 	public Path getRoot() {
 		return this.root;
@@ -180,9 +171,6 @@
 	 * @return set with all root paths related to given release in ct.sym file
 	 */
 	public List<Path> releaseRoots(String releaseInHex) {
-		if (!exists()) {
-			return Collections.emptyList();
-		}
 		List<Path> list = this.releaseRootPaths.computeIfAbsent(releaseInHex, x -> {
 			List<Path> rootDirs = new ArrayList<>();
 			try (DirectoryStream<Path> stream = Files.newDirectoryStream(this.root)) {
@@ -355,9 +343,6 @@
 	}
 
 	public byte[] getFileBytes(Path path) throws IOException {
-		if (!exists()) {
-			return null;
-		}
 		if (DISABLE_CACHE) {
 			return JRTUtil.safeReadBytes(path);
 		} else {
@@ -421,8 +406,6 @@
 		sb.append("CtSym ["); //$NON-NLS-1$
 		sb.append("file="); //$NON-NLS-1$
 		sb.append(this.ctSymFile);
-		sb.append(", exists="); //$NON-NLS-1$
-		sb.append(this.exists());
 		sb.append("]"); //$NON-NLS-1$
 		return sb.toString();
 	}
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/util/JRTUtil.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/util/JRTUtil.java
index c109246..789ec9e 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/util/JRTUtil.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/util/JRTUtil.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2015, 2017 IBM Corporation.
+ * Copyright (c) 2015, 2020 IBM Corporation.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -127,8 +127,23 @@
 		return system.orElse(null);
 	}
 
-	public static CtSym getCtSym(Path jdkHome) {
-		return ctSymFiles.computeIfAbsent(jdkHome, x -> new CtSym(x));
+	public static CtSym getCtSym(Path jdkHome) throws IOException {
+		CtSym ctSym;
+		try {
+			ctSym = ctSymFiles.compute(jdkHome, (Path x, CtSym current) -> {
+				if (current == null || !current.getFs().isOpen()) {
+					try {
+						return new CtSym(x);
+					} catch (IOException e) {
+						throw new RuntimeIOException(e);
+					}
+				}
+				return current;
+			});
+		} catch (RuntimeIOException rio) {
+			throw rio.getCause();
+		}
+		return ctSym;
 	}
 
 	/** TEST ONLY (use when changing the "modules.to.load" property). */
@@ -246,10 +261,6 @@
 		this.fs = null;// reset and proceed, TODO: this is crude and need to be removed.
 		this.releaseInHex = Integer.toHexString(Integer.parseInt(this.release)).toUpperCase();
 		this.ctSym = JRTUtil.getCtSym(Paths.get(this.jdkHome));
-
-		if (!this.ctSym.exists()) {
-			return;
-		}
 		this.fs = this.ctSym.getFs();
 		if (!Files.exists(this.fs.getPath(this.releaseInHex))
 				|| Files.exists(this.fs.getPath(this.releaseInHex, "system-modules"))) { //$NON-NLS-1$
@@ -304,6 +315,19 @@
 
 }
 
+final class RuntimeIOException extends RuntimeException {
+	private static final long serialVersionUID = 1L;
+
+	public RuntimeIOException(IOException cause) {
+		super(cause);
+	}
+
+	@Override
+	public synchronized IOException getCause() {
+		return (IOException) super.getCause();
+	}
+}
+
 class JrtFileSystem {
 
 	private final Map<String, String> packageToModule = new HashMap<String, String>();
@@ -502,19 +526,6 @@
 		}
 	}
 
-	static final class RuntimeIOException extends RuntimeException {
-		private static final long serialVersionUID = 1L;
-
-		public RuntimeIOException(IOException cause) {
-			super(cause);
-		}
-
-		@Override
-		public synchronized IOException getCause() {
-			return (IOException) super.getCause();
-		}
-	}
-
 	public ClassFileReader getClassfile(String fileName, String module, Predicate<String> moduleNameFilter) throws IOException, ClassFormatException {
 		ClassFileReader reader = null;
 		if (module == null) {
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathJrtWithReleaseOption.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathJrtWithReleaseOption.java
index 579cf00..8384bf4 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathJrtWithReleaseOption.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/ClasspathJrtWithReleaseOption.java
@@ -57,7 +57,7 @@
 	protected Path releasePath;
 	protected Path modulePath;
 	private String modPathString;
-	final CtSym ctSym;
+	private CtSym ctSym;
 
 
 
@@ -73,7 +73,12 @@
 			this.externalAnnotationPath = externalAnnotationPath.toString();
 		}
 		this.release = getReleaseOptionFromCompliance(release);
-		this.ctSym = JRTUtil.getCtSym(Paths.get(this.zipFilename).getParent().getParent());
+		try {
+			this.ctSym = JRTUtil.getCtSym(Paths.get(this.zipFilename).getParent().getParent());
+		} catch (IOException e) {
+			throw new CoreException(new Status(IStatus.ERROR, ClasspathJrtWithReleaseOption.class,
+					"Failed to init ct.sym for " + this.zipFilename, e)); //$NON-NLS-1$
+		}
 		initialize();
 		loadModules();
 	}
@@ -102,9 +107,6 @@
 	 */
 	protected void initialize() throws CoreException {
 		this.releaseInHex = Integer.toHexString(Integer.parseInt(this.release)).toUpperCase();
-		if (!this.ctSym.exists()) {
-			return;
-		}
 		this.fs = this.ctSym.getFs();
 		this.releasePath = this.ctSym.getRoot();
 		Path modPath = this.fs.getPath(this.releaseInHex + (this.ctSym.isJRE12Plus() ? "" : "-modules")); //$NON-NLS-1$ //$NON-NLS-2$
@@ -282,6 +284,7 @@
 		} finally {
 			// The same file system is also used in JRTUtil, so don't close it here.
 			this.fs = null;
+			this.ctSym = null;
 		}
 	}