import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Path;
import java.util.Enumeration;

import org.apache.commons.io.IOUtils;
import org.junit.Before;
import org.junit.Test;
import org.junit.rules.TemporaryFolder; 

public class URLClassLoaderIssueTest {
	 private final String libDirectory = "lib"; 
	    private final String sampleLib = "/xmltooling-1.3.2-1.jar"; 
	    private Path workingFolder; 

	    @Before 
	    public void setup() throws IOException { 
	        // we prepare a temp folder with a single JAR file used in our URL class loader to demonstrate the unclosed input stream 
	        TemporaryFolder temporaryFolder = new TemporaryFolder(); 
	        temporaryFolder.create(); 
	        workingFolder = temporaryFolder.newFolder(libDirectory).toPath(); 
	        try (FileOutputStream fileOutputStream = new FileOutputStream(workingFolder + sampleLib)) { 
	            IOUtils.copy(this.getClass().getResourceAsStream(sampleLib), fileOutputStream); 
	        } 
	    } 

	    /** 
	     * Given => a JAR file with META-INF/INDEX.LIST loaded with "normalized" path via the URL class loader 
	     * When => a dummy (META-INF) resource is loaded via URL class loader (e.g. like META-INF/spring.schemas) 
	     * Then => the URL class loader properly closes all open input streams to loaded resources 
	     */ 
	    @Test 
	    public void loadingDummyResource_withNormalizedJarFilePath_urlClassLoaderClosesInputStream() throws IOException { 
	        // GIVEN 
	        File libFile = new File(workingFolder + sampleLib); 
	        URLClassLoader urlClassLoader = new URLClassLoader(new URL[] { libFile.toURI().toURL() }, this.getClass().getClassLoader()); 
	        // WHEN 
	        Enumeration<URL> resources = urlClassLoader.getResources("META-INF/dummy"); 
	        while (resources.hasMoreElements()) { 
	            resources.nextElement(); 
	        } 
	        urlClassLoader.close(); 
	        // THEN (URL class loader uses normalized path to JAR file, so it can be deleted after closing) 
	        assertTrue(libFile.delete()); 
	    } 

	    /** 
	     * Given => a JAR file with META-INF/INDEX.LIST loaded with "non-normalized" path via URL class loader 
	     * When => a dummy (META-INF) resource is loaded via URL class loader (e.g. like META-INF/spring.schemas) 
	     * Then => the URL class loader keeps unclosed input stream after closing/unloading 
	     */ 
	    @Test 
	    public void loadingDummyResource_withoutNormalizedJarFilePath_urlClassLoaderLeaksInputStream() throws IOException { 
	        // GIVEN 
	        File libFile = new File(workingFolder + "/../" + libDirectory + sampleLib); // without "normalized" path! 
	        URLClassLoader urlClassLoader = new URLClassLoader(new URL[] { libFile.toURI().toURL() }, this.getClass().getClassLoader()); 
	        // WHEN 
	        Enumeration<URL> resources = urlClassLoader.getResources("META-INF/dummy"); 
	        while (resources.hasMoreElements()) { 
	            // when URL class loader uses paths to JAR files that are not normalized, the resource loading from the JARs with INDEX.LIST 
	            // opens additional input streams in "loader map (lmap)" of underlying URL class path, which are not considered for closing! 
	            resources.nextElement(); 
	        } 
	        urlClassLoader.close(); 
	        // THEN (URL class loader does NOT use normalized path to JAR file, so it can NOT be deleted after closing) 
	        assertFalse(libFile.delete()); 
	    } 
}
