diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ExecutableArchiveLauncher.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ExecutableArchiveLauncher.java index 19ace6d9bd..9228153a81 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ExecutableArchiveLauncher.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ExecutableArchiveLauncher.java @@ -17,8 +17,8 @@ package org.springframework.boot.loader; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; -import java.util.jar.JarEntry; import java.util.jar.Manifest; import org.springframework.boot.loader.archive.Archive; @@ -65,27 +65,64 @@ public abstract class ExecutableArchiveLauncher extends Launcher { } @Override - protected List getClassPathArchives() throws Exception { - List archives = new ArrayList<>(this.archive.getNestedArchives(this::isNestedArchive)); - postProcessClassPathArchives(archives); + protected Iterator getClassPathArchivesIterator() throws Exception { + Iterator archives = this.archive.getNestedArchives(this::isSearchCandidate, this::isNestedArchive); + if (isPostProcessingClassPathArchives()) { + archives = applyClassPathArchivePostProcessing(archives); + } return archives; } + private Iterator applyClassPathArchivePostProcessing(Iterator archives) throws Exception { + List list = new ArrayList(); + while (archives.hasNext()) { + list.add(archives.next()); + } + postProcessClassPathArchives(list); + return list.iterator(); + } + /** - * Determine if the specified {@link JarEntry} is a nested item that should be added - * to the classpath. The method is called once for each entry. - * @param entry the jar entry + * Determine if the specified entry is a a candidate for further searching. + * @param entry the entry to check + * @return {@code true} if the entry is a candidate for further searching + */ + protected boolean isSearchCandidate(Archive.Entry entry) { + return true; + } + + /** + * Determine if the specified entry is a nested item that should be added to the + * classpath. + * @param entry the entry to check * @return {@code true} if the entry is a nested item (jar or folder) */ protected abstract boolean isNestedArchive(Archive.Entry entry); + /** + * Return if post processing needs to be applied to the archives. For back + * compatibility this method returns {@true}, but subclasses that don't override + * {@link #postProcessClassPathArchives(List)} should provide an implementation that + * returns {@code false}. + * @return if the {@link #postProcessClassPathArchives(List)} method is implemented + */ + protected boolean isPostProcessingClassPathArchives() { + return true; + } + /** * Called to post-process archive entries before they are used. Implementations can * add and remove entries. * @param archives the archives * @throws Exception if the post processing fails + * @see #isPostProcessingClassPathArchives() */ protected void postProcessClassPathArchives(List archives) throws Exception { } + @Override + protected boolean supportsNestedJars() { + return this.archive.supportsNestedJars(); + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/JarLauncher.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/JarLauncher.java index d2f12c9e89..bcb6c75fda 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/JarLauncher.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/JarLauncher.java @@ -17,6 +17,7 @@ package org.springframework.boot.loader; import org.springframework.boot.loader.archive.Archive; +import org.springframework.boot.loader.archive.Archive.EntryFilter; /** * {@link Launcher} for JAR based archives. This launcher assumes that dependency jars are @@ -29,9 +30,12 @@ import org.springframework.boot.loader.archive.Archive; */ public class JarLauncher extends ExecutableArchiveLauncher { - static final String BOOT_INF_CLASSES = "BOOT-INF/classes/"; - - static final String BOOT_INF_LIB = "BOOT-INF/lib/"; + static final EntryFilter NESTED_ARCHIVE_ENTRY_FILTER = (entry) -> { + if (entry.isDirectory()) { + return entry.getName().equals("BOOT-INF/classes/"); + } + return entry.getName().startsWith("BOOT-INF/lib/"); + }; public JarLauncher() { } @@ -40,12 +44,19 @@ public class JarLauncher extends ExecutableArchiveLauncher { super(archive); } + @Override + protected boolean isPostProcessingClassPathArchives() { + return false; + } + + @Override + protected boolean isSearchCandidate(Archive.Entry entry) { + return entry.getName().startsWith("BOOT-INF/"); + } + @Override protected boolean isNestedArchive(Archive.Entry entry) { - if (entry.isDirectory()) { - return entry.getName().equals(BOOT_INF_CLASSES); - } - return entry.getName().startsWith(BOOT_INF_LIB); + return NESTED_ARCHIVE_ENTRY_FILTER.matches(entry); } public static void main(String[] args) throws Exception { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/Launcher.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/Launcher.java index d67c0c7048..7b100acf3b 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/Launcher.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/Launcher.java @@ -19,9 +19,11 @@ package org.springframework.boot.loader; import java.io.File; import java.net.URI; import java.net.URL; +import java.net.URLClassLoader; import java.security.CodeSource; import java.security.ProtectionDomain; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import org.springframework.boot.loader.archive.Archive; @@ -46,8 +48,10 @@ public abstract class Launcher { * @throws Exception if the application fails to launch */ protected void launch(String[] args) throws Exception { - JarFile.registerUrlProtocolHandler(); - ClassLoader classLoader = createClassLoader(getClassPathArchives()); + if (supportsNestedJars()) { + JarFile.registerUrlProtocolHandler(); + } + ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator()); launch(args, getMainClass(), classLoader); } @@ -56,11 +60,24 @@ public abstract class Launcher { * @param archives the archives * @return the classloader * @throws Exception if the classloader cannot be created + * @deprecated since 2.3.0 in favor of {@link #createClassLoader(Iterator)} */ + @Deprecated protected ClassLoader createClassLoader(List archives) throws Exception { - List urls = new ArrayList<>(archives.size()); - for (Archive archive : archives) { - urls.add(archive.getUrl()); + return createClassLoader(archives.iterator()); + } + + /** + * Create a classloader for the specified archives. + * @param archives the archives + * @return the classloader + * @throws Exception if the classloader cannot be created + * @since 2.3.0 + */ + protected ClassLoader createClassLoader(Iterator archives) throws Exception { + List urls = new ArrayList<>(50); + while (archives.hasNext()) { + urls.add(archives.next().getUrl()); } return createClassLoader(urls.toArray(new URL[0])); } @@ -72,7 +89,10 @@ public abstract class Launcher { * @throws Exception if the classloader cannot be created */ protected ClassLoader createClassLoader(URL[] urls) throws Exception { - return new LaunchedURLClassLoader(urls, getClass().getClassLoader()); + if (supportsNestedJars()) { + return new LaunchedURLClassLoader(urls, getClass().getClassLoader()); + } + return new URLClassLoader(urls, getClass().getClassLoader()); } /** @@ -109,8 +129,23 @@ public abstract class Launcher { * Returns the archives that will be used to construct the class path. * @return the class path archives * @throws Exception if the class path archives cannot be obtained + * @since 2.3.0 */ - protected abstract List getClassPathArchives() throws Exception; + protected Iterator getClassPathArchivesIterator() throws Exception { + return getClassPathArchives().iterator(); + } + + /** + * Returns the archives that will be used to construct the class path. + * @return the class path archives + * @throws Exception if the class path archives cannot be obtained + * @deprecated since 2.3.0 in favor of implementing + * {@link #getClassPathArchivesIterator()}. + */ + @Deprecated + protected List getClassPathArchives() throws Exception { + throw new IllegalStateException("Unexpected call to getClassPathArchives()"); + } protected final Archive createArchive() throws Exception { ProtectionDomain protectionDomain = getClass().getProtectionDomain(); @@ -127,4 +162,14 @@ public abstract class Launcher { return (root.isDirectory() ? new ExplodedArchive(root) : new JarFileArchive(root)); } + /** + * Returns if the launcher needs to support fully nested JARs. If this method returns + * {@code false} then only regular JARs are supported and the additional URL and + * ClassLoader support infrastructure will not be installed. + * @return if nested JARs are supported + */ + protected boolean supportsNestedJars() { + return true; + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/PropertiesLauncher.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/PropertiesLauncher.java index 75e48d507e..a7f8c38cc8 100755 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/PropertiesLauncher.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/PropertiesLauncher.java @@ -28,6 +28,7 @@ import java.net.URLConnection; import java.net.URLDecoder; import java.util.ArrayList; import java.util.Collections; +import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; @@ -447,12 +448,12 @@ public class PropertiesLauncher extends Launcher { } @Override - protected List getClassPathArchives() throws Exception { + protected Iterator getClassPathArchivesIterator() throws Exception { List lib = new ArrayList<>(); for (String path : this.paths) { for (Archive archive : getClassPathArchives(path)) { if (archive instanceof ExplodedArchive) { - List nested = new ArrayList<>(archive.getNestedArchives(new ArchiveEntryFilter())); + List nested = asList(archive.getNestedArchives(null, new ArchiveEntryFilter())); nested.add(0, archive); lib.addAll(nested); } @@ -462,7 +463,7 @@ public class PropertiesLauncher extends Launcher { } } addNestedEntries(lib); - return lib; + return lib.iterator(); } private List getClassPathArchives(String path) throws Exception { @@ -543,7 +544,7 @@ public class PropertiesLauncher extends Launcher { root = ""; } EntryFilter filter = new PrefixMatchingArchiveFilter(root); - List archives = new ArrayList<>(parent.getNestedArchives(filter)); + List archives = asList(parent.getNestedArchives(null, filter)); if (("".equals(root) || ".".equals(root)) && !path.endsWith(".jar") && parent != this.parent) { // You can't find the root with an entry filter so it has to be added // explicitly. But don't add the root of the parent archive. @@ -557,12 +558,10 @@ public class PropertiesLauncher extends Launcher { // directories, meaning we are running from an executable JAR. We add nested // entries from there with low priority (i.e. at end). try { - lib.addAll(this.parent.getNestedArchives((entry) -> { - if (entry.isDirectory()) { - return entry.getName().equals(JarLauncher.BOOT_INF_CLASSES); - } - return entry.getName().startsWith(JarLauncher.BOOT_INF_LIB); - })); + Iterator archives = this.parent.getNestedArchives(null, JarLauncher.NESTED_ARCHIVE_ENTRY_FILTER); + while (archives.hasNext()) { + lib.add(archives.next()); + } } catch (IOException ex) { // Ignore @@ -591,6 +590,14 @@ public class PropertiesLauncher extends Launcher { return path; } + private List asList(Iterator iterator) { + List list = new ArrayList(); + while (iterator.hasNext()) { + list.add(iterator.next()); + } + return list; + } + public static void main(String[] args) throws Exception { PropertiesLauncher launcher = new PropertiesLauncher(); args = launcher.getArgs(args); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/WarLauncher.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/WarLauncher.java index 09af7e4182..6f7d18ff5d 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/WarLauncher.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/WarLauncher.java @@ -17,6 +17,7 @@ package org.springframework.boot.loader; import org.springframework.boot.loader.archive.Archive; +import org.springframework.boot.loader.archive.Archive.Entry; /** * {@link Launcher} for WAR based archives. This launcher for standard WAR archives. @@ -29,14 +30,6 @@ import org.springframework.boot.loader.archive.Archive; */ public class WarLauncher extends ExecutableArchiveLauncher { - private static final String WEB_INF = "WEB-INF/"; - - private static final String WEB_INF_CLASSES = WEB_INF + "classes/"; - - private static final String WEB_INF_LIB = WEB_INF + "lib/"; - - private static final String WEB_INF_LIB_PROVIDED = WEB_INF + "lib-provided/"; - public WarLauncher() { } @@ -44,14 +37,22 @@ public class WarLauncher extends ExecutableArchiveLauncher { super(archive); } + @Override + protected boolean isPostProcessingClassPathArchives() { + return false; + } + + @Override + protected boolean isSearchCandidate(Entry entry) { + return entry.getName().startsWith("WEB-INF/"); + } + @Override public boolean isNestedArchive(Archive.Entry entry) { if (entry.isDirectory()) { - return entry.getName().equals(WEB_INF_CLASSES); - } - else { - return entry.getName().startsWith(WEB_INF_LIB) || entry.getName().startsWith(WEB_INF_LIB_PROVIDED); + return entry.getName().equals("WEB-INF/classes/"); } + return entry.getName().startsWith("WEB-INF/lib/") || entry.getName().startsWith("WEB-INF/lib-provided/"); } public static void main(String[] args) throws Exception { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/archive/Archive.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/archive/Archive.java index 9646e9499a..d06557a4d0 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/archive/Archive.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/archive/Archive.java @@ -19,6 +19,7 @@ package org.springframework.boot.loader.archive; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; +import java.util.Iterator; import java.util.List; import java.util.jar.Manifest; @@ -47,13 +48,44 @@ public interface Archive extends Iterable, AutoCloseable { */ Manifest getManifest() throws IOException; + /** + * Returns nested {@link Archive}s for entries that match the specified filters. + * @param searchFilter filter used to limit when additional sub-entry searching is + * required or {@code null} if all entries should be considered. + * @param includeFilter filter used to determine which entries should be included in + * the result or {@code null} if all entries should be included + * @return the nested archives + * @throws IOException on IO error + * @since 2.3.0 + */ + default Iterator getNestedArchives(EntryFilter searchFilter, EntryFilter includeFilter) + throws IOException { + EntryFilter combinedFilter = (entry) -> (searchFilter == null || searchFilter.matches(entry)) + && (includeFilter == null || includeFilter.matches(entry)); + List nestedArchives = getNestedArchives(combinedFilter); + return nestedArchives.iterator(); + } + /** * Returns nested {@link Archive}s for entries that match the specified filter. * @param filter the filter used to limit entries * @return nested archives * @throws IOException if nested archives cannot be read + * @deprecated since 2.3.0 in favor of + * {@link #getNestedArchives(EntryFilter, EntryFilter)} */ - List getNestedArchives(EntryFilter filter) throws IOException; + @Deprecated + default List getNestedArchives(EntryFilter filter) throws IOException { + throw new IllegalStateException("Unexpected call to getNestedArchives(filter)"); + } + + @Deprecated + @Override + Iterator iterator(); + + default boolean supportsNestedJars() { + return true; + } /** * Closes the {@code Archive}, releasing any open resources. @@ -87,6 +119,7 @@ public interface Archive extends Iterable, AutoCloseable { /** * Strategy interface to filter {@link Entry Entries}. */ + @FunctionalInterface interface EntryFilter { /** diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/archive/ExplodedArchive.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/archive/ExplodedArchive.java index e1c860d0c2..5af92f3b3e 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/archive/ExplodedArchive.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/archive/ExplodedArchive.java @@ -20,8 +20,8 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.net.MalformedURLException; +import java.net.URI; import java.net.URL; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; @@ -29,7 +29,6 @@ import java.util.Deque; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; -import java.util.List; import java.util.NoSuchElementException; import java.util.Set; import java.util.jar.Manifest; @@ -39,6 +38,7 @@ import java.util.jar.Manifest; * * @author Phillip Webb * @author Andy Wilkinson + * @author Madhura Bhave * @since 1.0.0 */ public class ExplodedArchive implements Archive { @@ -99,24 +99,23 @@ public class ExplodedArchive implements Archive { } @Override - public List getNestedArchives(EntryFilter filter) throws IOException { - List nestedArchives = new ArrayList<>(); - for (Entry entry : this) { - if (filter.matches(entry)) { - nestedArchives.add(getNestedArchive(entry)); - } - } - return Collections.unmodifiableList(nestedArchives); + public Iterator getNestedArchives(EntryFilter searchFilter, EntryFilter includeFilter) throws IOException { + return new ArchiveIterator(this.root, this.recursive, searchFilter, includeFilter); } @Override public Iterator iterator() { - return new FileEntryIterator(this.root, this.recursive); + return new EntryIterator(this.root, this.recursive, null, null); } protected Archive getNestedArchive(Entry entry) throws IOException { File file = ((FileEntry) entry).getFile(); - return (file.isDirectory() ? new ExplodedArchive(file) : new JarFileArchive(file)); + return (file.isDirectory() ? new ExplodedArchive(file) : new SimpleJarFileArchive((FileEntry) entry)); + } + + @Override + public boolean supportsNestedJars() { + return false; } @Override @@ -132,21 +131,30 @@ public class ExplodedArchive implements Archive { /** * File based {@link Entry} {@link Iterator}. */ - private static class FileEntryIterator implements Iterator { + private abstract static class AbstractIterator implements Iterator { - private final Comparator entryComparator = new EntryComparator(); + private static final Comparator entryComparator = Comparator.comparing(File::getAbsolutePath); private final File root; private final boolean recursive; + private final EntryFilter searchFilter; + + private final EntryFilter includeFilter; + private final Deque> stack = new LinkedList<>(); - private File current; + private FileEntry current; - FileEntryIterator(File root, boolean recursive) { + private String rootUrl; + + AbstractIterator(File root, boolean recursive, EntryFilter searchFilter, EntryFilter includeFilter) { this.root = root; + this.rootUrl = this.root.toURI().getPath(); this.recursive = recursive; + this.searchFilter = searchFilter; + this.includeFilter = includeFilter; this.stack.add(listFiles(root)); this.current = poll(); } @@ -157,34 +165,28 @@ public class ExplodedArchive implements Archive { } @Override - public Entry next() { - if (this.current == null) { + public T next() { + FileEntry entry = this.current; + if (entry == null) { throw new NoSuchElementException(); } - File file = this.current; - if (file.isDirectory() && (this.recursive || file.getParentFile().equals(this.root))) { - this.stack.addFirst(listFiles(file)); - } this.current = poll(); - String name = file.toURI().getPath().substring(this.root.toURI().getPath().length()); - return new FileEntry(name, file); + return adapt(entry); } - private Iterator listFiles(File file) { - File[] files = file.listFiles(); - if (files == null) { - return Collections.emptyIterator(); - } - Arrays.sort(files, this.entryComparator); - return Arrays.asList(files).iterator(); - } - - private File poll() { + private FileEntry poll() { while (!this.stack.isEmpty()) { while (this.stack.peek().hasNext()) { File file = this.stack.peek().next(); - if (!SKIPPED_NAMES.contains(file.getName())) { - return file; + if (SKIPPED_NAMES.contains(file.getName())) { + continue; + } + FileEntry entry = getFileEntry(file); + if (isListable(entry)) { + this.stack.addFirst(listFiles(file)); + } + if (this.includeFilter == null || this.includeFilter.matches(entry)) { + return entry; } } this.stack.poll(); @@ -192,21 +194,64 @@ public class ExplodedArchive implements Archive { return null; } + private FileEntry getFileEntry(File file) { + URI uri = file.toURI(); + String name = uri.getPath().substring(this.rootUrl.length()); + try { + return new FileEntry(name, file, uri.toURL()); + } + catch (MalformedURLException ex) { + throw new IllegalStateException(ex); + } + } + + private boolean isListable(FileEntry entry) { + return entry.isDirectory() && (this.recursive || entry.getFile().getParentFile().equals(this.root)) + && (this.searchFilter == null || this.searchFilter.matches(entry)) + && (this.includeFilter == null || !this.includeFilter.matches(entry)); + } + + private Iterator listFiles(File file) { + File[] files = file.listFiles(); + if (files == null) { + return Collections.emptyIterator(); + } + Arrays.sort(files, entryComparator); + return Arrays.asList(files).iterator(); + } + @Override public void remove() { throw new UnsupportedOperationException("remove"); } - /** - * {@link Comparator} that orders {@link File} entries by their absolute paths. - */ - private static class EntryComparator implements Comparator { + protected abstract T adapt(FileEntry entry); - @Override - public int compare(File o1, File o2) { - return o1.getAbsolutePath().compareTo(o2.getAbsolutePath()); - } + } + private static class EntryIterator extends AbstractIterator { + + EntryIterator(File root, boolean recursive, EntryFilter searchFilter, EntryFilter includeFilter) { + super(root, recursive, searchFilter, includeFilter); + } + + @Override + protected Entry adapt(FileEntry entry) { + return entry; + } + + } + + private static class ArchiveIterator extends AbstractIterator { + + ArchiveIterator(File root, boolean recursive, EntryFilter searchFilter, EntryFilter includeFilter) { + super(root, recursive, searchFilter, includeFilter); + } + + @Override + protected Archive adapt(FileEntry entry) { + File file = entry.getFile(); + return (file.isDirectory() ? new ExplodedArchive(file) : new SimpleJarFileArchive(entry)); } } @@ -220,9 +265,12 @@ public class ExplodedArchive implements Archive { private final File file; - FileEntry(String name, File file) { + private final URL url; + + FileEntry(String name, File file, URL url) { this.name = name; this.file = file; + this.url = url; } File getFile() { @@ -239,6 +287,55 @@ public class ExplodedArchive implements Archive { return this.name; } + URL getUrl() { + return this.url; + } + + } + + /** + * {@link Archive} implementation backed by a simple JAR file that doesn't itself + * contain nested archives. + */ + private static class SimpleJarFileArchive implements Archive { + + private final URL url; + + SimpleJarFileArchive(FileEntry file) { + this.url = file.getUrl(); + } + + @Override + public URL getUrl() throws MalformedURLException { + return this.url; + } + + @Override + public Manifest getManifest() throws IOException { + return null; + } + + @Override + public Iterator getNestedArchives(EntryFilter searchFilter, EntryFilter includeFilter) + throws IOException { + return Collections.emptyIterator(); + } + + @Override + public Iterator iterator() { + return Collections.emptyIterator(); + } + + @Override + public String toString() { + try { + return getUrl().toString(); + } + catch (Exception ex) { + return "jar archive"; + } + } + } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/archive/JarFileArchive.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/archive/JarFileArchive.java index 998c0b76f4..5c8625fd9d 100755 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/archive/JarFileArchive.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/archive/JarFileArchive.java @@ -23,11 +23,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.net.MalformedURLException; import java.net.URL; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Enumeration; import java.util.Iterator; -import java.util.List; import java.util.UUID; import java.util.jar.JarEntry; import java.util.jar.Manifest; @@ -80,19 +76,13 @@ public class JarFileArchive implements Archive { } @Override - public List getNestedArchives(EntryFilter filter) throws IOException { - List nestedArchives = new ArrayList<>(); - for (Entry entry : this) { - if (filter.matches(entry)) { - nestedArchives.add(getNestedArchive(entry)); - } - } - return Collections.unmodifiableList(nestedArchives); + public Iterator getNestedArchives(EntryFilter searchFilter, EntryFilter includeFilter) throws IOException { + return new NestedArchiveIterator(this.jarFile.iterator(), searchFilter, includeFilter); } @Override public Iterator iterator() { - return new EntryIterator(this.jarFile.entries()); + return new EntryIterator(this.jarFile.iterator(), null, null); } @Override @@ -169,29 +159,85 @@ public class JarFileArchive implements Archive { } /** - * {@link Archive.Entry} iterator implementation backed by {@link JarEntry}. + * Abstract base class for iterator implementations. */ - private static class EntryIterator implements Iterator { + private abstract static class AbstractIterator implements Iterator { - private final Enumeration enumeration; + private final Iterator iterator; - EntryIterator(Enumeration enumeration) { - this.enumeration = enumeration; + private final EntryFilter searchFilter; + + private final EntryFilter includeFilter; + + private Entry current; + + AbstractIterator(Iterator iterator, EntryFilter searchFilter, EntryFilter includeFilter) { + this.iterator = iterator; + this.searchFilter = searchFilter; + this.includeFilter = includeFilter; + this.current = poll(); } @Override public boolean hasNext() { - return this.enumeration.hasMoreElements(); + return this.current != null; } @Override - public Entry next() { - return new JarFileEntry(this.enumeration.nextElement()); + public T next() { + T result = adapt(this.current); + this.current = poll(); + return result; + } + + private Entry poll() { + while (this.iterator.hasNext()) { + JarFileEntry candidate = new JarFileEntry(this.iterator.next()); + if ((this.searchFilter == null || this.searchFilter.matches(candidate)) + && (this.includeFilter == null || this.includeFilter.matches(candidate))) { + return candidate; + } + } + return null; + } + + protected abstract T adapt(Entry entry); + + } + + /** + * {@link Archive.Entry} iterator implementation backed by {@link JarEntry}. + */ + private static class EntryIterator extends AbstractIterator { + + EntryIterator(Iterator iterator, EntryFilter searchFilter, EntryFilter includeFilter) { + super(iterator, searchFilter, includeFilter); } @Override - public void remove() { - throw new UnsupportedOperationException("remove"); + protected Entry adapt(Entry entry) { + return entry; + } + + } + + /** + * Nested {@link Archive} iterator implementation backed by {@link JarEntry}. + */ + private class NestedArchiveIterator extends AbstractIterator { + + NestedArchiveIterator(Iterator iterator, EntryFilter searchFilter, EntryFilter includeFilter) { + super(iterator, searchFilter, includeFilter); + } + + @Override + protected Archive adapt(Entry entry) { + try { + return getNestedArchive(entry); + } + catch (IOException ex) { + throw new IllegalStateException(ex); + } } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarFile.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarFile.java index 26d62978eb..b58434aa6e 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarFile.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarFile.java @@ -48,7 +48,7 @@ import org.springframework.boot.loader.data.RandomAccessDataFile; * @author Andy Wilkinson * @since 1.0.0 */ -public class JarFile extends java.util.jar.JarFile { +public class JarFile extends java.util.jar.JarFile implements Iterable { private static final String MANIFEST_NAME = "META-INF/MANIFEST.MF"; @@ -191,7 +191,7 @@ public class JarFile extends java.util.jar.JarFile { @Override public Enumeration entries() { - final Iterator iterator = this.entries.iterator(); + Iterator iterator = iterator(); return new Enumeration() { @Override @@ -207,6 +207,17 @@ public class JarFile extends java.util.jar.JarFile { }; } + /** + * Return an iterator for the contained entries. + * @see java.lang.Iterable#iterator() + * @since 2.3.0 + */ + @Override + @SuppressWarnings({ "unchecked", "rawtypes" }) + public Iterator iterator() { + return (Iterator) this.entries.iterator(); + } + public JarEntry getJarEntry(CharSequence name) { return this.entries.getEntry(name); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/JarLauncherTests.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/JarLauncherTests.java index fa171150f5..d892b564a5 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/JarLauncherTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/JarLauncherTests.java @@ -20,6 +20,7 @@ import java.io.File; import java.net.URL; import java.util.List; +import org.codehaus.plexus.util.CollectionUtils; import org.junit.jupiter.api.Test; import org.springframework.boot.loader.archive.Archive; @@ -39,7 +40,7 @@ class JarLauncherTests extends AbstractExecutableArchiveLauncherTests { void explodedJarHasOnlyBootInfClassesAndContentsOfBootInfLibOnClasspath() throws Exception { File explodedRoot = explode(createJarArchive("archive.jar", "BOOT-INF")); JarLauncher launcher = new JarLauncher(new ExplodedArchive(explodedRoot, true)); - List archives = launcher.getClassPathArchives(); + List archives = CollectionUtils.iteratorToList(launcher.getClassPathArchivesIterator()); assertThat(archives).hasSize(2); assertThat(getUrls(archives)).containsOnly(new File(explodedRoot, "BOOT-INF/classes").toURI().toURL(), new File(explodedRoot, "BOOT-INF/lib/foo.jar").toURI().toURL()); @@ -53,7 +54,7 @@ class JarLauncherTests extends AbstractExecutableArchiveLauncherTests { File jarRoot = createJarArchive("archive.jar", "BOOT-INF"); try (JarFileArchive archive = new JarFileArchive(jarRoot)) { JarLauncher launcher = new JarLauncher(archive); - List classPathArchives = launcher.getClassPathArchives(); + List classPathArchives = CollectionUtils.iteratorToList(launcher.getClassPathArchivesIterator()); assertThat(classPathArchives).hasSize(2); assertThat(getUrls(classPathArchives)).containsOnly( new URL("jar:" + jarRoot.toURI().toURL() + "!/BOOT-INF/classes!/"), diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/PropertiesLauncherTests.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/PropertiesLauncherTests.java index 26c1b006f3..5c65cbc29f 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/PropertiesLauncherTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/PropertiesLauncherTests.java @@ -30,6 +30,7 @@ import java.util.jar.Manifest; import org.assertj.core.api.Condition; import org.awaitility.Awaitility; +import org.codehaus.plexus.util.CollectionUtils; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -139,7 +140,7 @@ class PropertiesLauncherTests { System.setProperty("loader.path", "jars/"); PropertiesLauncher launcher = new PropertiesLauncher(); assertThat(ReflectionTestUtils.getField(launcher, "paths").toString()).isEqualTo("[jars/]"); - List archives = launcher.getClassPathArchives(); + List archives = CollectionUtils.iteratorToList(launcher.getClassPathArchivesIterator()); assertThat(archives).areExactly(1, endingWith("app.jar")); } @@ -169,7 +170,7 @@ class PropertiesLauncherTests { PropertiesLauncher launcher = new PropertiesLauncher(); assertThat(ReflectionTestUtils.getField(launcher, "paths").toString()) .isEqualTo("[jar:file:./src/test/resources/nested-jars/app.jar!/]"); - List archives = launcher.getClassPathArchives(); + List archives = CollectionUtils.iteratorToList(launcher.getClassPathArchivesIterator()); assertThat(archives).areExactly(1, endingWith("foo.jar!/")); assertThat(archives).areExactly(1, endingWith("app.jar")); } @@ -178,7 +179,7 @@ class PropertiesLauncherTests { void testUserSpecifiedRootOfJarPathWithDot() throws Exception { System.setProperty("loader.path", "nested-jars/app.jar!/./"); PropertiesLauncher launcher = new PropertiesLauncher(); - List archives = launcher.getClassPathArchives(); + List archives = CollectionUtils.iteratorToList(launcher.getClassPathArchivesIterator()); assertThat(archives).areExactly(1, endingWith("foo.jar!/")); assertThat(archives).areExactly(1, endingWith("app.jar")); } @@ -187,7 +188,7 @@ class PropertiesLauncherTests { void testUserSpecifiedRootOfJarPathWithDotAndJarPrefix() throws Exception { System.setProperty("loader.path", "jar:file:./src/test/resources/nested-jars/app.jar!/./"); PropertiesLauncher launcher = new PropertiesLauncher(); - List archives = launcher.getClassPathArchives(); + List archives = CollectionUtils.iteratorToList(launcher.getClassPathArchivesIterator()); assertThat(archives).areExactly(1, endingWith("foo.jar!/")); } @@ -196,7 +197,7 @@ class PropertiesLauncherTests { System.setProperty("loader.path", "nested-jars/app.jar"); System.setProperty("loader.main", "demo.Application"); PropertiesLauncher launcher = new PropertiesLauncher(); - List archives = launcher.getClassPathArchives(); + List archives = CollectionUtils.iteratorToList(launcher.getClassPathArchivesIterator()); assertThat(archives).areExactly(1, endingWith("foo.jar!/")); assertThat(archives).areExactly(1, endingWith("app.jar")); } @@ -206,7 +207,7 @@ class PropertiesLauncherTests { System.setProperty("loader.path", "nested-jars/app.jar!/foo.jar"); System.setProperty("loader.main", "demo.Application"); PropertiesLauncher launcher = new PropertiesLauncher(); - List archives = launcher.getClassPathArchives(); + List archives = CollectionUtils.iteratorToList(launcher.getClassPathArchivesIterator()); assertThat(archives).hasSize(1).areExactly(1, endingWith("foo.jar!/")); } @@ -335,7 +336,7 @@ class PropertiesLauncherTests { loaderPath.mkdir(); System.setProperty("loader.path", loaderPath.toURI().toURL().toString()); PropertiesLauncher launcher = new PropertiesLauncher(); - List archives = launcher.getClassPathArchives(); + List archives = CollectionUtils.iteratorToList(launcher.getClassPathArchivesIterator()); assertThat(archives.size()).isEqualTo(1); File archiveRoot = (File) ReflectionTestUtils.getField(archives.get(0), "root"); assertThat(archiveRoot).isEqualTo(loaderPath); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/WarLauncherTests.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/WarLauncherTests.java index 7b68a50d96..eede7587a4 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/WarLauncherTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/WarLauncherTests.java @@ -20,6 +20,7 @@ import java.io.File; import java.net.URL; import java.util.List; +import org.codehaus.plexus.util.CollectionUtils; import org.junit.jupiter.api.Test; import org.springframework.boot.loader.archive.Archive; @@ -39,7 +40,7 @@ class WarLauncherTests extends AbstractExecutableArchiveLauncherTests { void explodedWarHasOnlyWebInfClassesAndContentsOfWebInfLibOnClasspath() throws Exception { File explodedRoot = explode(createJarArchive("archive.war", "WEB-INF")); WarLauncher launcher = new WarLauncher(new ExplodedArchive(explodedRoot, true)); - List archives = launcher.getClassPathArchives(); + List archives = CollectionUtils.iteratorToList(launcher.getClassPathArchivesIterator()); assertThat(archives).hasSize(2); assertThat(getUrls(archives)).containsOnly(new File(explodedRoot, "WEB-INF/classes").toURI().toURL(), new File(explodedRoot, "WEB-INF/lib/foo.jar").toURI().toURL()); @@ -53,7 +54,7 @@ class WarLauncherTests extends AbstractExecutableArchiveLauncherTests { File jarRoot = createJarArchive("archive.war", "WEB-INF"); try (JarFileArchive archive = new JarFileArchive(jarRoot)) { WarLauncher launcher = new WarLauncher(archive); - List classPathArchives = launcher.getClassPathArchives(); + List classPathArchives = CollectionUtils.iteratorToList(launcher.getClassPathArchivesIterator()); assertThat(classPathArchives).hasSize(2); assertThat(getUrls(classPathArchives)).containsOnly( new URL("jar:" + jarRoot.toURI().toURL() + "!/WEB-INF/classes!/"), diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/archive/ExplodedArchiveTests.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/archive/ExplodedArchiveTests.java index 23aead134d..85b16be994 100755 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/archive/ExplodedArchiveTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/archive/ExplodedArchiveTests.java @@ -74,7 +74,6 @@ class ExplodedArchiveTests { private void createArchive(String folderName) throws Exception { File file = new File(this.tempDir, "test.jar"); TestJarCreator.createTestJar(file); - this.rootFolder = (StringUtils.hasText(folderName) ? new File(this.tempDir, folderName) : new File(this.tempDir, UUID.randomUUID().toString())); JarFile jarFile = new JarFile(file); @@ -102,7 +101,7 @@ class ExplodedArchiveTests { @Test void getEntries() { Map entries = getEntriesMap(this.archive); - assertThat(entries.size()).isEqualTo(12); + assertThat(entries).hasSize(12); } @Test @@ -121,7 +120,7 @@ class ExplodedArchiveTests { Entry entry = getEntriesMap(this.archive).get("nested.jar"); Archive nested = this.archive.getNestedArchive(entry); assertThat(nested.getUrl().toString()).isEqualTo(this.rootFolder.toURI() + "nested.jar"); - ((JarFileArchive) nested).close(); + nested.close(); } @Test