From 364353eaf23d55c9302d4f736c685d6b9fa59dcb Mon Sep 17 00:00:00 2001 From: =?utf8?q?David=20=E2=80=98Bombe=E2=80=99=20Roden?= Date: Wed, 2 Nov 2016 19:36:38 +0100 Subject: [PATCH] Fix bug when JAR file is stored in path with non-US-ASCII characters --- src/main/java/de/todesbaum/jsite/main/CLI.java | 3 +- .../todesbaum/jsite/main/ConfigurationLocator.java | 11 ++- .../de/todesbaum/jsite/main/JarFileLocator.java | 45 ++++++++++ src/main/java/de/todesbaum/jsite/main/Main.java | 3 +- .../jsite/main/ConfigurationLocatorTest.java | 62 ++++++++++++++ .../todesbaum/jsite/main/JarFileLocatorTest.java | 97 ++++++++++++++++++++++ 6 files changed, 213 insertions(+), 8 deletions(-) create mode 100644 src/main/java/de/todesbaum/jsite/main/JarFileLocator.java create mode 100644 src/test/java/de/todesbaum/jsite/main/ConfigurationLocatorTest.java create mode 100644 src/test/java/de/todesbaum/jsite/main/JarFileLocatorTest.java diff --git a/src/main/java/de/todesbaum/jsite/main/CLI.java b/src/main/java/de/todesbaum/jsite/main/CLI.java index 1765074..df08244 100644 --- a/src/main/java/de/todesbaum/jsite/main/CLI.java +++ b/src/main/java/de/todesbaum/jsite/main/CLI.java @@ -27,6 +27,7 @@ import de.todesbaum.jsite.application.InsertListener; import de.todesbaum.jsite.application.Node; import de.todesbaum.jsite.application.Project; import de.todesbaum.jsite.application.ProjectInserter; +import de.todesbaum.jsite.main.JarFileLocator.DefaultJarFileLocator; /** * Command-line interface for jSite. @@ -89,7 +90,7 @@ public class CLI implements InsertListener { } } - ConfigurationLocator configurationLocator = new ConfigurationLocator(); + ConfigurationLocator configurationLocator = new ConfigurationLocator(new DefaultJarFileLocator(getClass().getClassLoader())); if (configFile != null) { configurationLocator.setCustomLocation(configFile); } diff --git a/src/main/java/de/todesbaum/jsite/main/ConfigurationLocator.java b/src/main/java/de/todesbaum/jsite/main/ConfigurationLocator.java index 7d0a83b..2b19f3e 100644 --- a/src/main/java/de/todesbaum/jsite/main/ConfigurationLocator.java +++ b/src/main/java/de/todesbaum/jsite/main/ConfigurationLocator.java @@ -21,6 +21,7 @@ package de.todesbaum.jsite.main; import java.io.File; import java.util.HashMap; import java.util.Map; +import java.util.Optional; /** * Locator for configuration files in different places. @@ -61,13 +62,11 @@ public class ConfigurationLocator { * list, {@link ConfigurationLocation#CUSTOM} has to be enabled by calling * {@link #setCustomLocation(String)}. */ - public ConfigurationLocator() { + public ConfigurationLocator(JarFileLocator jarFileLocator) { /* are we executed from a JAR file? */ - String resource = getClass().getResource("/" + getClass().getName().replace(".", "/") + ".class").toString(); - if (resource.startsWith("jar:")) { - String jarFileLocation = resource.substring(9, resource.indexOf(".jar!") + 4); - String jarFileDirectory = new File(jarFileLocation).getParent(); - File configurationFile = new File(jarFileDirectory, "jSite.conf"); + Optional jarFile = jarFileLocator.locateJarFile(); + if (jarFile.isPresent()) { + File configurationFile = new File(jarFile.get().getParent(), "jSite.conf"); configurationFiles.put(ConfigurationLocation.NEXT_TO_JAR_FILE, configurationFile.getPath()); } File homeDirectoryFile = new File(System.getProperty("user.home"), ".jSite/config7"); diff --git a/src/main/java/de/todesbaum/jsite/main/JarFileLocator.java b/src/main/java/de/todesbaum/jsite/main/JarFileLocator.java new file mode 100644 index 0000000..da2e766 --- /dev/null +++ b/src/main/java/de/todesbaum/jsite/main/JarFileLocator.java @@ -0,0 +1,45 @@ +package de.todesbaum.jsite.main; + +import static java.util.Optional.empty; + +import java.io.File; +import java.io.UnsupportedEncodingException; +import java.net.URL; +import java.net.URLDecoder; +import java.util.Optional; + +/** + * Locates the JAR file used to load jSite in the filesystem. + */ +public interface JarFileLocator { + + Optional locateJarFile(); + + class DefaultJarFileLocator implements JarFileLocator { + + private final ClassLoader classLoader; + + public DefaultJarFileLocator(ClassLoader classLoader) { + this.classLoader = classLoader; + } + + @Override + public Optional locateJarFile() { + URL resourceUrl = classLoader.getResource(Main.class.getName().replace(".", "/") + ".class"); + if (resourceUrl == null) { + return empty(); + } + String resource = resourceUrl.toString(); + if (resource.startsWith("jar:")) { + try { + String jarFileLocation = URLDecoder.decode(resource.substring(9, resource.indexOf(".jar!") + 4), "UTF-8"); + return Optional.of(new File(jarFileLocation)); + } catch (UnsupportedEncodingException e) { + /* location is not available, ignore. */ + } + } + return empty(); + } + } + +} diff --git a/src/main/java/de/todesbaum/jsite/main/Main.java b/src/main/java/de/todesbaum/jsite/main/Main.java index a8d70e7..c783264 100644 --- a/src/main/java/de/todesbaum/jsite/main/Main.java +++ b/src/main/java/de/todesbaum/jsite/main/Main.java @@ -66,6 +66,7 @@ import de.todesbaum.jsite.gui.ProjectPage; import de.todesbaum.jsite.i18n.I18n; import de.todesbaum.jsite.i18n.I18nContainer; import de.todesbaum.jsite.main.ConfigurationLocator.ConfigurationLocation; +import de.todesbaum.jsite.main.JarFileLocator.DefaultJarFileLocator; import de.todesbaum.util.swing.TWizard; import de.todesbaum.util.swing.TWizardPage; import de.todesbaum.util.swing.WizardListener; @@ -177,7 +178,7 @@ public class Main implements ActionListener, ListSelectionListener, WizardListen */ private Main(String configFilename) { /* collect all possible configuration file locations. */ - ConfigurationLocator configurationLocator = new ConfigurationLocator(); + ConfigurationLocator configurationLocator = new ConfigurationLocator(new DefaultJarFileLocator(getClass().getClassLoader())); if (configFilename != null) { configurationLocator.setCustomLocation(configFilename); } diff --git a/src/test/java/de/todesbaum/jsite/main/ConfigurationLocatorTest.java b/src/test/java/de/todesbaum/jsite/main/ConfigurationLocatorTest.java new file mode 100644 index 0000000..779fb0f --- /dev/null +++ b/src/test/java/de/todesbaum/jsite/main/ConfigurationLocatorTest.java @@ -0,0 +1,62 @@ +package de.todesbaum.jsite.main; + +import static de.todesbaum.jsite.main.ConfigurationLocator.ConfigurationLocation.CUSTOM; +import static de.todesbaum.jsite.main.ConfigurationLocator.ConfigurationLocation.HOME_DIRECTORY; +import static de.todesbaum.jsite.main.ConfigurationLocator.ConfigurationLocation.NEXT_TO_JAR_FILE; +import static java.util.Optional.empty; +import static java.util.Optional.of; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.File; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +/** + * Unit test for {@link ConfigurationLocator}. + */ +public class ConfigurationLocatorTest { + + @Rule + public final TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @Test + public void configurationLocatorPrefersHomeDirectoryIfJarFileCanNotBeFound() { + JarFileLocator jarFileLocator = mock(JarFileLocator.class); + when(jarFileLocator.locateJarFile()).thenReturn(empty()); + ConfigurationLocator locator = new ConfigurationLocator(jarFileLocator); + assertThat(locator.findPreferredLocation(), is(HOME_DIRECTORY)); + assertThat(locator.getFile(HOME_DIRECTORY), endsWith("/config7")); + assertThat(locator.isValidLocation(HOME_DIRECTORY), is(true)); + } + + @Test + public void configurationLocatorUsesFileNextToJarFileIfJarFileIsFound() throws Exception { + File jarFile = temporaryFolder.newFile("test.jar"); + temporaryFolder.newFile("jSite.conf"); + JarFileLocator jarFileLocator = mock(JarFileLocator.class); + when(jarFileLocator.locateJarFile()).thenReturn(of(jarFile)); + ConfigurationLocator locator = new ConfigurationLocator(jarFileLocator); + assertThat(locator.findPreferredLocation(), is(NEXT_TO_JAR_FILE)); + assertThat(locator.getFile(NEXT_TO_JAR_FILE), endsWith("/jSite.conf")); + assertThat(locator.isValidLocation(NEXT_TO_JAR_FILE), is(true)); + } + + @Test + public void customLocationCanBeSet() throws Exception { + File configFile = temporaryFolder.newFile("jSite.conf"); + JarFileLocator jarFileLocator = mock(JarFileLocator.class); + when(jarFileLocator.locateJarFile()).thenReturn(empty()); + ConfigurationLocator locator = new ConfigurationLocator(jarFileLocator); + locator.setCustomLocation(configFile.getPath()); + assertThat(locator.findPreferredLocation(), is(CUSTOM)); + assertThat(locator.getFile(CUSTOM), is(configFile.getPath())); + assertThat(locator.isValidLocation(CUSTOM), is(true)); + } + +} diff --git a/src/test/java/de/todesbaum/jsite/main/JarFileLocatorTest.java b/src/test/java/de/todesbaum/jsite/main/JarFileLocatorTest.java new file mode 100644 index 0000000..bc8ddf1 --- /dev/null +++ b/src/test/java/de/todesbaum/jsite/main/JarFileLocatorTest.java @@ -0,0 +1,97 @@ +package de.todesbaum.jsite.main; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Optional; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; + +import net.pterodactylus.util.io.StreamCopier; + +import de.todesbaum.jsite.main.JarFileLocator.DefaultJarFileLocator; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +/** + * Unit test for {@link JarFileLocator}. + */ +public class JarFileLocatorTest { + + @Rule + public final TemporaryFolder temporaryFolder = new TemporaryFolder(); + + private static final Class CLASS_TO_LOAD = Main.class; + private static final String RESOURCE_TO_COPY = CLASS_TO_LOAD.getName().replace('.', '/') + ".class"; + private static final String PACKAGE_NAME = CLASS_TO_LOAD.getPackage().getName(); + private static final String CLASS_FILENAME = CLASS_TO_LOAD.getSimpleName() + ".class"; + + @Test + public void jarFileCanBeLocatedOnPathWithNonUsAsciiCharacters() throws Exception { + File jarFilePath = temporaryFolder.newFolder("Фото café"); + File jarFile = createJarFile(jarFilePath); + URLClassLoader urlClassLoader = createClassLoader(jarFile.toURI().toURL()); + JarFileLocator jarFileLocator = new DefaultJarFileLocator(urlClassLoader); + File locatedJarFile = jarFileLocator.locateJarFile().get(); + assertThat(locatedJarFile, is(jarFile)); + } + + private File createJarFile(File folder) throws Exception { + File jarFile = new File(folder, "test.jar"); + copyClassFileToStream(RESOURCE_TO_COPY, new FileOutputStream(jarFile)); + return jarFile; + } + + private void copyClassFileToStream(String fileToCopy, FileOutputStream outputStream) throws IOException { + try (JarOutputStream jarOutputStream = new JarOutputStream(outputStream); + InputStream inputStream = getClass().getResourceAsStream("/" + fileToCopy)) { + jarOutputStream.putNextEntry(new JarEntry(fileToCopy)); + StreamCopier.copy(inputStream, jarOutputStream); + jarOutputStream.closeEntry(); + } + } + + private URLClassLoader createClassLoader(URL url) throws MalformedURLException { + return new URLClassLoader(new URL[] { url }) { + @Override + public URL getResource(String name) { + /* ignore parent class loader here. */ + return findResource(name); + } + }; + } + + @Test + public void jarFileCanNotBeLocatedWhenLoadedFromFile() throws Exception { + File folder = temporaryFolder.newFolder(PACKAGE_NAME.split("\\.")); + createClassFile(folder); + ClassLoader classLoader = createClassLoader(temporaryFolder.getRoot().toURI().toURL()); + JarFileLocator jarFileLocator = new DefaultJarFileLocator(classLoader); + Optional locatedJarFile = jarFileLocator.locateJarFile(); + assertThat(locatedJarFile.isPresent(), is(false)); + } + + private void createClassFile(File folder) throws IOException { + File classFile = new File(folder, CLASS_FILENAME); + try (FileOutputStream outputStream = new FileOutputStream(classFile)) { + copyClassFileToStream(RESOURCE_TO_COPY, outputStream); + } + } + + @Test + public void jarFileCanNotBeLoadedIfClasspathIsSuperWeirdAndClassDoesNotExist() throws Exception { + ClassLoader classLoader = createClassLoader(temporaryFolder.getRoot().toURI().toURL()); + JarFileLocator jarFileLocator = new DefaultJarFileLocator(classLoader); + Optional locatedJarFile = jarFileLocator.locateJarFile(); + assertThat(locatedJarFile.isPresent(), is(false)); + } + +} -- 2.7.4