cc.shacocloud.mirage.loader.jar.JarFile Maven / Gradle / Ivy
package cc.shacocloud.mirage.loader.jar;
import cc.shacocloud.mirage.loader.data.RandomAccessData;
import cc.shacocloud.mirage.loader.data.RandomAccessDataFile;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.FilePermission;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.SoftReference;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;
import java.security.Permission;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Supplier;
import java.util.jar.Manifest;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import java.util.zip.ZipEntry;
/**
* {@link java.util.jar.JarFile} 的扩展变体
*/
public class JarFile extends AbstractJarFile implements Iterable {
private static final String MANIFEST_NAME = "META-INF/MANIFEST.MF";
private static final String PROTOCOL_HANDLER = "java.protocol.handler.pkgs";
private static final String HANDLERS_PACKAGE = "cc.shacocloud.mirage.loader.jar";
private static final AsciiBytes META_INF = new AsciiBytes("META-INF/");
private static final AsciiBytes SIGNATURE_FILE_EXTENSION = new AsciiBytes(".SF");
private static final String READ_ACTION = "read";
private final RandomAccessDataFile rootFile;
private final String pathFromRoot;
private final RandomAccessData data;
private final JarFileType type;
private URL url;
private String urlString;
private final JarFileEntries entries;
private final Supplier manifestSupplier;
private SoftReference manifest;
private boolean signed;
private String comment;
private volatile boolean closed;
private volatile JarFileWrapper wrapper;
/**
* 创建一个由指定文件支持的新 {@link JarFile}
*/
public JarFile(File file) throws IOException {
this(new RandomAccessDataFile(file));
}
/**
* 创建一个由指定文件支持的新 {@link JarFile}
*/
JarFile(RandomAccessDataFile file) throws IOException {
this(file, "", file, JarFileType.DIRECT);
}
/**
* 私有构造函数,用于直接或从嵌套条目创建新的 {@link JarFile}
*/
private JarFile(RandomAccessDataFile rootFile, String pathFromRoot, RandomAccessData data, JarFileType type)
throws IOException {
this(rootFile, pathFromRoot, data, null, type, null);
}
private JarFile(@NotNull RandomAccessDataFile rootFile, String pathFromRoot, RandomAccessData data, JarEntryFilter filter,
JarFileType type, Supplier manifestSupplier) throws IOException {
super(rootFile.getFile());
if (System.getSecurityManager() == null) {
super.close();
}
this.rootFile = rootFile;
this.pathFromRoot = pathFromRoot;
CentralDirectoryParser parser = new CentralDirectoryParser();
this.entries = parser.addVisitor(new JarFileEntries(this, filter));
this.type = type;
parser.addVisitor(centralDirectoryVisitor());
try {
this.data = parser.parse(data, filter == null);
} catch (RuntimeException ex) {
try {
this.rootFile.close();
super.close();
} catch (IOException ignore) {
}
throw ex;
}
this.manifestSupplier = (manifestSupplier != null) ? manifestSupplier : () -> {
try (InputStream inputStream = getInputStream(MANIFEST_NAME)) {
if (inputStream == null) {
return null;
}
return new Manifest(inputStream);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
};
}
@Contract(value = " -> new", pure = true)
private @NotNull CentralDirectoryVisitor centralDirectoryVisitor() {
return new CentralDirectoryVisitor() {
@Override
public void visitStart(CentralDirectoryEndRecord endRecord, RandomAccessData centralDirectoryData) {
JarFile.this.comment = endRecord.getComment();
}
@Override
public void visitFileHeader(CentralDirectoryFileHeader fileHeader, long dataOffset) {
AsciiBytes name = fileHeader.getName();
if (name.startsWith(META_INF) && name.endsWith(SIGNATURE_FILE_EXTENSION)) {
JarFile.this.signed = true;
}
}
@Override
public void visitEnd() {
}
};
}
JarFileWrapper getWrapper() throws IOException {
JarFileWrapper wrapper = this.wrapper;
if (wrapper == null) {
wrapper = new JarFileWrapper(this);
this.wrapper = wrapper;
}
return wrapper;
}
@Override
Permission getPermission() {
return new FilePermission(this.rootFile.getFile().getPath(), READ_ACTION);
}
protected final RandomAccessDataFile getRootJarFile() {
return this.rootFile;
}
RandomAccessData getData() {
return this.data;
}
@Override
public Manifest getManifest() throws IOException {
Manifest manifest = (this.manifest != null) ? this.manifest.get() : null;
if (manifest == null) {
try {
manifest = this.manifestSupplier.get();
} catch (RuntimeException ex) {
throw new IOException(ex);
}
this.manifest = new SoftReference<>(manifest);
}
return manifest;
}
@Override
public Enumeration entries() {
return new JarEntryEnumeration(this.entries.iterator());
}
@Override
public Stream stream() {
Spliterator spliterator = Spliterators.spliterator(iterator(), size(),
Spliterator.ORDERED | Spliterator.DISTINCT | Spliterator.IMMUTABLE | Spliterator.NONNULL);
return StreamSupport.stream(spliterator, false);
}
/**
* 返回所包含条目的迭代器
*
* @see Iterable#iterator()
*/
@Override
@SuppressWarnings({"unchecked", "rawtypes"})
public Iterator iterator() {
return (Iterator) this.entries.iterator(this::ensureOpen);
}
public JarEntry getJarEntry(CharSequence name) {
return this.entries.getEntry(name);
}
@Override
public JarEntry getJarEntry(String name) {
return (JarEntry) getEntry(name);
}
public boolean containsEntry(String name) {
return this.entries.containsEntry(name);
}
@Override
public ZipEntry getEntry(String name) {
ensureOpen();
return this.entries.getEntry(name);
}
@Override
InputStream getInputStream() throws IOException {
return this.data.getInputStream();
}
@Override
public synchronized InputStream getInputStream(ZipEntry entry) throws IOException {
ensureOpen();
if (entry instanceof JarEntry) {
return this.entries.getInputStream((JarEntry) entry);
}
return getInputStream((entry != null) ? entry.getName() : null);
}
InputStream getInputStream(String name) throws IOException {
return this.entries.getInputStream(name);
}
/**
* 返回从指定条目加载的嵌套 {@link JarFile}
*/
public synchronized JarFile getNestedJarFile(JarEntry entry) throws IOException {
try {
return createJarFileFromEntry(entry);
} catch (Exception ex) {
throw new IOException("无法打开嵌套的 jar 文件 '" + entry.getName() + "'", ex);
}
}
private JarFile createJarFileFromEntry(@NotNull JarEntry entry) throws IOException {
if (entry.isDirectory()) {
return createJarFileFromDirectoryEntry(entry);
}
return createJarFileFromFileEntry(entry);
}
@Contract("_ -> new")
private @NotNull JarFile createJarFileFromDirectoryEntry(@NotNull JarEntry entry) throws IOException {
AsciiBytes name = entry.getAsciiBytesName();
JarEntryFilter filter = (candidate) -> {
if (candidate.startsWith(name) && !candidate.equals(name)) {
return candidate.substring(name.length());
}
return null;
};
return new JarFile(this.rootFile, this.pathFromRoot + "!/" + entry.getName().substring(0, name.length() - 1),
this.data, filter, JarFileType.NESTED_DIRECTORY, this.manifestSupplier);
}
@Contract("_ -> new")
private @NotNull JarFile createJarFileFromFileEntry(@NotNull JarEntry entry) throws IOException {
if (entry.getMethod() != ZipEntry.STORED) {
throw new IllegalStateException("无法打开嵌套条目 '" + entry.getName() + "',它已被压缩," +
"嵌套的jar文件必须在不压缩的情况下存储。请检查用于创建可执行 jar 文件的机制!");
}
RandomAccessData entryData = this.entries.getEntryData(entry.getName());
return new JarFile(this.rootFile, this.pathFromRoot + "!/" + entry.getName(), entryData,
JarFileType.NESTED_JAR);
}
@Override
public String getComment() {
ensureOpen();
return this.comment;
}
@Override
public int size() {
ensureOpen();
return this.entries.getSize();
}
@Override
public void close() throws IOException {
if (this.closed) {
return;
}
super.close();
if (this.type == JarFileType.DIRECT) {
this.rootFile.close();
}
this.closed = true;
}
private void ensureOpen() {
if (this.closed) {
throw new IllegalStateException("jar 文件已关闭");
}
}
boolean isClosed() {
return this.closed;
}
String getUrlString() throws MalformedURLException {
if (this.urlString == null) {
this.urlString = getUrl().toString();
}
return this.urlString;
}
@Override
public URL getUrl() throws MalformedURLException {
if (this.url == null) {
String file = this.rootFile.getFile().toURI() + this.pathFromRoot + "!/";
file = file.replace("file:////", "file://");
this.url = new URL("jar", "", -1, file, new Handler(this));
}
return this.url;
}
@Override
public String toString() {
return getName();
}
@Override
public String getName() {
return this.rootFile.getFile() + this.pathFromRoot;
}
boolean isSigned() {
return this.signed;
}
JarEntryCertification getCertification(JarEntry entry) {
try {
return this.entries.getCertification(entry);
} catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
public void clearCache() {
this.entries.clearCache();
}
protected String getPathFromRoot() {
return this.pathFromRoot;
}
@Override
JarFileType getType() {
return this.type;
}
/**
* 注册一个 {@literal 'java.protocol.handler.pkgs'} 属性,以便找到 {@link URLStreamHandler} 来处理 jar URL。
*/
public static void registerUrlProtocolHandler() {
Handler.captureJarContextUrl();
String handlers = System.getProperty(PROTOCOL_HANDLER, "");
System.setProperty(PROTOCOL_HANDLER,
((handlers == null || handlers.isEmpty()) ? HANDLERS_PACKAGE : handlers + "|" + HANDLERS_PACKAGE));
resetCachedUrlHandlers();
}
/**
* 重置所有缓存的处理程序,以防已使用 jar 协,我们通过尝试设置空 {@link URLStreamHandlerFactory} 来重置处理程序,除了清除处理程序缓存外,它应该没有任何效果。
*/
private static void resetCachedUrlHandlers() {
try {
URL.setURLStreamHandlerFactory(null);
} catch (Error ignore) {
}
}
/**
* {@linkplain java.util.jar.JarEntry} 的 {@link Enumeration}
*/
private static class JarEntryEnumeration implements Enumeration {
private final Iterator iterator;
JarEntryEnumeration(Iterator iterator) {
this.iterator = iterator;
}
@Override
public boolean hasMoreElements() {
return this.iterator.hasNext();
}
@Override
public java.util.jar.JarEntry nextElement() {
return this.iterator.next();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy