nameList = new ArrayList<>(names.keySet());
+ Collections.sort(nameList);
+
+ for (int i = 0; i < nameList.size(); i++) {
+ String s = nameList.get(i);
+ System.out.println((i+1) + ") " + s + " <- " + StitchUtil.join(", ", names.get(s)));
+ }
+
+ if (!interactive) {
+ throw new RuntimeException("Conflict detected!");
+ }
+
+ while (true) {
+ String cmd = scanner.nextLine();
+ int i;
+
+ try {
+ i = Integer.parseInt(cmd);
+ } catch (NumberFormatException e) {
+ e.printStackTrace();
+ continue;
+ }
+
+ if (i >= 1 && i <= nameList.size()) {
+ for (JarMethodEntry mm : allEntries) {
+ methodNames.put(mm, nameList.get(i - 1));
+ }
+
+ System.out.println("OK!");
+ return nameList.get(i - 1);
+ }
+ }
+ } else if (names.size() == 1) {
+ String s = names.keySet().iterator().next();
+
+ for (JarMethodEntry mm : allEntries) {
+ methodNames.put(mm, s);
+ }
+
+ if (s.contains("method_")) {
+ return s;
+ } else {
+ String newName = next(m, "method");
+ System.out.println(s + " is now " + newName);
+ return newName;
+ }
+ }
+ }
+
+ return next(m, "method");
+ }
+
+ private void addClass(JarClassEntry c, ClassStorage storageOld, ClassStorage storage, String prefix) throws IOException {
+ String cName = "";
+ String origPrefix = prefix;
+
+ if (!this.obfuscatedPatterns.stream().anyMatch(p -> p.matcher(c.getName()).matches())) {
+ // Class name is not obfuscated. We don't need to generate
+ // an intermediary name, so we just leave it as is and
+ // don't add a prefix.
+ prefix = "";
+ } else if (!isMappedClass(storage, c)) {
+ cName = c.getName();
+ } else {
+ cName = null;
+
+ if (newToOld != null || targetFileMappingsPresent) {
+ Object existingMapping = mappingTree.getClass(c.getFullyQualifiedName());
+ String existingName = null;
+
+ // Check for existing name from target file
+ if (existingMapping != null) {
+ existingName = ((ClassMapping) existingMapping).getDstName(intermediaryIndex);
+ }
+
+ // Check for existing name from supplied old mappings file
+ if (existingName == null
+ && newToOld != null
+ && (existingMapping = newToOld.getClass(c.getFullyQualifiedName())) != null) {
+ existingName = oldToIntermediary.getClass((String) existingMapping);
+ }
+
+ if (existingName != null) {
+ // There is an existing name, so we reuse that.
+ // If we're looking at a subclass, only reuse the
+ // subclass's name, not the parent classes' ones too.
+ String[] r = existingName.split("\\$");
+ cName = r[r.length - 1];
+
+ if (r.length == 1) {
+ // We aren't looking at a subclass;
+ // reuse entire fully qualified name.
+ prefix = "";
+ }
+ }
+ }
+
+ if (cName != null && !cName.contains("class_")) {
+ System.out.println(cName + " is now " + (cName = next(c, "class")));
+ prefix = origPrefix;
+ } else if (cName == null) {
+ cName = next(c, "class");
+ }
+ }
+
+ mappingTree.visitClass(c.getFullyQualifiedName());
+ mappingTree.visitDstName(MappedElementKind.CLASS, intermediaryIndex, prefix + cName);
+
+ for (JarFieldEntry f : c.getFields()) {
+ String fName = getFieldName(storage, c, f);
+
+ if (fName == null) {
+ fName = f.getName();
+ }
+
+ if (fName != null) {
+ mappingTree.visitField(f.getName(), f.getDescriptor());
+ mappingTree.visitDstName(MappedElementKind.FIELD, intermediaryIndex, fName);
+ }
+ }
+
+ for (JarMethodEntry m : c.getMethods()) {
+ String mName = getMethodName(storageOld, storage, c, m);
+
+ if (mName == null) {
+ if (!m.getName().startsWith("<") && m.isSource(storage, c)) {
+ mName = m.getName();
+ }
+ }
+
+ if (mName != null) {
+ mappingTree.visitMethod(m.getName(), m.getDescriptor());
+ mappingTree.visitDstName(MappedElementKind.METHOD, intermediaryIndex, mName);
+ }
+ }
+
+ for (JarClassEntry cc : c.getInnerClasses()) {
+ addClass(cc, storageOld, storage, prefix + cName + "$");
+ }
+ }
+
+ public void prepareRewrite(File oldMappings) throws IOException {
+ oldToIntermediary = new GenMap();
+ newToOld = new GenMap.Dummy();
+
+ readOldMappings(oldMappings).accept(mappingTree);
+ }
+
+ public void prepareUpdate(File oldMappings, File matches) throws IOException {
+ oldToIntermediary = new GenMap();
+ newToOld = new GenMap();
+
+ oldToIntermediary.load(readOldMappings(oldMappings));
+
+ try (FileReader fileReader = new FileReader(matches)) {
+ try (BufferedReader reader = new BufferedReader(fileReader)) {
+ MatcherUtil.read(reader, true, newToOld::addClass, newToOld::addField, newToOld::addMethod);
+ }
+ }
+ }
+
+ private MappingTree readOldMappings(File oldMappings) throws IOException {
+ MemoryMappingTree tempTree = new MemoryMappingTree();
+ mappingFileFormat = MappingReader.detectFormat(oldMappings.toPath());
+ MappingReader.read(oldMappings.toPath(), mappingFileFormat, tempTree);
+
+ validateNamespaces(tempTree);
+ readCountersFromTree(tempTree);
+
+ return tempTree;
+ }
+
+ private void readCounterFileIfPresent() throws IOException {
+ Path counterPath = getExternalCounterFile();
+
+ if (counterPath == null || !Files.exists(counterPath)) {
+ return;
+ }
+
+ MappingFormat format = MappingReader.detectFormat(counterPath);
+
+ if (format != null) {
+ MemoryMappingTree mappingTree = new MemoryMappingTree();
+ MappingReader.read(counterPath, format, mappingTree);
+ readCountersFromTree(mappingTree);
+ counterFileFormat = format;
+ return;
+ }
+
+ System.err.println("Counter file isn't a valid mapping file! Switching to fallback mode...");
+
+ try (FileReader fileReader = new FileReader(counterPath.toFile())) {
+ try (BufferedReader reader = new BufferedReader(fileReader)) {
+ String line;
+
+ while ((line = reader.readLine()) != null) {
+ if (line.startsWith("# INTERMEDIARY-COUNTER")) {
+ String[] parts = line.split(" ");
+ counters.put(parts[2], Integer.parseInt(parts[3]));
+ }
+ }
+ }
+ }
+ }
+
+ private void readCountersFromTree(MappingTree tree) {
+ String counter = tree.getMetadata("next-intermediary-class");
+ if (counter != null) counters.put("class", Integer.parseInt(counter));
+
+ counter = tree.getMetadata("next-intermediary-field");
+ if (counter != null) counters.put("field", Integer.parseInt(counter));
+
+ counter = tree.getMetadata("next-intermediary-method");
+ if (counter != null) counters.put("method", Integer.parseInt(counter));
+ }
+
+ private void writeCounters() throws IOException {
+ clearCounterMetadata();
+ Path counterPath = getExternalCounterFile();
+ MemoryMappingTree mappingTree;
+
+ if (counterPath == null) {
+ mappingTree = this.mappingTree;
+ } else {
+ mappingTree = new MemoryMappingTree();
+ mappingTree.visitNamespaces(official, Arrays.asList(intermediary));
+ }
+
+ mappingTree.visitMetadata("next-intermediary-class", counters.getOrDefault("class", 0).toString());
+ mappingTree.visitMetadata("next-intermediary-field", counters.getOrDefault("field", 0).toString());
+ mappingTree.visitMetadata("next-intermediary-method", counters.getOrDefault("method", 0).toString());
+
+ if (counterPath != null) {
+ MappingWriter writer = MappingWriter.create(counterPath, counterFileFormat);
+ mappingTree.accept(writer);
+ writer.close();
+ }
+ }
+
+ private Path getExternalCounterFile() {
+ if (System.getProperty("stitch.counter") != null) {
+ return Paths.get(System.getProperty("stitch.counter"));
+ }
+
+ return null;
+ }
}
diff --git a/src/main/java/net/fabricmc/stitch/commands/tinyv2/CommandMergeTinyV2.java b/src/main/java/net/fabricmc/stitch/commands/tinyv2/CommandMergeTinyV2.java
deleted file mode 100644
index 4257ec0..0000000
--- a/src/main/java/net/fabricmc/stitch/commands/tinyv2/CommandMergeTinyV2.java
+++ /dev/null
@@ -1,307 +0,0 @@
-/*
- * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
- *
- * Licensed 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 net.fabricmc.stitch.commands.tinyv2;
-
-import java.io.IOException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.function.Function;
-import java.util.regex.Pattern;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import net.fabricmc.stitch.Command;
-import net.fabricmc.stitch.util.Pair;
-
-/**
- * Merges a tiny file with 2 columns (namespaces) of mappings, with another tiny file that has
- * the same namespace as the first column and a different namespace as the second column.
- * The first column of the output will contain the shared namespace,
- * the second column of the output would be the second namespace of input a,
- * and the third column of the output would be the second namespace of input b
- *
- * Descriptors will remain as-is (using the namespace of the first column)
- *
- *
- * For example:
- *
- * Input A:
- * intermediary named
- * c net/minecraft/class_123 net/minecraft/somePackage/someClass
- * m (Lnet/minecraft/class_124;)V method_1234 someMethod
- *
- * Input B:
- * intermediary official
- * c net/minecraft/class_123 a
- * m (Lnet/minecraft/class_124;)V method_1234 a
- *
- * The output will be:
- *
- * intermediary named official
- * c net/minecraft/class_123 net/minecraft/somePackage/someClass a
- * m (Lnet/minecraft/class_124;)V method_1234 someMethod a
- *
- *
- * After intermediary-named mappings are obtained,
- * and official-intermediary mappings are obtained and swapped using CommandReorderTinyV2, Loom merges them using this command,
- * and then reorders it to official-intermediary-named using CommandReorderTinyV2 again.
- * This is a convenient way of storing all the mappings in Loom.
- */
-public class CommandMergeTinyV2 extends Command {
- public CommandMergeTinyV2() {
- super("mergeTinyV2");
- }
-
- /**
- * and are the tiny files to be merged. The result will be written to