Fix bug when JAR file is stored in path with non-US-ASCII characters
authorDavid ‘Bombe’ Roden <bombe@freenetproject.org>
Wed, 2 Nov 2016 18:36:38 +0000 (19:36 +0100)
committerDavid ‘Bombe’ Roden <bombe@freenetproject.org>
Wed, 2 Nov 2016 18:36:38 +0000 (19:36 +0100)
src/main/java/de/todesbaum/jsite/main/CLI.java
src/main/java/de/todesbaum/jsite/main/ConfigurationLocator.java
src/main/java/de/todesbaum/jsite/main/JarFileLocator.java [new file with mode: 0644]
src/main/java/de/todesbaum/jsite/main/Main.java
src/test/java/de/todesbaum/jsite/main/ConfigurationLocatorTest.java [new file with mode: 0644]
src/test/java/de/todesbaum/jsite/main/JarFileLocatorTest.java [new file with mode: 0644]

index 1765074..df08244 100644 (file)
@@ -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);
                }
index 7d0a83b..2b19f3e 100644 (file)
@@ -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<File> 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 (file)
index 0000000..da2e766
--- /dev/null
@@ -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<File> locateJarFile();
+
+       class DefaultJarFileLocator implements JarFileLocator {
+
+               private final ClassLoader classLoader;
+
+               public DefaultJarFileLocator(ClassLoader classLoader) {
+                       this.classLoader = classLoader;
+               }
+
+               @Override
+               public Optional<File> 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();
+               }
+       }
+
+}
index a8d70e7..c783264 100644 (file)
@@ -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 (file)
index 0000000..779fb0f
--- /dev/null
@@ -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 (file)
index 0000000..bc8ddf1
--- /dev/null
@@ -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<File> 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<File> locatedJarFile = jarFileLocator.locateJarFile();
+               assertThat(locatedJarFile.isPresent(), is(false));
+       }
+
+}