cn.taketoday.app.loader.archive.ExplodedArchive Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of infra-app-loader Show documentation
Show all versions of infra-app-loader Show documentation
TODAY Infrastructure Application Loader
/*
* Copyright 2017 - 2023 the original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see [http://www.gnu.org/licenses/]
*/
package cn.taketoday.app.loader.archive;
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.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.jar.Manifest;
/**
* {@link Archive} implementation backed by an exploded archive directory.
*
* @author Phillip Webb
* @author Andy Wilkinson
* @author Madhura Bhave
* @author Harry Yang
* @since 4.0
*/
public class ExplodedArchive implements Archive {
private static final Set SKIPPED_NAMES = new HashSet<>(Arrays.asList(".", ".."));
private final File root;
private final boolean recursive;
private final File manifestFile;
private Manifest manifest;
/**
* Create a new {@link ExplodedArchive} instance.
*
* @param root the root directory
*/
public ExplodedArchive(File root) {
this(root, true);
}
/**
* Create a new {@link ExplodedArchive} instance.
*
* @param root the root directory
* @param recursive if recursive searching should be used to locate the manifest.
* Defaults to {@code true}, directories with a large tree might want to set this to
* {@code false}.
*/
public ExplodedArchive(File root, boolean recursive) {
if (!root.exists() || !root.isDirectory()) {
throw new IllegalArgumentException("Invalid source directory " + root);
}
this.root = root;
this.recursive = recursive;
this.manifestFile = getManifestFile(root);
}
private File getManifestFile(File root) {
File metaInf = new File(root, "META-INF");
return new File(metaInf, "MANIFEST.MF");
}
@Override
public URL getUrl() throws MalformedURLException {
return this.root.toURI().toURL();
}
@Override
public Manifest getManifest() throws IOException {
if (this.manifest == null && this.manifestFile.exists()) {
try (FileInputStream inputStream = new FileInputStream(this.manifestFile)) {
this.manifest = new Manifest(inputStream);
}
}
return this.manifest;
}
@Override
public Iterator getNestedArchives(EntryFilter searchFilter, EntryFilter includeFilter) throws IOException {
return new ArchiveIterator(this.root, this.recursive, searchFilter, includeFilter);
}
@Override
public Iterator iterator() {
return new EntryIterator(this.root, this.recursive, null, null);
}
protected Archive getNestedArchive(Entry entry) {
File file = ((FileEntry) entry).getFile();
return (file.isDirectory() ? new ExplodedArchive(file) : new SimpleJarFileArchive((FileEntry) entry));
}
@Override
public boolean isExploded() {
return true;
}
@Override
public String toString() {
try {
return getUrl().toString();
}
catch (Exception ex) {
return "exploded archive";
}
}
/**
* File based {@link Entry} {@link Iterator}.
*/
private abstract static class AbstractIterator implements Iterator {
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 FileEntry current;
private final 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();
}
@Override
public boolean hasNext() {
return this.current != null;
}
@Override
public T next() {
FileEntry entry = this.current;
if (entry == null) {
throw new NoSuchElementException();
}
this.current = poll();
return adapt(entry);
}
private FileEntry poll() {
while (!this.stack.isEmpty()) {
while (this.stack.peek().hasNext()) {
File file = this.stack.peek().next();
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();
}
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");
}
protected abstract T adapt(FileEntry entry);
}
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));
}
}
/**
* {@link Entry} backed by a File.
*/
private static class FileEntry implements Entry {
private final String name;
private final File file;
private final URL url;
FileEntry(String name, File file, URL url) {
this.name = name;
this.file = file;
this.url = url;
}
File getFile() {
return this.file;
}
@Override
public boolean isDirectory() {
return this.file.isDirectory();
}
@Override
public String getName() {
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";
}
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy