com.alipay.sofa.ark.loader.archive.ExplodedArchive Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 com.alipay.sofa.ark.loader.archive;
import com.alipay.sofa.ark.spi.archive.Archive;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
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.List;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
/**
* {@link Archive} implementation backed by an exploded archive directory.
*
* @author Phillip Webb
* @author Andy Wilkinson
*/
public class ExplodedArchive implements Archive {
private static final Set SKIPPED_NAMES = new HashSet<>(Arrays.asList(".", ".."));
private final File root;
private final boolean recursive;
private File manifestFile;
private Manifest manifest;
/**
* Create a new {@link ExplodedArchive} instance.
* @param root the root folder
*/
public ExplodedArchive(File root) {
this(root, true);
}
/**
* Create a new {@link ExplodedArchive} instance.
* @param root the root folder
* @param recursive if recursive searching should be used to locate the manifest.
* Defaults to {@code true}, folders 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 folder " + 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 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);
}
@Override
public InputStream getInputStream(ZipEntry zipEntry) throws IOException {
throw new RuntimeException("unreachable invocation.");
}
@Override
public Iterator iterator() {
return new FileEntryIterator(this.root, this.recursive);
}
public Archive getNestedArchive(Entry entry) throws IOException {
File file = ((FileEntry) entry).getFile();
return (file.isDirectory() ? new ExplodedArchive(file) : new JarFileArchive(file));
}
@Override
public String toString() {
try {
return getUrl().toString();
} catch (Exception ex) {
return "exploded archive";
}
}
/**
* File based {@link Entry} {@link Iterator}.
*/
private static class FileEntryIterator implements Iterator {
private final Comparator entryComparator = new EntryComparator();
private final File root;
private final boolean recursive;
private final Deque> stack = new LinkedList<>();
private File current;
FileEntryIterator(File root, boolean recursive) {
this.root = root;
this.recursive = recursive;
this.stack.add(listFiles(root));
this.current = poll();
}
@Override
public boolean hasNext() {
return this.current != null;
}
@Override
public Entry next() {
if (this.current == 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);
}
private Iterator listFiles(File file) {
File[] files = file.listFiles();
if (files == null) {
return Collections. emptyList().iterator();
}
Arrays.sort(files, this.entryComparator);
return Arrays.asList(files).iterator();
}
private File poll() {
while (!this.stack.isEmpty()) {
while (this.stack.peek().hasNext()) {
File file = this.stack.peek().next();
if (!SKIPPED_NAMES.contains(file.getName())) {
return file;
}
}
this.stack.poll();
}
return null;
}
@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 {
@Override
public int compare(File o1, File o2) {
return o1.getAbsolutePath().compareTo(o2.getAbsolutePath());
}
}
}
/**
* {@link Entry} backed by a File.
*/
private static class FileEntry implements Entry {
private final String name;
private final File file;
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;
}
}
}