190 lines
8.1 KiB
Java
190 lines
8.1 KiB
Java
|
package io.gitlab.jfronny.libjf.config.plugin;
|
||
|
|
||
|
import io.gitlab.jfronny.gson.stream.JsonReader;
|
||
|
import io.gitlab.jfronny.gson.stream.JsonWriter;
|
||
|
import io.gitlab.jfronny.libjf.config.plugin.asm.ConfigInjectClassTransformer;
|
||
|
import io.gitlab.jfronny.libjf.config.plugin.fmj.FabricModJsonTransformer;
|
||
|
import io.gitlab.jfronny.libjf.config.plugin.util.*;
|
||
|
import org.apache.tools.zip.*;
|
||
|
import org.gradle.api.GradleException;
|
||
|
import org.gradle.api.file.FileCopyDetails;
|
||
|
import org.objectweb.asm.*;
|
||
|
import org.objectweb.asm.util.CheckClassAdapter;
|
||
|
|
||
|
import java.io.*;
|
||
|
import java.util.*;
|
||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||
|
|
||
|
public class StreamAction {
|
||
|
private static final long CONSTANT_TIME_FOR_ZIP_ENTRIES = (new GregorianCalendar(1980, 1, 1, 0, 0, 0)).getTimeInMillis();
|
||
|
private final ZipOutputStream zipOutStr;
|
||
|
private final File zipFile;
|
||
|
private final boolean preserveFileTimestamps;
|
||
|
private final String modId;
|
||
|
private final Set<Type> knownConfigClasses;
|
||
|
|
||
|
public StreamAction(ZipOutputStream zipOutStr, File zipFile, boolean preserveFileTimestamps, String modId, Set<Type> knownConfigClasses) {
|
||
|
this.zipOutStr = zipOutStr;
|
||
|
this.zipFile = zipFile;
|
||
|
this.preserveFileTimestamps = preserveFileTimestamps;
|
||
|
this.modId = modId;
|
||
|
this.knownConfigClasses = knownConfigClasses;
|
||
|
}
|
||
|
|
||
|
public boolean processFile(FileCopyDetails details) {
|
||
|
try {
|
||
|
if (details.isDirectory()) {
|
||
|
visitDirectory(details);
|
||
|
return false;
|
||
|
} else {
|
||
|
return visitFile(details);
|
||
|
}
|
||
|
} catch (Exception e) {
|
||
|
throw new GradleException("Could not add " + details + " to ZIP " + zipFile, e);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void visitDirectory(FileCopyDetails details) throws IOException {
|
||
|
String path = details.getRelativePath().getPathString() + "/";
|
||
|
ZipEntry archiveEntry = new ZipEntry(path);
|
||
|
archiveEntry.setTime(getArchiveTimeFor(details.getLastModified()));
|
||
|
archiveEntry.setUnixMode(UnixStat.DIR_FLAG | details.getMode());
|
||
|
zipOutStr.putNextEntry(archiveEntry);
|
||
|
zipOutStr.closeEntry();
|
||
|
}
|
||
|
|
||
|
private boolean visitFile(FileCopyDetails details) throws IOException {
|
||
|
if (details.getPath().endsWith(".jar")) {
|
||
|
return processArchive(details);
|
||
|
} else if (details.getPath().endsWith(".class")) {
|
||
|
processClass(details);
|
||
|
} else if (details.getPath().equals("fabric.mod.json")) {
|
||
|
processFMJ(details);
|
||
|
return true;
|
||
|
} else {
|
||
|
ZipEntry archiveEntry = new ZipEntry(details.getRelativePath().getPathString());
|
||
|
archiveEntry.setTime(getArchiveTimeFor(details.getLastModified()));
|
||
|
archiveEntry.setUnixMode(UnixStat.FILE_FLAG | details.getMode());
|
||
|
zipOutStr.putNextEntry(archiveEntry);
|
||
|
details.copyTo(zipOutStr);
|
||
|
zipOutStr.closeEntry();
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
private boolean processArchive(FileCopyDetails details) throws IOException {
|
||
|
try (ZipFile archive = new ZipFile(details.getFile())) {
|
||
|
AtomicBoolean fmjFound = new AtomicBoolean(false);
|
||
|
EnumerationSpliterator.stream(archive.getEntries())
|
||
|
.map(RelativeArchivePath::new)
|
||
|
.map(ArchiveFileTreeElement::new)
|
||
|
.filter(it -> it.getRelativePath().isFile())
|
||
|
.forEach(archiveElement -> {
|
||
|
fmjFound.compareAndSet(false, visitArchiveFile(archiveElement, archive));
|
||
|
});
|
||
|
return fmjFound.get();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void visitArchiveDirectory(RelativeArchivePath archiveDir) throws IOException {
|
||
|
zipOutStr.putNextEntry(archiveDir.entry);
|
||
|
zipOutStr.closeEntry();
|
||
|
}
|
||
|
|
||
|
private boolean visitArchiveFile(ArchiveFileTreeElement archiveFile, ZipFile archive) {
|
||
|
RelativeArchivePath archiveFilePath = archiveFile.getRelativePath();
|
||
|
try {
|
||
|
if (archiveFile.isClassFile()) {
|
||
|
processClass(archiveFilePath, archive);
|
||
|
} else if (archiveFilePath.getPathString().equals("fabric.mod.json")) {
|
||
|
processFMJ(archiveFilePath, archive);
|
||
|
return true;
|
||
|
} else {
|
||
|
copyArchiveEntry(archiveFilePath, archive);
|
||
|
}
|
||
|
return false;
|
||
|
} catch (IOException e) {
|
||
|
throw new GradleException("Could not read archive entry " + archiveFilePath.getPathString(), e);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void copyArchiveEntry(RelativeArchivePath archiveFile, ZipFile archive) throws IOException {
|
||
|
ZipEntry entry = new ZipEntry(archiveFile.entry.getName());
|
||
|
entry.setTime(getArchiveTimeFor(archiveFile.entry.getTime()));
|
||
|
RelativeArchivePath path = new RelativeArchivePath(entry);
|
||
|
addParentDirectories(path);
|
||
|
zipOutStr.putNextEntry(path.entry);
|
||
|
try (InputStream is = archive.getInputStream(archiveFile.entry)) {
|
||
|
byte[] buffer = new byte[1024];
|
||
|
int n;
|
||
|
while (-1 != (n = is.read(buffer))) {
|
||
|
zipOutStr.write(buffer, 0, n);
|
||
|
}
|
||
|
}
|
||
|
zipOutStr.closeEntry();
|
||
|
}
|
||
|
|
||
|
private void addParentDirectories(RelativeArchivePath file) throws IOException {
|
||
|
if (file != null) {
|
||
|
addParentDirectories(file.getParent());
|
||
|
if (!file.isFile()) {
|
||
|
visitArchiveDirectory(file);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void processClass(RelativeArchivePath file, ZipFile archive) throws IOException {
|
||
|
ZipEntry zipEntry = new ZipEntry(file.getPathString());
|
||
|
addParentDirectories(new RelativeArchivePath(zipEntry));
|
||
|
processClass(archive.getInputStream(file.entry), file.getPathString(), file.entry.getTime());
|
||
|
}
|
||
|
|
||
|
private void processClass(FileCopyDetails details) throws IOException {
|
||
|
try (InputStream is = new BufferedInputStream(new FileInputStream(details.getFile()))) {
|
||
|
processClass(is, details.getPath(), details.getLastModified());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void processClass(InputStream classInputStream, String path, long lastModified) throws IOException {
|
||
|
final ClassReader reader = new ClassReader(classInputStream);
|
||
|
final ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_FRAMES);
|
||
|
reader.accept(new CheckClassAdapter(
|
||
|
new ConfigInjectClassTransformer(modId, writer, knownConfigClasses)
|
||
|
), ClassReader.EXPAND_FRAMES);
|
||
|
ZipEntry archiveEntry = new ZipEntry(path);
|
||
|
archiveEntry.setTime(getArchiveTimeFor(lastModified));
|
||
|
zipOutStr.putNextEntry(archiveEntry);
|
||
|
zipOutStr.write(writer.toByteArray());
|
||
|
zipOutStr.closeEntry();
|
||
|
}
|
||
|
|
||
|
private void processFMJ(RelativeArchivePath file, ZipFile archive) throws IOException {
|
||
|
ZipEntry zipEntry = new ZipEntry(file.getPathString());
|
||
|
addParentDirectories(new RelativeArchivePath(zipEntry));
|
||
|
processFMJ(archive.getInputStream(file.entry), file.getPathString(), file.entry.getTime());
|
||
|
}
|
||
|
|
||
|
private void processFMJ(FileCopyDetails details) throws IOException {
|
||
|
try (InputStream is = new BufferedInputStream(new FileInputStream(details.getFile()))) {
|
||
|
processFMJ(is, details.getPath(), details.getLastModified());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void processFMJ(InputStream fmj, String path, long lastModified) throws IOException {
|
||
|
ZipEntry archiveEntry = new ZipEntry(path);
|
||
|
archiveEntry.setTime(getArchiveTimeFor(lastModified));
|
||
|
zipOutStr.putNextEntry(archiveEntry);
|
||
|
// Leave this closeable open, as everything else will break the writer
|
||
|
|
||
|
try (JsonReader reader = new JsonReader(new InputStreamReader(fmj));
|
||
|
JsonWriter writer = new JsonWriter(new OutputStreamWriter(new DelegatingUncloseableOutputStream(zipOutStr)))) {
|
||
|
FabricModJsonTransformer.transform(reader, writer, knownConfigClasses);
|
||
|
}
|
||
|
zipOutStr.closeEntry();
|
||
|
}
|
||
|
|
||
|
private long getArchiveTimeFor(long timestamp) {
|
||
|
return preserveFileTimestamps ? timestamp : CONSTANT_TIME_FOR_ZIP_ENTRIES;
|
||
|
}
|
||
|
}
|