diff --git a/spring-launcher/pom.xml b/spring-launcher/pom.xml
index 7d3e5c0f34..50e9462292 100644
--- a/spring-launcher/pom.xml
+++ b/spring-launcher/pom.xml
@@ -12,4 +12,27 @@
${basedir}/..
+
+
+
+ maven-invoker-plugin
+
+ ${project.build.directory}/it
+ src/it/settings.xml
+ ${project.build.directory}/local-repo
+ verify
+ true
+
+
+
+ integration-test
+
+ install
+ run
+
+
+
+
+
+
diff --git a/spring-launcher/src/it/executable-jar/pom.xml b/spring-launcher/src/it/executable-jar/pom.xml
new file mode 100644
index 0000000000..c9b4c7a6ae
--- /dev/null
+++ b/spring-launcher/src/it/executable-jar/pom.xml
@@ -0,0 +1,91 @@
+
+
+ 4.0.0
+ org.springframework.zero.launcher.it
+ executable-jar
+ 0.0.1.BUILD-SNAPSHOT
+ jar
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+ 2.6
+
+
+ unpack
+ prepare-package
+
+ unpack
+
+
+
+
+ @project.groupId@
+ @project.artifactId@
+ @project.version@
+ jar
+
+
+ ${project.build.directory}/assembly
+
+
+
+ copy
+ prepare-package
+
+ copy-dependencies
+
+
+ ${project.build.directory}/assembly/lib
+
+
+
+
+
+ maven-assembly-plugin
+ 2.4
+
+
+ src/main/assembly/jar-with-dependencies.xml
+
+
+
+ org.springframework.launcher.JarLauncher
+
+
+ org.springframework.launcher.it.jar.EmbeddedJarStarter
+
+
+
+
+
+ jar-with-dependencies
+ package
+
+ single
+
+
+
+
+
+
+
+
+ org.eclipse.jetty
+ jetty-webapp
+ 8.1.8.v20121106
+
+
+ org.eclipse.jetty
+ jetty-annotations
+ 8.1.8.v20121106
+
+
+ org.springframework
+ spring-webmvc
+ 3.2.0.RELEASE
+
+
+
diff --git a/spring-launcher/src/it/executable-jar/src/main/assembly/jar-with-dependencies.xml b/spring-launcher/src/it/executable-jar/src/main/assembly/jar-with-dependencies.xml
new file mode 100644
index 0000000000..44626f91aa
--- /dev/null
+++ b/spring-launcher/src/it/executable-jar/src/main/assembly/jar-with-dependencies.xml
@@ -0,0 +1,26 @@
+
+
+ full
+
+ jar
+
+ false
+
+
+
+
+ ${project.groupId}:${project.artifactId}
+
+ true
+
+
+
+
+ ${project.build.directory}/assembly
+ /
+
+
+
diff --git a/spring-launcher/src/it/executable-jar/src/main/java/org/springframework/launcher/it/jar/EmbeddedJarStarter.java b/spring-launcher/src/it/executable-jar/src/main/java/org/springframework/launcher/it/jar/EmbeddedJarStarter.java
new file mode 100644
index 0000000000..8bafbd9f8b
--- /dev/null
+++ b/spring-launcher/src/it/executable-jar/src/main/java/org/springframework/launcher/it/jar/EmbeddedJarStarter.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.launcher.it.jar;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
+import org.springframework.web.servlet.DispatcherServlet;
+
+/**
+ * Main class to start the embedded server.
+ *
+ * @author Phillip Webb
+ */
+public final class EmbeddedJarStarter {
+
+ public static void main(String[] args) throws Exception {
+ Server server = new Server(8080);
+
+ ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
+ context.setContextPath("/");
+ server.setHandler(context);
+
+ AnnotationConfigWebApplicationContext webApplicationContext = new AnnotationConfigWebApplicationContext();
+ webApplicationContext.register(SpringConfiguration.class);
+ DispatcherServlet dispatcherServlet = new DispatcherServlet(webApplicationContext);
+ context.addServlet(new ServletHolder(dispatcherServlet), "/*");
+
+ server.start();
+ server.join();
+ }
+}
diff --git a/spring-launcher/src/it/executable-jar/src/main/java/org/springframework/launcher/it/jar/ExampleController.java b/spring-launcher/src/it/executable-jar/src/main/java/org/springframework/launcher/it/jar/ExampleController.java
new file mode 100644
index 0000000000..2ad0474a43
--- /dev/null
+++ b/spring-launcher/src/it/executable-jar/src/main/java/org/springframework/launcher/it/jar/ExampleController.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.launcher.it.jar;
+
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+/**
+ * Simple example Spring MVC Controller.
+ *
+ * @author Phillip Webb
+ */
+@Controller
+public class ExampleController {
+
+ @RequestMapping("/")
+ @ResponseBody
+ public String helloWorld() {
+ return "Hello Embedded Jar World!";
+ }
+
+}
diff --git a/spring-launcher/src/it/executable-jar/src/main/java/org/springframework/launcher/it/jar/SpringConfiguration.java b/spring-launcher/src/it/executable-jar/src/main/java/org/springframework/launcher/it/jar/SpringConfiguration.java
new file mode 100644
index 0000000000..2d533998db
--- /dev/null
+++ b/spring-launcher/src/it/executable-jar/src/main/java/org/springframework/launcher/it/jar/SpringConfiguration.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.launcher.it.jar;
+
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+
+/**
+ * Spring configuration.
+ *
+ * @author Phillip Webb
+ */
+@Configuration
+@EnableWebMvc
+@ComponentScan
+public class SpringConfiguration {
+
+}
diff --git a/spring-launcher/src/it/executable-war/pom.xml b/spring-launcher/src/it/executable-war/pom.xml
new file mode 100644
index 0000000000..ff2b1118c8
--- /dev/null
+++ b/spring-launcher/src/it/executable-war/pom.xml
@@ -0,0 +1,76 @@
+
+
+ 4.0.0
+ org.springframework.zero.launcher.it
+ executable-war
+ 0.0.1.BUILD-SNAPSHOT
+ war
+
+
+
+ org.apache.maven.plugins
+ maven-war-plugin
+ 2.3
+
+
+
+ org.springframework.launcher.WarLauncher
+
+
+ org.springframework.launcher.it.war.embedded.EmbeddedWarStarter
+
+
+ false
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+ 2.6
+
+
+ unpack
+ prepare-package
+
+ unpack
+
+
+
+
+ @project.groupId@
+ @project.artifactId@
+ @project.version@
+ jar
+
+
+ ${project.build.directory}/${project.artifactId}-${project.version}
+
+
+
+
+
+
+
+
+ org.eclipse.jetty
+ jetty-webapp
+ 8.1.8.v20121106
+
+
+ org.eclipse.jetty
+ jetty-plus
+ 8.1.8.v20121106
+
+
+ org.eclipse.jetty
+ jetty-annotations
+ 8.1.8.v20121106
+
+
+ org.springframework
+ spring-webmvc
+ 3.2.0.RELEASE
+
+
+
diff --git a/spring-launcher/src/it/executable-war/src/main/java/org/springframework/launcher/it/war/ExampleController.java b/spring-launcher/src/it/executable-war/src/main/java/org/springframework/launcher/it/war/ExampleController.java
new file mode 100644
index 0000000000..3cef4c506a
--- /dev/null
+++ b/spring-launcher/src/it/executable-war/src/main/java/org/springframework/launcher/it/war/ExampleController.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.launcher.it.war;
+
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+/**
+ * Simple example Spring MVC Controller.
+ *
+ * @author Phillip Webb
+ */
+@Controller
+public class ExampleController {
+
+ @RequestMapping("/")
+ @ResponseBody
+ public String helloWorld() {
+ return "Hello Embedded WAR World!";
+ }
+
+}
diff --git a/spring-launcher/src/it/executable-war/src/main/java/org/springframework/launcher/it/war/SpringConfiguration.java b/spring-launcher/src/it/executable-war/src/main/java/org/springframework/launcher/it/war/SpringConfiguration.java
new file mode 100644
index 0000000000..d1fdd10d95
--- /dev/null
+++ b/spring-launcher/src/it/executable-war/src/main/java/org/springframework/launcher/it/war/SpringConfiguration.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.launcher.it.war;
+
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+
+/**
+ * Spring configuration.
+ *
+ * @author Phillip Webb
+ */
+@Configuration
+@EnableWebMvc
+@ComponentScan
+public class SpringConfiguration {
+
+}
diff --git a/spring-launcher/src/it/executable-war/src/main/java/org/springframework/launcher/it/war/SpringInitializer.java b/spring-launcher/src/it/executable-war/src/main/java/org/springframework/launcher/it/war/SpringInitializer.java
new file mode 100644
index 0000000000..8099eb77c7
--- /dev/null
+++ b/spring-launcher/src/it/executable-war/src/main/java/org/springframework/launcher/it/war/SpringInitializer.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.launcher.it.war;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+
+import org.springframework.web.WebApplicationInitializer;
+import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
+import org.springframework.web.servlet.DispatcherServlet;
+
+/**
+ * Spring {@link WebApplicationInitializer} for classic WAR deployment.
+ *
+ * @author Phillip Webb
+ */
+public class SpringInitializer implements WebApplicationInitializer {
+
+ @Override
+ public void onStartup(ServletContext servletContext) throws ServletException {
+ AnnotationConfigWebApplicationContext webApplicationContext = new AnnotationConfigWebApplicationContext();
+ webApplicationContext.register(SpringConfiguration.class);
+ servletContext.addServlet("dispatcherServlet",
+ new DispatcherServlet(webApplicationContext)).addMapping("/*");
+ }
+
+}
diff --git a/spring-launcher/src/it/executable-war/src/main/java/org/springframework/launcher/it/war/embedded/EmbeddedWarStarter.java b/spring-launcher/src/it/executable-war/src/main/java/org/springframework/launcher/it/war/embedded/EmbeddedWarStarter.java
new file mode 100644
index 0000000000..a3d54083ab
--- /dev/null
+++ b/spring-launcher/src/it/executable-war/src/main/java/org/springframework/launcher/it/war/embedded/EmbeddedWarStarter.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.launcher.it.war.embedded;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.webapp.Configuration;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.springframework.launcher.it.war.SpringInitializer;
+
+/**
+ * Starter to launch the embedded server. NOTE: Jetty annotation scanning is not
+ * compatible with executable WARs so we must specify the {@link SpringInitializer}.
+ *
+ * @author Phillip Webb
+ */
+public final class EmbeddedWarStarter {
+
+ public static void main(String[] args) throws Exception {
+ Server server = new Server(8080);
+
+ WebAppContext webAppContext = new WebAppContext();
+ webAppContext.setContextPath("/");
+ webAppContext.setConfigurations(new Configuration[] {
+ new WebApplicationInitializersConfiguration(SpringInitializer.class) });
+
+ webAppContext.setParentLoaderPriority(true);
+ server.setHandler(webAppContext);
+ server.start();
+
+ server.join();
+ }
+}
diff --git a/spring-launcher/src/it/executable-war/src/main/java/org/springframework/launcher/it/war/embedded/WebApplicationInitializersConfiguration.java b/spring-launcher/src/it/executable-war/src/main/java/org/springframework/launcher/it/war/embedded/WebApplicationInitializersConfiguration.java
new file mode 100644
index 0000000000..9fefb25391
--- /dev/null
+++ b/spring-launcher/src/it/executable-war/src/main/java/org/springframework/launcher/it/war/embedded/WebApplicationInitializersConfiguration.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.launcher.it.war.embedded;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+
+import org.eclipse.jetty.webapp.AbstractConfiguration;
+import org.eclipse.jetty.webapp.Configuration;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.springframework.util.Assert;
+import org.springframework.web.WebApplicationInitializer;
+
+/**
+ * Jetty {@link Configuration} that allows Spring {@link WebApplicationInitializer} to be
+ * started. This is required because Jetty annotation scanning does not work with packaged
+ * WARs.
+ *
+ * @author Phillip Webb
+ */
+public class WebApplicationInitializersConfiguration extends AbstractConfiguration {
+
+ private Class>[] webApplicationInitializers;
+
+ public WebApplicationInitializersConfiguration(Class> webApplicationInitializer,
+ Class>... webApplicationInitializers) {
+ this.webApplicationInitializers = new Class>[webApplicationInitializers.length + 1];
+ this.webApplicationInitializers[0] = webApplicationInitializer;
+ System.arraycopy(webApplicationInitializers, 0, this.webApplicationInitializers,
+ 1, webApplicationInitializers.length);
+ for (Class> i : webApplicationInitializers) {
+ Assert.notNull(i, "WebApplicationInitializer must not be null");
+ Assert.isAssignable(WebApplicationInitializer.class, i);
+ }
+ }
+
+ @Override
+ public void configure(WebAppContext context) throws Exception {
+ context.getServletContext().addListener(new ServletContextListener() {
+
+ @Override
+ public void contextInitialized(ServletContextEvent sce) {
+ try {
+ for (Class> webApplicationInitializer : webApplicationInitializers) {
+ WebApplicationInitializer initializer = (WebApplicationInitializer) webApplicationInitializer.newInstance();
+ initializer.onStartup(sce.getServletContext());
+ }
+ }
+ catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void contextDestroyed(ServletContextEvent sce) {
+ }
+ });
+ }
+
+}
diff --git a/spring-launcher/src/it/settings.xml b/spring-launcher/src/it/settings.xml
new file mode 100644
index 0000000000..e1e0ace341
--- /dev/null
+++ b/spring-launcher/src/it/settings.xml
@@ -0,0 +1,35 @@
+
+
+
+
+ it-repo
+
+ true
+
+
+
+ local.central
+ @localRepositoryUrl@
+
+ true
+
+
+ true
+
+
+
+
+
+ local.central
+ @localRepositoryUrl@
+
+ true
+
+
+ true
+
+
+
+
+
+
diff --git a/spring-launcher/src/main/java/org/springframework/launcher/Archive.java b/spring-launcher/src/main/java/org/springframework/launcher/Archive.java
new file mode 100644
index 0000000000..61e2142cdb
--- /dev/null
+++ b/spring-launcher/src/main/java/org/springframework/launcher/Archive.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2012-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.launcher;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.jar.Manifest;
+
+/**
+ * An archive that can be launched by the {@link Launcher}.
+ *
+ * @author Phillip Webb
+ * @see JarFileArchive
+ */
+public interface Archive {
+
+ /**
+ * Returns the manifest of the archive.
+ * @return the manifest
+ * @throws IOException
+ */
+ Manifest getManifest() throws IOException;
+
+ /**
+ * Returns archive entries.
+ * @return the archive entries
+ */
+ Iterable getEntries();
+
+ /**
+ * Returns a URL that can be used to load the archive.
+ * @return the archive URL
+ * @throws MalformedURLException
+ */
+ URL getUrl() throws MalformedURLException;
+
+ /**
+ * Returns a nest archive from on the the contained entries.
+ * @param entry the entry (may be a directory or file)
+ * @return the nested archive
+ * @throws IOException
+ */
+ Archive getNestedArchive(Entry entry) throws IOException;
+
+ /**
+ * Returns a filtered version of the archive.
+ * @param filter the filter to apply
+ * @return a filter archive
+ * @throws IOException
+ */
+ Archive getFilteredArchive(EntryFilter filter) throws IOException;
+
+ /**
+ * Represents a single entry in the archive.
+ */
+ public static interface Entry {
+
+ /**
+ * Returns {@code true} if the entry represents a directory.
+ * @return if the entry is a directory
+ */
+ boolean isDirectory();
+
+ /**
+ * Returns the name of the entry
+ * @return the name of the entry
+ */
+ String getName();
+
+ }
+
+ /**
+ * A filter for archive entries.
+ */
+ public static interface EntryFilter {
+
+ /**
+ * Apply the jar entry filter.
+ * @param entryName the current entry name. This may be different that the
+ * original entry name if a previous filter has been applied
+ * @param entry the entry to filter
+ * @return the new name of the entry or {@code null} if the entry should not be
+ * included.
+ */
+ String apply(String entryName, Entry entry);
+
+ }
+
+}
diff --git a/spring-launcher/src/main/java/org/springframework/launcher/ExplodedArchive.java b/spring-launcher/src/main/java/org/springframework/launcher/ExplodedArchive.java
new file mode 100644
index 0000000000..91d221c72c
--- /dev/null
+++ b/spring-launcher/src/main/java/org/springframework/launcher/ExplodedArchive.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2012-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.launcher;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.jar.Manifest;
+
+/**
+ * {@link Archive} implementation backed by an exploded archive directory.
+ *
+ * @author Phillip Webb
+ */
+public class ExplodedArchive implements Archive {
+
+ private static final Set SKIPPED_NAMES = new HashSet(Arrays.asList(
+ ".", ".."));
+
+ private static final Object MANIFEST_ENTRY_NAME = "META-INF/MANIFEST.MF";
+
+ private File root;
+
+ private Map entries = new LinkedHashMap();
+
+ private Manifest manifest;
+
+ public ExplodedArchive(File root) {
+ if (!root.exists() || !root.isDirectory()) {
+ throw new IllegalArgumentException("Invalid source folder " + root);
+ }
+ this.root = root;
+ buildEntries(root);
+ this.entries = Collections.unmodifiableMap(this.entries);
+ }
+
+ private ExplodedArchive(File root, Map entries) {
+ this.root = root;
+ this.entries = Collections.unmodifiableMap(entries);
+ }
+
+ private void buildEntries(File file) {
+ if (!file.equals(this.root)) {
+ String name = file.getAbsolutePath().substring(
+ this.root.getAbsolutePath().length() + 1);
+ if (file.isDirectory()) {
+ name += "/";
+ }
+ this.entries.put(name, new FileEntry(name, file));
+ }
+ if (file.isDirectory()) {
+ for (File child : file.listFiles()) {
+ if (!SKIPPED_NAMES.contains(child.getName())) {
+ buildEntries(child);
+ }
+ }
+ }
+ }
+
+ @Override
+ public Manifest getManifest() throws IOException {
+ if (this.manifest == null && this.entries.containsKey(MANIFEST_ENTRY_NAME)) {
+ FileEntry entry = (FileEntry) this.entries.get(MANIFEST_ENTRY_NAME);
+ FileInputStream inputStream = new FileInputStream(entry.getFile());
+ try {
+ this.manifest = new Manifest(inputStream);
+ }
+ finally {
+ inputStream.close();
+ }
+ }
+ return this.manifest;
+ }
+
+ @Override
+ public Iterable getEntries() {
+ return this.entries.values();
+ }
+
+ @Override
+ public URL getUrl() throws MalformedURLException {
+ FilteredURLStreamHandler handler = new FilteredURLStreamHandler();
+ return new URL("file", "", -1, this.root.getAbsolutePath() + "/", handler);
+ // return this.root.toURI().toURL();
+ }
+
+ @Override
+ public Archive getNestedArchive(Entry entry) throws IOException {
+ File file = ((FileEntry) entry).getFile();
+ return (file.isDirectory() ? new ExplodedArchive(file) : new JarFileArchive(file));
+ }
+
+ @Override
+ public Archive getFilteredArchive(EntryFilter filter) throws IOException {
+ Map filteredEntries = new LinkedHashMap();
+ for (Map.Entry entry : this.entries.entrySet()) {
+ String filteredName = filter.apply(entry.getKey(), entry.getValue());
+ if (filteredName != null) {
+ filteredEntries.put(filteredName, new FileEntry(filteredName,
+ ((FileEntry) entry.getValue()).getFile()));
+ }
+ }
+ return new ExplodedArchive(this.root, filteredEntries);
+ }
+
+ private class FileEntry implements Entry {
+
+ private final String name;
+ private final File file;
+
+ public FileEntry(String name, File file) {
+ this.name = name;
+ this.file = file;
+ }
+
+ public File getFile() {
+ return this.file;
+ }
+
+ @Override
+ public boolean isDirectory() {
+ return this.file.isDirectory();
+ }
+
+ @Override
+ public String getName() {
+ return this.name;
+ }
+ }
+
+ /**
+ * {@link URLStreamHandler} that respects filtered entries.
+ */
+ private class FilteredURLStreamHandler extends URLStreamHandler {
+
+ public FilteredURLStreamHandler() {
+ }
+
+ @Override
+ protected URLConnection openConnection(URL url) throws IOException {
+ String name = url.getPath().substring(
+ ExplodedArchive.this.root.getAbsolutePath().length() + 1);
+ if (ExplodedArchive.this.entries.containsKey(name)) {
+ return new URL(url.toString()).openConnection();
+ }
+ return new FileNotFoundURLConnection(url, name);
+ }
+ }
+
+ /**
+ * {@link URLConnection} used to represent a filtered file.
+ */
+ private static class FileNotFoundURLConnection extends URLConnection {
+
+ private String name;
+
+ public FileNotFoundURLConnection(URL url, String name) {
+ super(url);
+ this.name = name;
+ }
+
+ @Override
+ public void connect() throws IOException {
+ throw new FileNotFoundException(this.name);
+ }
+
+ }
+
+}
diff --git a/spring-launcher/src/main/java/org/springframework/launcher/JarFileArchive.java b/spring-launcher/src/main/java/org/springframework/launcher/JarFileArchive.java
new file mode 100644
index 0000000000..6b9c648bcf
--- /dev/null
+++ b/spring-launcher/src/main/java/org/springframework/launcher/JarFileArchive.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2012-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.launcher;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.Manifest;
+
+import org.springframework.launcher.jar.JarEntryFilter;
+import org.springframework.launcher.jar.RandomAccessJarFile;
+
+/**
+ * {@link Archive} implementation backed by a {@link RandomAccessJarFile}.
+ *
+ * @author Phillip Webb
+ */
+public class JarFileArchive implements Archive {
+
+ private final RandomAccessJarFile jarFile;
+
+ private final List entries;
+
+ public JarFileArchive(File file) throws IOException {
+ this(new RandomAccessJarFile(file));
+ }
+
+ public JarFileArchive(RandomAccessJarFile jarFile) {
+ this.jarFile = jarFile;
+ ArrayList jarFileEntries = new ArrayList();
+ Enumeration entries = jarFile.entries();
+ while (entries.hasMoreElements()) {
+ jarFileEntries.add(new JarFileEntry(entries.nextElement()));
+ }
+ this.entries = Collections.unmodifiableList(jarFileEntries);
+ }
+
+ @Override
+ public Manifest getManifest() throws IOException {
+ return this.jarFile.getManifest();
+ }
+
+ @Override
+ public Iterable getEntries() {
+ return this.entries;
+ }
+
+ @Override
+ public URL getUrl() throws MalformedURLException {
+ return this.jarFile.getUrl();
+ }
+
+ @Override
+ public Archive getNestedArchive(Entry entry) throws IOException {
+ JarEntry jarEntry = ((JarFileEntry) entry).getJarEntry();
+ RandomAccessJarFile jarFile = this.jarFile.getNestedJarFile(jarEntry);
+ return new JarFileArchive(jarFile);
+ }
+
+ @Override
+ public Archive getFilteredArchive(final EntryFilter filter) throws IOException {
+ RandomAccessJarFile filteredJar = this.jarFile
+ .getFilteredJarFile(new JarEntryFilter() {
+ @Override
+ public String apply(String name, JarEntry entry) {
+ return filter.apply(name, new JarFileEntry(entry));
+ }
+ });
+ return new JarFileArchive(filteredJar);
+ }
+
+ /**
+ * {@link Archive.Entry} implementation backed by a {@link JarEntry}.
+ */
+ private static class JarFileEntry implements Entry {
+
+ private final JarEntry jarEntry;
+
+ public JarFileEntry(JarEntry jarEntry) {
+ this.jarEntry = jarEntry;
+ }
+
+ public JarEntry getJarEntry() {
+ return this.jarEntry;
+ }
+
+ @Override
+ public boolean isDirectory() {
+ return this.jarEntry.isDirectory();
+ }
+
+ @Override
+ public String getName() {
+ return this.jarEntry.getName();
+ }
+
+ }
+
+}
diff --git a/spring-launcher/src/main/java/org/springframework/launcher/JarLauncher.java b/spring-launcher/src/main/java/org/springframework/launcher/JarLauncher.java
index db3732a254..6e07532ea3 100644
--- a/spring-launcher/src/main/java/org/springframework/launcher/JarLauncher.java
+++ b/spring-launcher/src/main/java/org/springframework/launcher/JarLauncher.java
@@ -17,9 +17,6 @@
package org.springframework.launcher;
import java.util.List;
-import java.util.jar.JarEntry;
-
-import org.springframework.launcher.jar.RandomAccessJarFile;
/**
* {@link Launcher} for JAR based archives. This launcher assumes that dependency jars are
@@ -30,14 +27,13 @@ import org.springframework.launcher.jar.RandomAccessJarFile;
public class JarLauncher extends Launcher {
@Override
- protected boolean isNestedJarFile(JarEntry jarEntry) {
- return !jarEntry.isDirectory() && jarEntry.getName().startsWith("lib/");
+ protected boolean isNestedArchive(Archive.Entry entry) {
+ return !entry.isDirectory() && entry.getName().startsWith("lib/");
}
@Override
- protected void postProcessLib(RandomAccessJarFile jarFile,
- List lib) throws Exception {
- lib.add(0, jarFile);
+ protected void postProcessLib(Archive archive, List lib) throws Exception {
+ lib.add(0, archive);
}
public static void main(String[] args) {
diff --git a/spring-launcher/src/main/java/org/springframework/launcher/Launcher.java b/spring-launcher/src/main/java/org/springframework/launcher/Launcher.java
index 84072d7aab..2dc17e997f 100644
--- a/spring-launcher/src/main/java/org/springframework/launcher/Launcher.java
+++ b/spring-launcher/src/main/java/org/springframework/launcher/Launcher.java
@@ -23,13 +23,10 @@ import java.net.URLClassLoader;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.util.ArrayList;
-import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.logging.Logger;
-import org.springframework.launcher.jar.RandomAccessJarFile;
-
/**
* Base class for launchers that can start an application with a fully configured
* classpath.
@@ -77,11 +74,14 @@ public abstract class Launcher {
if (codeSourcePath == null) {
throw new IllegalStateException("Unable to determine code source archive");
}
- if (codeSourcePath.endsWith("/")) {
- throw new IllegalStateException("The specified code source path '"
- + codeSourcePath + "' is not an archive");
+ File root = new File(codeSourcePath);
+ if (!root.exists()) {
+ throw new IllegalStateException(
+ "Unable to determine code source archive from " + root);
}
- launch(args, new File(codeSourcePath));
+ Archive archive = (root.isDirectory() ? new ExplodedArchive(root)
+ : new JarFileArchive(root));
+ launch(args, archive);
}
/**
@@ -90,22 +90,19 @@ public abstract class Launcher {
* @param archive the underlying (zip/war/jar) archive
* @throws Exception
*/
- protected void launch(String[] args, File archive) throws Exception {
- RandomAccessJarFile jarFile = new RandomAccessJarFile(archive);
-
- List lib = new ArrayList();
- Enumeration jarEntries = jarFile.entries();
- while (jarEntries.hasMoreElements()) {
- JarEntry jarEntry = jarEntries.nextElement();
- if (isNestedJarFile(jarEntry)) {
- this.logger.fine("Adding: " + jarEntry.getName());
- lib.add(jarFile.getNestedJarFile(jarEntry));
+ protected void launch(String[] args, Archive archive) throws Exception {
+ List lib = new ArrayList();
+ for (Archive.Entry entry : archive.getEntries()) {
+ if (isNestedArchive(entry)) {
+ this.logger.fine("Adding: " + entry.getName());
+ lib.add(archive.getNestedArchive(entry));
}
}
+
this.logger.fine("Added " + lib.size() + " entries");
- postProcessLib(jarFile, lib);
+ postProcessLib(archive, lib);
ClassLoader classLoader = createClassLoader(lib);
- launch(args, jarFile, classLoader);
+ launch(args, archive, classLoader);
}
/**
@@ -114,17 +111,16 @@ public abstract class Launcher {
* @param jarEntry the jar entry
* @return {@code true} if the entry is a nested item (jar or folder)
*/
- protected abstract boolean isNestedJarFile(JarEntry jarEntry);
+ protected abstract boolean isNestedArchive(Archive.Entry jarEntry);
/**
* Called to post-process lib entries before they are used. Implementations can add
* and remove entries.
- * @param jarFile the jar file
+ * @param archive the archive
* @param lib the existing lib
* @throws Exception
*/
- protected void postProcessLib(RandomAccessJarFile jarFile,
- List lib) throws Exception {
+ protected void postProcessLib(Archive archive, List lib) throws Exception {
}
/**
@@ -133,8 +129,7 @@ public abstract class Launcher {
* @return the classloader
* @throws Exception
*/
- protected ClassLoader createClassLoader(List lib)
- throws Exception {
+ protected ClassLoader createClassLoader(List lib) throws Exception {
URL[] urls = new URL[lib.size()];
for (int i = 0; i < urls.length; i++) {
urls[i] = lib.get(i).getUrl();
@@ -155,13 +150,13 @@ public abstract class Launcher {
/**
* Launch the application given the archive file and a fully configured classloader.
* @param args the incoming arguments
- * @param jarFile the jar file
+ * @param archive the archive
* @param classLoader the classloader
* @throws Exception
*/
- protected void launch(String[] args, RandomAccessJarFile jarFile,
- ClassLoader classLoader) throws Exception {
- String mainClass = getMainClass(jarFile);
+ protected void launch(String[] args, Archive archive, ClassLoader classLoader)
+ throws Exception {
+ String mainClass = getMainClass(archive);
Runnable runner = createMainMethodRunner(mainClass, args, classLoader);
Thread runnerThread = new Thread(runner);
runnerThread.setContextClassLoader(classLoader);
@@ -172,12 +167,12 @@ public abstract class Launcher {
/**
* Obtain the main class that should be used to launch the application. By default
* this method uses a {@code Start-Class} manifest entry.
- * @param jarFile the jar file
+ * @param archive the archive
* @return the main class
* @throws Exception
*/
- protected String getMainClass(RandomAccessJarFile jarFile) throws Exception {
- String mainClass = jarFile.getManifest().getMainAttributes()
+ protected String getMainClass(Archive archive) throws Exception {
+ String mainClass = archive.getManifest().getMainAttributes()
.getValue("Start-Class");
if (mainClass == null) {
throw new IllegalStateException("No 'Start-Class' manifest entry specified");
diff --git a/spring-launcher/src/main/java/org/springframework/launcher/WarLauncher.java b/spring-launcher/src/main/java/org/springframework/launcher/WarLauncher.java
index e152424af3..478dc7e914 100644
--- a/spring-launcher/src/main/java/org/springframework/launcher/WarLauncher.java
+++ b/spring-launcher/src/main/java/org/springframework/launcher/WarLauncher.java
@@ -18,10 +18,6 @@ package org.springframework.launcher;
import java.io.IOException;
import java.util.List;
-import java.util.jar.JarEntry;
-
-import org.springframework.launcher.jar.JarEntryFilter;
-import org.springframework.launcher.jar.RandomAccessJarFile;
/**
* {@link Launcher} for WAR based archives. This launcher for standard WAR archives.
@@ -33,35 +29,33 @@ import org.springframework.launcher.jar.RandomAccessJarFile;
public class WarLauncher extends Launcher {
@Override
- protected boolean isNestedJarFile(JarEntry jarEntry) {
- if (jarEntry.isDirectory()) {
- return jarEntry.getName().equals("WEB-INF/classes/");
+ protected boolean isNestedArchive(Archive.Entry entry) {
+ if (entry.isDirectory()) {
+ return entry.getName().equals("WEB-INF/classes/");
}
else {
- return jarEntry.getName().startsWith("WEB-INF/lib/")
- || jarEntry.getName().startsWith("WEB-INF/lib-provided/");
+ return entry.getName().startsWith("WEB-INF/lib/")
+ || entry.getName().startsWith("WEB-INF/lib-provided/");
}
}
@Override
- protected void postProcessLib(RandomAccessJarFile jarFile,
- List lib) throws Exception {
- lib.add(0, filterJarFile(jarFile));
+ protected void postProcessLib(Archive archive, List lib) throws Exception {
+ lib.add(0, filterArchive(archive));
}
/**
* Filter the specified WAR file to exclude elements that should not appear on the
* classpath.
- * @param jarFile the source file
- * @return the filtered file
+ * @param archive the source archive
+ * @return the filtered archive
* @throws IOException on error
*/
- protected RandomAccessJarFile filterJarFile(RandomAccessJarFile jarFile)
- throws IOException {
- return jarFile.getFilteredJarFile(new JarEntryFilter() {
+ protected Archive filterArchive(Archive archive) throws IOException {
+ return archive.getFilteredArchive(new Archive.EntryFilter() {
@Override
- public String apply(String entryName, JarEntry entry) {
+ public String apply(String entryName, Archive.Entry entry) {
if (entryName.startsWith("META-INF/") || entryName.startsWith("WEB-INF/")) {
return null;
}
diff --git a/spring-launcher/src/main/java/org/springframework/launcher/jar/RandomAccessJarFile.java b/spring-launcher/src/main/java/org/springframework/launcher/jar/RandomAccessJarFile.java
index ac8b46b34c..ffb6368dcb 100644
--- a/spring-launcher/src/main/java/org/springframework/launcher/jar/RandomAccessJarFile.java
+++ b/spring-launcher/src/main/java/org/springframework/launcher/jar/RandomAccessJarFile.java
@@ -353,8 +353,7 @@ public class RandomAccessJarFile extends JarFile {
}
/**
- * {@link RandomAccessJarURLConnection} used to support
- * {@link RandomAccessJarFile#getUrl()}.
+ * {@link JarURLConnection} used to support {@link RandomAccessJarFile#getUrl()}.
*/
private static class RandomAccessJarURLConnection extends JarURLConnection {
diff --git a/spring-launcher/src/main/java/org/springframework/launcher/package-info.java b/spring-launcher/src/main/java/org/springframework/launcher/package-info.java
index a878456639..c05c211eea 100644
--- a/spring-launcher/src/main/java/org/springframework/launcher/package-info.java
+++ b/spring-launcher/src/main/java/org/springframework/launcher/package-info.java
@@ -17,7 +17,7 @@
/**
* System that allows self contained JAR/WAR archives to be launched using
* {@code java -jar}. Archives can include nested packaged dependency JARs (there is
- * not need to create shade style jars) and are executed without unpacking. The only
+ * no need to create shade style jars) and are executed without unpacking. The only
* constraint is that nested JARs must be stored in the archive uncompressed.
*
* @see org.springframework.launcher.JarLauncher
diff --git a/spring-launcher/src/test/java/org/springframework/launcher/ExplodedArchiveTests.java b/spring-launcher/src/test/java/org/springframework/launcher/ExplodedArchiveTests.java
new file mode 100644
index 0000000000..1c6e2967c5
--- /dev/null
+++ b/spring-launcher/src/test/java/org/springframework/launcher/ExplodedArchiveTests.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2012-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.launcher;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.springframework.launcher.Archive.Entry;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Tests for {@link ExplodedArchive}.
+ *
+ * @author Phillip Webb
+ */
+public class ExplodedArchiveTests {
+
+ @Rule
+ public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+ private File rootFolder;
+
+ private ExplodedArchive archive;
+
+ @Before
+ public void setup() throws Exception {
+ File file = this.temporaryFolder.newFile();
+ TestJarCreator.createTestJar(file);
+
+ this.rootFolder = this.temporaryFolder.newFolder();
+ JarFile jarFile = new JarFile(file);
+ Enumeration entries = jarFile.entries();
+ while (entries.hasMoreElements()) {
+ JarEntry entry = entries.nextElement();
+ File destination = new File(this.rootFolder.getAbsolutePath()
+ + File.separator + entry.getName());
+ destination.getParentFile().mkdirs();
+ if (entry.isDirectory()) {
+ destination.mkdir();
+ }
+ else {
+ copy(jarFile.getInputStream(entry), new FileOutputStream(destination));
+ }
+ }
+ this.archive = new ExplodedArchive(this.rootFolder);
+ }
+
+ private void copy(InputStream in, OutputStream out) throws IOException {
+ byte[] buffer = new byte[1024];
+ int len = in.read(buffer);
+ while (len != -1) {
+ out.write(buffer, 0, len);
+ len = in.read(buffer);
+ }
+ }
+
+ @Test
+ public void getManifest() throws Exception {
+ assertThat(this.archive.getManifest().getMainAttributes().getValue("Built-By"),
+ equalTo("j1"));
+ }
+
+ @Test
+ public void getEntries() throws Exception {
+ Map entries = getEntriesMap(this.archive);
+ assertThat(entries.size(), equalTo(7));
+ }
+
+ @Test
+ public void getUrl() throws Exception {
+ URL url = this.archive.getUrl();
+ assertThat(url, equalTo(this.rootFolder.toURI().toURL()));
+ }
+
+ @Test
+ public void getNestedArchive() throws Exception {
+ Entry entry = getEntriesMap(this.archive).get("nested.jar");
+ Archive nested = this.archive.getNestedArchive(entry);
+ assertThat(nested.getUrl().toString(),
+ equalTo("jar:file:" + this.rootFolder.getPath() + "/nested.jar!/"));
+ }
+
+ @Test
+ public void nestedDirArchive() throws Exception {
+ Entry entry = getEntriesMap(this.archive).get("d/");
+ Archive nested = this.archive.getNestedArchive(entry);
+ Map nestedEntries = getEntriesMap(nested);
+ assertThat(nestedEntries.size(), equalTo(1));
+ assertThat(nested.getUrl().toString(),
+ equalTo("file:" + this.rootFolder.getPath() + "/d/"));
+ }
+
+ @Test
+ public void getFilteredArchive() throws Exception {
+ Archive filteredArchive = this.archive
+ .getFilteredArchive(new Archive.EntryFilter() {
+ @Override
+ public String apply(String entryName, Entry entry) {
+ if (entryName.equals("1.dat")) {
+ return entryName;
+ }
+ return null;
+ }
+ });
+ Map entries = getEntriesMap(filteredArchive);
+ assertThat(entries.size(), equalTo(1));
+ URLClassLoader classLoader = new URLClassLoader(
+ new URL[] { filteredArchive.getUrl() });
+ assertThat(classLoader.getResourceAsStream("1.dat").read(), equalTo(1));
+ assertThat(classLoader.getResourceAsStream("2.dat"), nullValue());
+ }
+
+ private Map getEntriesMap(Archive archive) {
+ Map entries = new HashMap();
+ for (Archive.Entry entry : archive.getEntries()) {
+ entries.put(entry.getName(), entry);
+ }
+ return entries;
+ }
+}
diff --git a/spring-launcher/src/test/java/org/springframework/launcher/JarFileArchiveTests.java b/spring-launcher/src/test/java/org/springframework/launcher/JarFileArchiveTests.java
new file mode 100644
index 0000000000..d3c3b732b5
--- /dev/null
+++ b/spring-launcher/src/test/java/org/springframework/launcher/JarFileArchiveTests.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2012-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.launcher;
+
+import java.io.File;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.springframework.launcher.Archive.Entry;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Tests for {@link JarFileArchive}.
+ *
+ * @author Phillip Webb
+ */
+public class JarFileArchiveTests {
+
+ @Rule
+ public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+ private File rootJarFile;
+
+ private JarFileArchive archive;
+
+ @Before
+ public void setup() throws Exception {
+ this.rootJarFile = this.temporaryFolder.newFile();
+ TestJarCreator.createTestJar(this.rootJarFile);
+ this.archive = new JarFileArchive(this.rootJarFile);
+ }
+
+ @Test
+ public void getManifest() throws Exception {
+ assertThat(this.archive.getManifest().getMainAttributes().getValue("Built-By"),
+ equalTo("j1"));
+ }
+
+ @Test
+ public void getEntries() throws Exception {
+ Map entries = getEntriesMap(this.archive);
+ assertThat(entries.size(), equalTo(7));
+ }
+
+ @Test
+ public void getUrl() throws Exception {
+ URL url = this.archive.getUrl();
+ assertThat(url.toString(), equalTo("jar:file:" + this.rootJarFile.getPath()
+ + "!/"));
+ }
+
+ @Test
+ public void getNestedArchive() throws Exception {
+ Entry entry = getEntriesMap(this.archive).get("nested.jar");
+ Archive nested = this.archive.getNestedArchive(entry);
+ assertThat(nested.getUrl().toString(),
+ equalTo("jar:file:" + this.rootJarFile.getPath() + "!/nested.jar!/"));
+ }
+
+ @Test
+ public void getFilteredArchive() throws Exception {
+ Archive filteredArchive = this.archive
+ .getFilteredArchive(new Archive.EntryFilter() {
+ @Override
+ public String apply(String entryName, Entry entry) {
+ if (entryName.equals("1.dat")) {
+ return entryName;
+ }
+ return null;
+ }
+ });
+ Map entries = getEntriesMap(filteredArchive);
+ assertThat(entries.size(), equalTo(1));
+ }
+
+ private Map getEntriesMap(Archive archive) {
+ Map entries = new HashMap();
+ for (Archive.Entry entry : archive.getEntries()) {
+ entries.put(entry.getName(), entry);
+ }
+ return entries;
+ }
+}
diff --git a/spring-launcher/src/test/java/org/springframework/launcher/TestJarCreator.java b/spring-launcher/src/test/java/org/springframework/launcher/TestJarCreator.java
new file mode 100644
index 0000000000..362da85e09
--- /dev/null
+++ b/spring-launcher/src/test/java/org/springframework/launcher/TestJarCreator.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2012-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.launcher;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.jar.Attributes;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+import java.util.zip.CRC32;
+import java.util.zip.ZipEntry;
+
+/**
+ * Creates a simple test jar.
+ *
+ * @author Phillip Webb
+ */
+public abstract class TestJarCreator {
+
+ public static void createTestJar(File file) throws Exception {
+ FileOutputStream fileOutputStream = new FileOutputStream(file);
+ JarOutputStream jarOutputStream = new JarOutputStream(fileOutputStream);
+ try {
+ writeManifest(jarOutputStream, "j1");
+ writeEntry(jarOutputStream, "1.dat", 1);
+ writeEntry(jarOutputStream, "2.dat", 2);
+ writeDirEntry(jarOutputStream, "d/");
+ writeEntry(jarOutputStream, "d/9.dat", 9);
+
+ JarEntry nestedEntry = new JarEntry("nested.jar");
+ byte[] nestedJarData = getNestedJarData();
+ nestedEntry.setSize(nestedJarData.length);
+ nestedEntry.setCompressedSize(nestedJarData.length);
+ CRC32 crc32 = new CRC32();
+ crc32.update(nestedJarData);
+ nestedEntry.setCrc(crc32.getValue());
+
+ nestedEntry.setMethod(ZipEntry.STORED);
+ jarOutputStream.putNextEntry(nestedEntry);
+ jarOutputStream.write(nestedJarData);
+ jarOutputStream.closeEntry();
+ }
+ finally {
+ jarOutputStream.close();
+ }
+ }
+
+ private static byte[] getNestedJarData() throws Exception {
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ JarOutputStream jarOutputStream = new JarOutputStream(byteArrayOutputStream);
+ writeManifest(jarOutputStream, "j2");
+ writeEntry(jarOutputStream, "3.dat", 3);
+ writeEntry(jarOutputStream, "4.dat", 4);
+ jarOutputStream.close();
+ return byteArrayOutputStream.toByteArray();
+ }
+
+ private static void writeManifest(JarOutputStream jarOutputStream, String name)
+ throws Exception {
+ writeDirEntry(jarOutputStream, "META-INF/");
+ Manifest manifest = new Manifest();
+ manifest.getMainAttributes().putValue("Built-By", name);
+ manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
+ jarOutputStream.putNextEntry(new ZipEntry("META-INF/MANIFEST.MF"));
+ manifest.write(jarOutputStream);
+ jarOutputStream.closeEntry();
+ }
+
+ private static void writeDirEntry(JarOutputStream jarOutputStream, String name)
+ throws IOException {
+ jarOutputStream.putNextEntry(new JarEntry(name));
+ jarOutputStream.closeEntry();
+ }
+
+ private static void writeEntry(JarOutputStream jarOutputStream, String name, int data)
+ throws IOException {
+ jarOutputStream.putNextEntry(new JarEntry(name));
+ jarOutputStream.write(new byte[] { (byte) data });
+ jarOutputStream.closeEntry();
+ }
+
+}
diff --git a/spring-launcher/src/test/java/org/springframework/launcher/data/RandomAccessDataFileTest.java b/spring-launcher/src/test/java/org/springframework/launcher/data/RandomAccessDataFileTests.java
similarity index 99%
rename from spring-launcher/src/test/java/org/springframework/launcher/data/RandomAccessDataFileTest.java
rename to spring-launcher/src/test/java/org/springframework/launcher/data/RandomAccessDataFileTests.java
index 959508c65c..245b5e866a 100644
--- a/spring-launcher/src/test/java/org/springframework/launcher/data/RandomAccessDataFileTest.java
+++ b/spring-launcher/src/test/java/org/springframework/launcher/data/RandomAccessDataFileTests.java
@@ -48,7 +48,7 @@ import org.springframework.launcher.data.RandomAccessDataFile;
*
* @author Phillip Webb
*/
-public class RandomAccessDataFileTest {
+public class RandomAccessDataFileTests {
private static final byte[] BYTES;
static {
diff --git a/spring-launcher/src/test/java/org/springframework/launcher/jar/RandomAccessDataZipInputStreamTest.java b/spring-launcher/src/test/java/org/springframework/launcher/jar/RandomAccessDataZipInputStreamTests.java
similarity index 98%
rename from spring-launcher/src/test/java/org/springframework/launcher/jar/RandomAccessDataZipInputStreamTest.java
rename to spring-launcher/src/test/java/org/springframework/launcher/jar/RandomAccessDataZipInputStreamTests.java
index b6e7a9fc58..bba954ec11 100644
--- a/spring-launcher/src/test/java/org/springframework/launcher/jar/RandomAccessDataZipInputStreamTest.java
+++ b/spring-launcher/src/test/java/org/springframework/launcher/jar/RandomAccessDataZipInputStreamTests.java
@@ -40,7 +40,7 @@ import org.springframework.launcher.jar.RandomAccessDataZipInputStream;
*
* @author Phillip Webb
*/
-public class RandomAccessDataZipInputStreamTest {
+public class RandomAccessDataZipInputStreamTests {
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
diff --git a/spring-launcher/src/test/java/org/springframework/launcher/jar/RandomAccessJarFileTest.java b/spring-launcher/src/test/java/org/springframework/launcher/jar/RandomAccessJarFileTests.java
similarity index 64%
rename from spring-launcher/src/test/java/org/springframework/launcher/jar/RandomAccessJarFileTest.java
rename to spring-launcher/src/test/java/org/springframework/launcher/jar/RandomAccessJarFileTests.java
index b50ddd5a43..e374f12ad0 100644
--- a/spring-launcher/src/test/java/org/springframework/launcher/jar/RandomAccessJarFileTest.java
+++ b/spring-launcher/src/test/java/org/springframework/launcher/jar/RandomAccessJarFileTests.java
@@ -16,6 +16,25 @@
package org.springframework.launcher.jar;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.JarURLConnection;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.zip.ZipEntry;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+import org.springframework.launcher.TestJarCreator;
+import org.springframework.launcher.data.RandomAccessDataFile;
+
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.instanceOf;
@@ -26,38 +45,12 @@ import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.JarURLConnection;
-import java.net.URL;
-import java.util.Enumeration;
-import java.util.jar.Attributes;
-import java.util.jar.JarEntry;
-import java.util.jar.JarFile;
-import java.util.jar.JarOutputStream;
-import java.util.jar.Manifest;
-import java.util.zip.CRC32;
-import java.util.zip.ZipEntry;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.rules.TemporaryFolder;
-import org.springframework.launcher.data.RandomAccessDataFile;
-import org.springframework.launcher.jar.JarEntryFilter;
-import org.springframework.launcher.jar.RandomAccessJarFile;
-
/**
* Tests for {@link RandomAccessJarFile}.
*
* @author Phillip Webb
*/
-public class RandomAccessJarFileTest {
+public class RandomAccessJarFileTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
@@ -71,69 +64,11 @@ public class RandomAccessJarFileTest {
@Before
public void setup() throws Exception {
- this.rootJarFile = temporaryFolder.newFile();
- FileOutputStream fileOutputStream = new FileOutputStream(this.rootJarFile);
- JarOutputStream jarOutputStream = new JarOutputStream(fileOutputStream);
- try {
- writeManifest(jarOutputStream, "j1");
- writeEntry(jarOutputStream, "1.dat", 1);
- writeEntry(jarOutputStream, "2.dat", 2);
- writeDirEntry(jarOutputStream, "d/");
- writeEntry(jarOutputStream, "d/9.dat", 9);
-
- JarEntry nestedEntry = new JarEntry("nested.jar");
- byte[] nestedJarData = getNestedJarData();
- nestedEntry.setSize(nestedJarData.length);
- nestedEntry.setCompressedSize(nestedJarData.length);
- CRC32 crc32 = new CRC32();
- crc32.update(nestedJarData);
- nestedEntry.setCrc(crc32.getValue());
-
- nestedEntry.setMethod(ZipEntry.STORED);
- jarOutputStream.putNextEntry(nestedEntry);
- jarOutputStream.write(nestedJarData);
- jarOutputStream.closeEntry();
- }
- finally {
- jarOutputStream.close();
- }
+ this.rootJarFile = this.temporaryFolder.newFile();
+ TestJarCreator.createTestJar(this.rootJarFile);
this.jarFile = new RandomAccessJarFile(this.rootJarFile);
}
- private byte[] getNestedJarData() throws Exception {
- ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
- JarOutputStream jarOutputStream = new JarOutputStream(byteArrayOutputStream);
- writeManifest(jarOutputStream, "j2");
- writeEntry(jarOutputStream, "3.dat", 3);
- writeEntry(jarOutputStream, "4.dat", 4);
- jarOutputStream.close();
- return byteArrayOutputStream.toByteArray();
- }
-
- private void writeManifest(JarOutputStream jarOutputStream, String name)
- throws Exception {
- writeDirEntry(jarOutputStream, "META-INF/");
- Manifest manifest = new Manifest();
- manifest.getMainAttributes().putValue("Built-By", name);
- manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
- jarOutputStream.putNextEntry(new ZipEntry("META-INF/MANIFEST.MF"));
- manifest.write(jarOutputStream);
- jarOutputStream.closeEntry();
- }
-
- private void writeDirEntry(JarOutputStream jarOutputStream, String name)
- throws IOException {
- jarOutputStream.putNextEntry(new JarEntry(name));
- jarOutputStream.closeEntry();
- }
-
- private void writeEntry(JarOutputStream jarOutputStream, String name, int data)
- throws IOException {
- jarOutputStream.putNextEntry(new JarEntry(name));
- jarOutputStream.write(new byte[] { (byte) data });
- jarOutputStream.closeEntry();
- }
-
@Test
public void createFromFile() throws Exception {
RandomAccessJarFile jarFile = new RandomAccessJarFile(this.rootJarFile);
@@ -150,13 +85,13 @@ public class RandomAccessJarFileTest {
@Test
public void getManifest() throws Exception {
- assertThat(jarFile.getManifest().getMainAttributes().getValue("Built-By"),
+ assertThat(this.jarFile.getManifest().getMainAttributes().getValue("Built-By"),
equalTo("j1"));
}
@Test
public void getEntries() throws Exception {
- Enumeration entries = jarFile.entries();
+ Enumeration entries = this.jarFile.entries();
assertThat(entries.nextElement().getName(), equalTo("META-INF/"));
assertThat(entries.nextElement().getName(), equalTo("META-INF/MANIFEST.MF"));
assertThat(entries.nextElement().getName(), equalTo("1.dat"));
@@ -169,26 +104,27 @@ public class RandomAccessJarFileTest {
@Test
public void getJarEntry() throws Exception {
- JarEntry entry = jarFile.getJarEntry("1.dat");
+ JarEntry entry = this.jarFile.getJarEntry("1.dat");
assertThat(entry, notNullValue(ZipEntry.class));
assertThat(entry.getName(), equalTo("1.dat"));
}
@Test
public void getInputStream() throws Exception {
- InputStream inputStream = jarFile.getInputStream(jarFile.getEntry("1.dat"));
+ InputStream inputStream = this.jarFile.getInputStream(this.jarFile
+ .getEntry("1.dat"));
assertThat(inputStream.read(), equalTo(1));
assertThat(inputStream.read(), equalTo(-1));
}
@Test
public void getName() throws Exception {
- assertThat(jarFile.getName(), equalTo(rootJarFile.getPath()));
+ assertThat(this.jarFile.getName(), equalTo(this.rootJarFile.getPath()));
}
@Test
public void getSize() throws Exception {
- assertThat(jarFile.size(), equalTo((int) rootJarFile.length()));
+ assertThat(this.jarFile.size(), equalTo((int) this.rootJarFile.length()));
}
@Test
@@ -202,25 +138,26 @@ public class RandomAccessJarFileTest {
@Test
public void getUrl() throws Exception {
- URL url = jarFile.getUrl();
- assertThat(url.toString(), equalTo("jar:file:" + rootJarFile.getPath() + "!/"));
+ URL url = this.jarFile.getUrl();
+ assertThat(url.toString(), equalTo("jar:file:" + this.rootJarFile.getPath()
+ + "!/"));
JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
- assertThat(jarURLConnection.getJarFile(), sameInstance((JarFile) jarFile));
+ assertThat(jarURLConnection.getJarFile(), sameInstance((JarFile) this.jarFile));
assertThat(jarURLConnection.getJarEntry(), nullValue());
assertThat(jarURLConnection.getContentLength(), greaterThan(1));
- assertThat(jarURLConnection.getContent(), sameInstance((Object) jarFile));
+ assertThat(jarURLConnection.getContent(), sameInstance((Object) this.jarFile));
assertThat(jarURLConnection.getContentType(), equalTo("x-java/jar"));
}
@Test
public void getEntryUrl() throws Exception {
- URL url = new URL(jarFile.getUrl(), "1.dat");
- assertThat(url.toString(), equalTo("jar:file:" + rootJarFile.getPath()
+ URL url = new URL(this.jarFile.getUrl(), "1.dat");
+ assertThat(url.toString(), equalTo("jar:file:" + this.rootJarFile.getPath()
+ "!/1.dat"));
JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
- assertThat(jarURLConnection.getJarFile(), sameInstance((JarFile) jarFile));
+ assertThat(jarURLConnection.getJarFile(), sameInstance((JarFile) this.jarFile));
assertThat(jarURLConnection.getJarEntry(),
- sameInstance(jarFile.getJarEntry("1.dat")));
+ sameInstance(this.jarFile.getJarEntry("1.dat")));
assertThat(jarURLConnection.getContentLength(), equalTo(1));
assertThat(jarURLConnection.getContent(), instanceOf(InputStream.class));
assertThat(jarURLConnection.getContentType(), equalTo("content/unknown"));
@@ -228,24 +165,24 @@ public class RandomAccessJarFileTest {
@Test
public void getMissingEntryUrl() throws Exception {
- URL url = new URL(jarFile.getUrl(), "missing.dat");
- assertThat(url.toString(), equalTo("jar:file:" + rootJarFile.getPath()
+ URL url = new URL(this.jarFile.getUrl(), "missing.dat");
+ assertThat(url.toString(), equalTo("jar:file:" + this.rootJarFile.getPath()
+ "!/missing.dat"));
- thrown.expect(FileNotFoundException.class);
+ this.thrown.expect(FileNotFoundException.class);
((JarURLConnection) url.openConnection()).getJarEntry();
}
@Test
public void getUrlStream() throws Exception {
- URL url = jarFile.getUrl();
+ URL url = this.jarFile.getUrl();
url.openConnection();
- thrown.expect(IOException.class);
+ this.thrown.expect(IOException.class);
url.openStream();
}
@Test
public void getEntryUrlStream() throws Exception {
- URL url = new URL(jarFile.getUrl(), "1.dat");
+ URL url = new URL(this.jarFile.getUrl(), "1.dat");
url.openConnection();
InputStream stream = url.openStream();
assertThat(stream.read(), equalTo(1));
@@ -254,7 +191,7 @@ public class RandomAccessJarFileTest {
@Test
public void getNestedJarFile() throws Exception {
- RandomAccessJarFile nestedJarFile = jarFile.getNestedJarFile(jarFile
+ RandomAccessJarFile nestedJarFile = this.jarFile.getNestedJarFile(this.jarFile
.getEntry("nested.jar"));
Enumeration entries = nestedJarFile.entries();
@@ -270,7 +207,7 @@ public class RandomAccessJarFileTest {
assertThat(inputStream.read(), equalTo(-1));
URL url = nestedJarFile.getUrl();
- assertThat(url.toString(), equalTo("jar:file:" + rootJarFile.getPath()
+ assertThat(url.toString(), equalTo("jar:file:" + this.rootJarFile.getPath()
+ "!/nested.jar!/"));
assertThat(((JarURLConnection) url.openConnection()).getJarFile(),
sameInstance((JarFile) nestedJarFile));
@@ -278,7 +215,7 @@ public class RandomAccessJarFileTest {
@Test
public void getNestedJarDirectory() throws Exception {
- RandomAccessJarFile nestedJarFile = jarFile.getNestedJarFile(jarFile
+ RandomAccessJarFile nestedJarFile = this.jarFile.getNestedJarFile(this.jarFile
.getEntry("d/"));
Enumeration entries = nestedJarFile.entries();
@@ -291,28 +228,30 @@ public class RandomAccessJarFileTest {
assertThat(inputStream.read(), equalTo(-1));
URL url = nestedJarFile.getUrl();
- assertThat(url.toString(), equalTo("jar:file:" + rootJarFile.getPath() + "!/d!/"));
+ assertThat(url.toString(), equalTo("jar:file:" + this.rootJarFile.getPath()
+ + "!/d!/"));
assertThat(((JarURLConnection) url.openConnection()).getJarFile(),
sameInstance((JarFile) nestedJarFile));
}
@Test
public void getDirectoryInputStream() throws Exception {
- InputStream inputStream = jarFile.getInputStream(jarFile.getEntry("d/"));
+ InputStream inputStream = this.jarFile
+ .getInputStream(this.jarFile.getEntry("d/"));
assertThat(inputStream, notNullValue());
assertThat(inputStream.read(), equalTo(-1));
}
@Test
public void getDirectoryInputStreamWithoutSlash() throws Exception {
- InputStream inputStream = jarFile.getInputStream(jarFile.getEntry("d"));
+ InputStream inputStream = this.jarFile.getInputStream(this.jarFile.getEntry("d"));
assertThat(inputStream, notNullValue());
assertThat(inputStream.read(), equalTo(-1));
}
@Test
public void getFilteredJarFile() throws Exception {
- RandomAccessJarFile filteredJarFile = jarFile
+ RandomAccessJarFile filteredJarFile = this.jarFile
.getFilteredJarFile(new JarEntryFilter() {
@Override
public String apply(String entryName, JarEntry entry) {
@@ -334,8 +273,8 @@ public class RandomAccessJarFileTest {
@Test
public void sensibleToString() throws Exception {
- assertThat(jarFile.toString(), equalTo(rootJarFile.getPath()));
- assertThat(jarFile.getNestedJarFile(jarFile.getEntry("nested.jar")).toString(),
- equalTo(rootJarFile.getPath() + "!/nested.jar"));
+ assertThat(this.jarFile.toString(), equalTo(this.rootJarFile.getPath()));
+ assertThat(this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar"))
+ .toString(), equalTo(this.rootJarFile.getPath() + "!/nested.jar"));
}
}