From effe71ed549dd08b895a444e3fb75b68f48b4641 Mon Sep 17 00:00:00 2001 From: Ryan Gustafson Date: Fri, 14 Apr 2017 01:16:18 -0500 Subject: [PATCH] https://github.com/pmd/pmd/issues/337 Modify ResourceLoader to close underlying JarFile if using JarURLConnection to avoid open file leaks. Clarify on JavaDoc that caller must close the returned InputStream. Minor test usage cleanups. --- .../sourceforge/pmd/util/ResourceLoader.java | 30 ++++++++++++++++++- .../sourceforge/pmd/RuleSetFactoryTest.java | 1 + .../pmd/AbstractRuleSetFactoryTest.java | 4 ++- 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/ResourceLoader.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/ResourceLoader.java index ec7ccd1e47..61c5087b23 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/ResourceLoader.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/ResourceLoader.java @@ -10,7 +10,9 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; +import java.net.JarURLConnection; import java.net.URL; +import java.net.URLConnection; import net.sourceforge.pmd.RuleSetNotFoundException; @@ -41,6 +43,8 @@ public final class ResourceLoader { * Method to find a file, first by finding it as a file (either by the * absolute or relative path), then as a URL, and then finally seeing if it * is on the classpath. + *

+ * Caller is responsible for closing the {@link InputStream}. * * @param name * String @@ -59,6 +63,8 @@ public final class ResourceLoader { /** * Uses the ClassLoader passed in to attempt to load the resource if it's * not a File or a URL + *

+ * Caller is responsible for closing the {@link InputStream}. * * @param name * String @@ -91,8 +97,30 @@ public final class ResourceLoader { if (resource == null) { // Don't throw RuleSetNotFoundException, keep API compatibility return null; + } else { + final URLConnection connection = resource.openConnection(); + final InputStream inputStream = connection.getInputStream(); + if (connection instanceof JarURLConnection) { + // Wrap the InputStream to also close the underlying JarFile if from a JarURLConnection. + // See https://github.com/pmd/pmd/issues/337 + return new InputStream() { + @Override + public int read() throws IOException { + return inputStream.read(); + } + + @Override + public void close() throws IOException { + inputStream.close(); + if (connection instanceof JarURLConnection) { + ((JarURLConnection) connection).getJarFile().close(); + } + } + }; + } else { + return inputStream; + } } - return resource.openStream(); } catch (IOException e1) { // Ignored } diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/RuleSetFactoryTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/RuleSetFactoryTest.java index cb9f003dd0..252c346450 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/RuleSetFactoryTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/RuleSetFactoryTest.java @@ -55,6 +55,7 @@ public class RuleSetFactoryTest { InputStream in = ResourceLoader.loadResourceAsStream("net/sourceforge/pmd/rulesets/reference-ruleset.xml", this.getClass().getClassLoader()); Assert.assertNotNull("Test ruleset not found - can't continue with test!", in); + in.close(); RuleSetFactory rsf = new RuleSetFactory(); RuleSets rs = rsf.createRuleSets("net/sourceforge/pmd/rulesets/reference-ruleset.xml"); diff --git a/pmd-test/src/main/java/net/sourceforge/pmd/AbstractRuleSetFactoryTest.java b/pmd-test/src/main/java/net/sourceforge/pmd/AbstractRuleSetFactoryTest.java index e6f4d81647..38c7c12b59 100644 --- a/pmd-test/src/main/java/net/sourceforge/pmd/AbstractRuleSetFactoryTest.java +++ b/pmd-test/src/main/java/net/sourceforge/pmd/AbstractRuleSetFactoryTest.java @@ -229,7 +229,9 @@ public abstract class AbstractRuleSetFactoryTest { List ruleSetFileNames = new ArrayList<>(); try { Properties properties = new Properties(); - properties.load(ResourceLoader.loadResourceAsStream("rulesets/" + language + "/rulesets.properties")); + try (InputStream is = ResourceLoader.loadResourceAsStream("rulesets/" + language + "/rulesets.properties")) { + properties.load(is); + } String fileNames = properties.getProperty("rulesets.filenames"); StringTokenizer st = new StringTokenizer(fileNames, ","); while (st.hasMoreTokens()) {