diff --git a/src/main/java/net/fabricmc/tinyremapper/ClassInstance.java b/src/main/java/net/fabricmc/tinyremapper/ClassInstance.java index 8c57af22..d9e624b3 100644 --- a/src/main/java/net/fabricmc/tinyremapper/ClassInstance.java +++ b/src/main/java/net/fabricmc/tinyremapper/ClassInstance.java @@ -18,7 +18,6 @@ package net.fabricmc.tinyremapper; -import java.nio.file.Path; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; @@ -51,12 +50,12 @@ import net.fabricmc.tinyremapper.api.TrMethod; public final class ClassInstance implements TrClass { - ClassInstance(TinyRemapper tr, boolean isInput, InputTag[] inputTags, Path srcFile, byte[] data) { + ClassInstance(TinyRemapper tr, boolean isInput, InputTag[] inputTags, String source, byte[] data) { assert !isInput || data != null; this.tr = tr; this.isInput = isInput; this.inputTags = inputTags; - this.srcPath = srcFile; + this.source = source; this.data = data; this.mrjOrigin = this; } @@ -837,7 +836,7 @@ private static void addMatching(T member, String name, Stri ClassInstance constructMrjCopy(MrjState newContext) { // isInput should be false, since the MRJ copy should not be emitted - ClassInstance copy = new ClassInstance(tr, false, inputTags, srcPath, data); + ClassInstance copy = new ClassInstance(tr, false, inputTags, source, data); copy.init(mrjVersion, name, signature, superName, access, interfaces); copy.setContext(newContext); @@ -870,6 +869,10 @@ public static String getMrjName(String clsName, int mrjVersion) { } } + /** + * @deprecated Please use {@link TrEnvironment#DEFAULT_MRJ_VERSION}. + */ + @Deprecated public static final int MRJ_DEFAULT = -1; public static final String MRJ_PREFIX = "/META-INF/versions"; @@ -882,7 +885,7 @@ public static String getMrjName(String clsName, int mrjVersion) { final boolean isInput; private volatile InputTag[] inputTags; // cow input tag list, null for none - final Path srcPath; + final String source; byte[] data; private ClassInstance mrjOrigin; private final Map members = new HashMap<>(); // methods and fields are distinct due to their different desc separators diff --git a/src/main/java/net/fabricmc/tinyremapper/FileSystemHandler.java b/src/main/java/net/fabricmc/tinyremapper/FileSystemHandler.java index 4cb6eaac..927b1667 100644 --- a/src/main/java/net/fabricmc/tinyremapper/FileSystemHandler.java +++ b/src/main/java/net/fabricmc/tinyremapper/FileSystemHandler.java @@ -20,10 +20,12 @@ import java.io.IOException; import java.net.URI; +import java.net.URISyntaxException; import java.nio.file.FileSystem; import java.nio.file.FileSystemAlreadyExistsException; import java.nio.file.FileSystemNotFoundException; import java.nio.file.FileSystems; +import java.nio.file.Path; import java.util.Collections; import java.util.IdentityHashMap; import java.util.Map; @@ -36,6 +38,14 @@ * invocations are mirrored. */ public final class FileSystemHandler { + public static synchronized FileSystem open(Path path) throws IOException { + try { + return open(new URI("jar:" + path.toUri())); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } + public static synchronized FileSystem open(URI uri) throws IOException { boolean opened = false; FileSystem ret = null; diff --git a/src/main/java/net/fabricmc/tinyremapper/TinyRemapper.java b/src/main/java/net/fabricmc/tinyremapper/TinyRemapper.java index 029dbe09..e61b3930 100644 --- a/src/main/java/net/fabricmc/tinyremapper/TinyRemapper.java +++ b/src/main/java/net/fabricmc/tinyremapper/TinyRemapper.java @@ -19,14 +19,8 @@ package net.fabricmc.tinyremapper; import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.file.FileSystem; -import java.nio.file.FileVisitResult; -import java.nio.file.Files; +import java.io.UncheckedIOException; import java.nio.file.Path; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; @@ -49,10 +43,10 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiConsumer; -import java.util.function.Supplier; +import java.util.function.Function; +import java.util.function.Predicate; import java.util.regex.Pattern; import java.util.stream.Collectors; -import java.util.zip.ZipError; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; @@ -69,6 +63,8 @@ import net.fabricmc.tinyremapper.api.TrEnvironment; import net.fabricmc.tinyremapper.api.TrMember; import net.fabricmc.tinyremapper.api.TrMember.MemberType; +import net.fabricmc.tinyremapper.api.io.InputSupplier; +import net.fabricmc.tinyremapper.util.PathInputSupplier; public class TinyRemapper { public static class Builder { @@ -332,20 +328,36 @@ public InputTag createInputTag() { return ret; } - public void readInputs(final Path... inputs) { - readInputs(null, inputs); + /** + * @deprecated Unstable API + */ + @Deprecated + public void readInputs(InputSupplier... inputs) { + readInputsAsync(inputs).join(); } - public void readInputs(InputTag tag, Path... inputs) { - read(inputs, true, tag).join(); + /** + * @deprecated Unstable API + */ + @Deprecated + public void readInputs(InputTag tag, InputSupplier... inputs) { + readInputsAsync(tag, inputs).join(); } - public CompletableFuture readInputsAsync(Path... inputs) { + /** + * @deprecated Unstable API + */ + @Deprecated + public CompletableFuture readInputsAsync(InputSupplier... inputs) { return readInputsAsync(null, inputs); } - public CompletableFuture readInputsAsync(InputTag tag, Path... inputs) { - CompletableFuture ret = read(inputs, true, tag); + /** + * @deprecated Unstable API + */ + @Deprecated + public CompletableFuture readInputsAsync(InputTag tag, InputSupplier... inputs) { + CompletableFuture ret = readAsync0(true, tag, inputs); if (!ret.isDone()) { pendingReads.add(ret); @@ -356,12 +368,20 @@ public CompletableFuture readInputsAsync(InputTag tag, Path... inputs) { return ret; } - public void readClassPath(final Path... inputs) { - read(inputs, false, null).join(); + /** + * @deprecated Unstable API + */ + @Deprecated + public void readClasspath(InputSupplier... inputs) { + readClasspathAsync(inputs).join(); } - public CompletableFuture readClassPathAsync(final Path... inputs) { - CompletableFuture ret = read(inputs, false, null); + /** + * @deprecated Unstable API + */ + @Deprecated + public CompletableFuture readClasspathAsync(InputSupplier... inputs) { + CompletableFuture ret = readAsync0(false, null, inputs); if (!ret.isDone()) { pendingReads.add(ret); @@ -372,24 +392,21 @@ public CompletableFuture readClassPathAsync(final Path... inputs) { return ret; } - private CompletableFuture> read(Path[] inputs, boolean isInput, InputTag tag) { + private CompletableFuture readAsync0(boolean isInput, InputTag tag, InputSupplier... inputs) { InputTag[] tags = singleInputTags.get().get(tag); - List>> futures = new ArrayList<>(); - List fsToClose = Collections.synchronizedList(new ArrayList<>()); - for (Path input : inputs) { - futures.addAll(read(input, isInput, tags, true, fsToClose)); - } + Collection> futures = new ArrayList<>(); - CompletableFuture> ret; + for (InputSupplier input : inputs) { + String source = input.getSource(); - if (futures.isEmpty()) { - return CompletableFuture.completedFuture(Collections.emptyList()); - } else if (futures.size() == 1) { - ret = futures.get(0); - } else { - ret = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) - .thenApply(ignore -> futures.stream().flatMap(f -> f.join().stream()).collect(Collectors.toList())); + futures.add(CompletableFuture.supplyAsync(() -> { + try { + return input.read(classFileNameFilter, threadPool, (path, data) -> analyze(isInput, tags, source, path, data)); + } catch (IOException e) { + throw new UncheckedIOException("Error reading "+input, e); + } + }).thenCompose(Function.identity())); } if (!dirty) { @@ -400,23 +417,52 @@ private CompletableFuture> read(Path[] inputs, boolean isInp } } - return ret.whenComplete((res, exc) -> { - for (FileSystem fs : fsToClose) { - try { - FileSystemHandler.close(fs); - } catch (IOException e) { - // ignore - } + return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); + } + + /** Path overloads. **/ + public void readInputs(final Path... inputs) { + readInputsAsync(inputs).join(); + } + + public void readInputs(InputTag tag, Path... inputs) { + readInputsAsync(tag, inputs).join(); + } + + public CompletableFuture readInputsAsync(Path... inputs) { + return readInputsAsync(null, inputs); + } + + public CompletableFuture readInputsAsync(InputTag tag, Path... paths) { + InputSupplier[] inputs = new InputSupplier[paths.length]; + + for (int i = 0; i < paths.length; i++) { + try { + inputs[i] = new PathInputSupplier(paths[i]); + } catch (IOException e) { + throw new RuntimeException(e); } + } - if (res != null) { - for (ClassInstance node : res) { - addClass(node, readClasses, true); - } + return readInputsAsync(tag, inputs); + } + + public void readClassPath(final Path... paths) { + readClassPathAsync(paths).join(); + } + + public CompletableFuture readClassPathAsync(final Path... paths) { + InputSupplier[] inputs = new InputSupplier[paths.length]; + + for (int i = 0; i < paths.length; i += 1) { + try { + inputs[i] = new PathInputSupplier(paths[i]); + } catch (IOException e) { + throw new RuntimeException(e); } + } - assert dirty; - }); + return readClasspathAsync(inputs); } private static void addClass(ClassInstance cls, Map out, boolean isVersionAware) { @@ -438,7 +484,7 @@ private static void addClass(ClassInstance cls, Map out, } } else if (cls.isInput) { if (prev.isInput) { - System.out.printf("duplicate input class %s, from %s and %s%n", name, prev.srcPath, cls.srcPath); + System.out.printf("duplicate input class %s, from %s and %s%n", name, prev.source, cls.source); prev.addInputTags(cls.getInputTags()); return; } else if (out.replace(name, prev, cls)) { // cas with retry-loop on failure @@ -454,76 +500,6 @@ private static void addClass(ClassInstance cls, Map out, } } - private List>> read(final Path file, boolean isInput, InputTag[] tags, - boolean saveData, final List fsToClose) { - try { - return read(file, isInput, tags, file, saveData, fsToClose); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - private List>> read(final Path file, boolean isInput, InputTag[] tags, final Path srcPath, - final boolean saveData, final List fsToClose) throws IOException { - List>> ret = new ArrayList<>(); - - Files.walkFileTree(file, new SimpleFileVisitor() { - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - String name = file.getFileName().toString(); - - if (name.endsWith(".jar") - || name.endsWith(".zip") - || name.endsWith(".class")) { - ret.add(CompletableFuture.supplyAsync(new Supplier>() { - @Override - public List get() { - try { - return readFile(file, isInput, tags, srcPath, fsToClose); - } catch (URISyntaxException e) { - throw new RuntimeException(e); - } catch (IOException | ZipError e) { - throw new RuntimeException("Error reading file "+file, e); - } - } - }, threadPool)); - } - - return FileVisitResult.CONTINUE; - } - }); - - return ret; - } - - private List readFile(Path file, boolean isInput, InputTag[] tags, final Path srcPath, - List fsToClose) throws IOException, URISyntaxException { - List ret = new ArrayList(); - - if (file.toString().endsWith(".class")) { - ClassInstance res = analyze(isInput, tags, srcPath, file); - if (res != null) ret.add(res); - } else { - URI uri = new URI("jar:"+file.toUri().toString()); - FileSystem fs = FileSystemHandler.open(uri); - fsToClose.add(fs); - - Files.walkFileTree(fs.getPath("/"), new SimpleFileVisitor() { - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - if (file.toString().endsWith(".class")) { - ClassInstance res = analyze(isInput, tags, srcPath, file); - if (res != null) ret.add(res); - } - - return FileVisitResult.CONTINUE; - } - }); - } - - return ret; - } - /** * Determine the MRJ version of the supplied class file and name. * @@ -553,22 +529,21 @@ private static int analyzeMrjVersion(Path file, String name) { } } - return ClassInstance.MRJ_DEFAULT; + return TrEnvironment.DEFAULT_MRJ_VERSION; } - private ClassInstance analyze(boolean isInput, InputTag[] tags, Path srcPath, Path file) throws IOException { - byte[] data = Files.readAllBytes(file); + private void analyze(boolean isInput, InputTag[] tags, String source, Path file, byte[] data) { ClassReader reader = new ClassReader(data); - if ((reader.getAccess() & Opcodes.ACC_MODULE) != 0) return null; // special attribute for module-info.class, can't be a regular class + if ((reader.getAccess() & Opcodes.ACC_MODULE) != 0) return; // special attribute for module-info.class, can't be a regular class - final ClassInstance ret = new ClassInstance(this, isInput, tags, srcPath, isInput ? data : null); + final ClassInstance cls = new ClassInstance(this, isInput, tags, source, isInput ? data : null); reader.accept(new ClassVisitor(Opcodes.ASM9) { @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { int mrjVersion = analyzeMrjVersion(file, name); - ret.init(mrjVersion, name, signature, superName, access, interfaces); + cls.init(mrjVersion, name, signature, superName, access, interfaces); for (int i = analyzeVisitors.size() - 1; i >= 0; i--) { cv = analyzeVisitors.get(i).insertAnalyzeVisitor(mrjVersion, name, cv); @@ -579,22 +554,22 @@ public void visit(int version, int access, String name, String signature, String @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { - MemberInstance prev = ret.addMember(new MemberInstance(TrMember.MemberType.METHOD, ret, name, desc, access, ret.getMembers().size())); - if (prev != null) throw new RuntimeException(String.format("duplicate method %s/%s%s in inputs", ret.getName(), name, desc)); + MemberInstance prev = cls.addMember(new MemberInstance(TrMember.MemberType.METHOD, cls, name, desc, access, cls.getMembers().size())); + if (prev != null) throw new RuntimeException(String.format("duplicate method %s/%s%s in inputs", cls.getName(), name, desc)); return super.visitMethod(access, name, desc, signature, exceptions); } @Override public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { - MemberInstance prev = ret.addMember(new MemberInstance(TrMember.MemberType.FIELD, ret, name, desc, access, ret.getMembers().size())); - if (prev != null) throw new RuntimeException(String.format("duplicate field %s/%s;;%s in inputs", ret.getName(), name, desc)); + MemberInstance prev = cls.addMember(new MemberInstance(TrMember.MemberType.FIELD, cls, name, desc, access, cls.getMembers().size())); + if (prev != null) throw new RuntimeException(String.format("duplicate field %s/%s;;%s in inputs", cls.getName(), name, desc)); return super.visitField(access, name, desc, signature, value); } }, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES | ClassReader.SKIP_CODE); - return ret; + addClass(cls, readClasses, true); } private void loadMappings() { @@ -1290,7 +1265,9 @@ public void propagate(TrMember m, String newName) { volatile boolean dirty = true; } - private final boolean check = false; + private static final boolean check = false; + + private static final Predicate classFileNameFilter = name -> name.endsWith(".class"); private final boolean keepInputData; final Set forcePropagation; @@ -1317,7 +1294,7 @@ public void propagate(TrMember m, String newName) { final List> pendingReads = new ArrayList<>(); // reads that need to be waited for before continuing processing (assumes lack of external waiting) final Map readClasses = new ConcurrentHashMap<>(); // classes being potentially concurrently read, to be transferred into unsynchronized classes later - final MrjState defaultState = new MrjState(this, ClassInstance.MRJ_DEFAULT); + final MrjState defaultState = new MrjState(this, TrEnvironment.DEFAULT_MRJ_VERSION); final Map mrjStates = new HashMap<>(); { diff --git a/src/main/java/net/fabricmc/tinyremapper/api/TrClass.java b/src/main/java/net/fabricmc/tinyremapper/api/TrClass.java index e2f1f511..e5b777c6 100644 --- a/src/main/java/net/fabricmc/tinyremapper/api/TrClass.java +++ b/src/main/java/net/fabricmc/tinyremapper/api/TrClass.java @@ -18,6 +18,8 @@ package net.fabricmc.tinyremapper.api; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Collection; import java.util.List; import java.util.function.Predicate; @@ -139,4 +141,15 @@ default boolean isRecord() { default boolean isModule() { return (getAccess() & Opcodes.ACC_MODULE) != 0; } + + static Path getPathInJar(String clsName, int mrjVersion) { + final String CLASS_SUFFIX = ".class"; + final String MRJ_PREFIX = "/META-INF/versions"; + + if (mrjVersion != TrEnvironment.DEFAULT_MRJ_VERSION) { + return Paths.get(MRJ_PREFIX, Integer.toString(mrjVersion), clsName + CLASS_SUFFIX); + } else { + return Paths.get(clsName + CLASS_SUFFIX); + } + } } diff --git a/src/main/java/net/fabricmc/tinyremapper/api/TrEnvironment.java b/src/main/java/net/fabricmc/tinyremapper/api/TrEnvironment.java index 55c37b05..371a1483 100644 --- a/src/main/java/net/fabricmc/tinyremapper/api/TrEnvironment.java +++ b/src/main/java/net/fabricmc/tinyremapper/api/TrEnvironment.java @@ -40,4 +40,6 @@ default TrMethod getMethod(String owner, String name, String desc) { } void propagate(TrMember member, String newName); + + int DEFAULT_MRJ_VERSION = -1; } diff --git a/src/main/java/net/fabricmc/tinyremapper/api/io/InputSupplier.java b/src/main/java/net/fabricmc/tinyremapper/api/io/InputSupplier.java new file mode 100644 index 00000000..48b64aaf --- /dev/null +++ b/src/main/java/net/fabricmc/tinyremapper/api/io/InputSupplier.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2016, 2018, Player, asie + * Copyright (c) 2021, FabricMC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package net.fabricmc.tinyremapper.api.io; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.function.Predicate; + +public interface InputSupplier { + interface InputConsumer { + void accept(Path path, byte[] data); + } + + /** + * Get the source of the input. + */ + String getSource(); + + /** + * Load matching files into {@link InputConsumer}. + */ + CompletableFuture read(Predicate fileNameFilter, Executor executor, InputConsumer consumer) throws IOException; +} diff --git a/src/main/java/net/fabricmc/tinyremapper/api/io/MappingSupplier.java b/src/main/java/net/fabricmc/tinyremapper/api/io/MappingSupplier.java new file mode 100644 index 00000000..25d2c946 --- /dev/null +++ b/src/main/java/net/fabricmc/tinyremapper/api/io/MappingSupplier.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2016, 2018, Player, asie + * Copyright (c) 2021, FabricMC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package net.fabricmc.tinyremapper.api.io; + +import java.io.IOException; + +/** + * @deprecated Not Implemented. + */ +@Deprecated +public interface MappingSupplier { + String getSource(); + void load(MappingConsumer consumer) throws IOException; + + interface MappingConsumer { + void acceptClass(String srcName, String dstName); + void acceptMethod(String owner, String srcName, String desc, String dstName); + void acceptMethodArg(String owner, String srcName, String desc, int lvIndex, String dstName); + void acceptMethodVar(String owner, String srcName, String desc, int lvIndex, int startOpIdx, int asmIndex, String dstName); + void acceptField(String owner, String srcName, String desc, String dstName); + } +} diff --git a/src/main/java/net/fabricmc/tinyremapper/api/io/OutputConsumer.java b/src/main/java/net/fabricmc/tinyremapper/api/io/OutputConsumer.java new file mode 100644 index 00000000..eb98d441 --- /dev/null +++ b/src/main/java/net/fabricmc/tinyremapper/api/io/OutputConsumer.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2016, 2018, Player, asie + * Copyright (c) 2021, FabricMC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package net.fabricmc.tinyremapper.api.io; + +import java.io.IOException; +import java.nio.file.Path; + +import net.fabricmc.tinyremapper.api.TrClass; + +/** + * @deprecated Not Implemented. + * Only {@link OutputConsumer#acceptResource(Path, byte[])} + * and {@link OutputConsumer#acceptClassFile(String, int, byte[])} + * will be called in the internal implementation. + */ +@Deprecated +public interface OutputConsumer { + void acceptResource(Path path, byte[] data) throws IOException; + void acceptClassFile(Path path, byte[] data) throws IOException; + default void acceptClassFile(String name, int mrjVersion, byte[] data) throws IOException { + acceptClassFile(TrClass.getPathInJar(name, mrjVersion), data); + } +} diff --git a/src/main/java/net/fabricmc/tinyremapper/util/DirectoryInputSupplier.java b/src/main/java/net/fabricmc/tinyremapper/util/DirectoryInputSupplier.java new file mode 100644 index 00000000..75eb630b --- /dev/null +++ b/src/main/java/net/fabricmc/tinyremapper/util/DirectoryInputSupplier.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2016, 2018, Player, asie + * Copyright (c) 2021, FabricMC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package net.fabricmc.tinyremapper.util; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.function.Predicate; + +import net.fabricmc.tinyremapper.api.io.InputSupplier; + +public final class DirectoryInputSupplier implements InputSupplier { + public DirectoryInputSupplier(Path directory) throws IOException { + this.path = directory; + } + + @Override + public String getSource() { + return path.toString(); + } + + @Override + public CompletableFuture read(Predicate fileNameFilter, Executor executor, InputConsumer consumer) throws IOException { + if (!Files.isDirectory(path)) throw new IOException("not a directory: "+path); + + return PathInputSupplier.read(path, fileNameFilter, false, executor, consumer); + } + + @Override + public String toString() { + return String.format("dir %s", path); + } + + private final Path path; +} diff --git a/src/main/java/net/fabricmc/tinyremapper/util/JarInputSupplier.java b/src/main/java/net/fabricmc/tinyremapper/util/JarInputSupplier.java new file mode 100644 index 00000000..ed1262a1 --- /dev/null +++ b/src/main/java/net/fabricmc/tinyremapper/util/JarInputSupplier.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2016, 2018, Player, asie + * Copyright (c) 2021, FabricMC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package net.fabricmc.tinyremapper.util; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.function.Predicate; + +import net.fabricmc.tinyremapper.api.io.InputSupplier; + +public class JarInputSupplier implements InputSupplier { + public JarInputSupplier(Path path) { + this.path = path; + } + + @Override + public String getSource() { + return path.toString(); + } + + @Override + public CompletableFuture read(Predicate fileNameFilter, Executor executor, InputConsumer consumer) throws IOException { + return PathInputSupplier.readZip(path, fileNameFilter, executor, consumer); + } + + @Override + public String toString() { + return String.format("jar %s", path); + } + + private final Path path; +} diff --git a/src/main/java/net/fabricmc/tinyremapper/util/PathInputSupplier.java b/src/main/java/net/fabricmc/tinyremapper/util/PathInputSupplier.java new file mode 100644 index 00000000..3ab29e47 --- /dev/null +++ b/src/main/java/net/fabricmc/tinyremapper/util/PathInputSupplier.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2016, 2018, Player, asie + * Copyright (c) 2021, FabricMC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package net.fabricmc.tinyremapper.util; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.FileSystem; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.function.Predicate; + +import net.fabricmc.tinyremapper.FileSystemHandler; +import net.fabricmc.tinyremapper.api.io.InputSupplier; + +public final class PathInputSupplier implements InputSupplier { + public PathInputSupplier(Path path) throws IOException { + this.path = path; + } + + @Override + public String getSource() { + return path.toString(); + } + + @Override + public CompletableFuture read(Predicate fileNameFilter, Executor executor, InputConsumer consumer) throws IOException { + return read(path, fileNameFilter, true, executor, consumer); + } + + static CompletableFuture read(Path path, Predicate fileNameFilter, boolean recurseZip, Executor executor, InputConsumer consumer) throws IOException { + List> futures = new ArrayList<>(); + + Files.walkFileTree(path, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + String fileName = file.getFileName().toString().toLowerCase(Locale.ENGLISH); + + if (recurseZip && (fileName.endsWith(".zip") || fileName.endsWith(".jar"))) { + futures.add(readZip(file, fileNameFilter, executor, consumer)); + } else if (fileNameFilter.test(fileName)) { + futures.add(CompletableFuture.runAsync(() -> { + byte[] data; + + try { + data = Files.readAllBytes(file); + } catch (IOException e) { + throw new UncheckedIOException("Error reading "+file, e); + } + + consumer.accept(file, data); + }, executor)); + } + + return FileVisitResult.CONTINUE; + } + }); + + return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); + } + + static CompletableFuture readZip(Path file, Predicate fileNameFilter, Executor executor, InputConsumer consumer) throws IOException { + if (Files.isDirectory(file)) throw new IOException("not a jar/zip file: "+file); + + FileSystem fs = FileSystemHandler.open(file); + Path root = fs.getPath("/"); + + return read(root, fileNameFilter, false, executor, consumer) + .whenComplete((res, exc) -> { + try { + FileSystemHandler.close(fs); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + } + + @Override + public String toString() { + return String.format("path %s", path); + } + + private final Path path; +} diff --git a/src/test/java/net/fabricmc/tinyremapper/TinyRemapperTest.java b/src/test/java/net/fabricmc/tinyremapper/TinyRemapperTest.java index af975992..82ece179 100644 --- a/src/test/java/net/fabricmc/tinyremapper/TinyRemapperTest.java +++ b/src/test/java/net/fabricmc/tinyremapper/TinyRemapperTest.java @@ -26,6 +26,8 @@ import org.junit.jupiter.api.Test; +import net.fabricmc.tinyremapper.api.TrEnvironment; + class TinyRemapperTest { @Test public void analyzeMrjVersion() throws ReflectiveOperationException { @@ -53,27 +55,27 @@ public void analyzeMrjVersion() throws ReflectiveOperationException { input = "/path/to/bin/com/github/logicf/App.class"; result = getMrjVersionFromPath(input, name); - assertEquals(ClassInstance.MRJ_DEFAULT, result); + assertEquals(TrEnvironment.DEFAULT_MRJ_VERSION, result); input = "/path/to/bin/META-INF/versions/16/abc/com/github/logicf/App.class"; result = getMrjVersionFromPath(input, name); - assertEquals(ClassInstance.MRJ_DEFAULT, result); + assertEquals(TrEnvironment.DEFAULT_MRJ_VERSION, result); input = "/path/to/bin/versions/16/com/github/logicf/App.class"; result = getMrjVersionFromPath(input, name); - assertEquals(ClassInstance.MRJ_DEFAULT, result); + assertEquals(TrEnvironment.DEFAULT_MRJ_VERSION, result); input = "/META-INF/versions/9aa/com/github/logicf/App.class"; result = getMrjVersionFromPath(input, name); - assertEquals(ClassInstance.MRJ_DEFAULT, result); + assertEquals(TrEnvironment.DEFAULT_MRJ_VERSION, result); input = "/bin/App.class"; result = getMrjVersionFromPath(input, name); - assertEquals(ClassInstance.MRJ_DEFAULT, result); + assertEquals(TrEnvironment.DEFAULT_MRJ_VERSION, result); input = "App.class"; result = getMrjVersionFromPath(input, name); - assertEquals(ClassInstance.MRJ_DEFAULT, result); + assertEquals(TrEnvironment.DEFAULT_MRJ_VERSION, result); } private static int getMrjVersionFromPath(String file, String name) throws ReflectiveOperationException {