diff --git a/CHANGELOG.md b/CHANGELOG.md
index 091d1141..a2a39078 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added Recaf Simple reader and writer
- Added `OuterClassNameInheritingVisitor`
- Added `MappingFormat#hasWriter` boolean
+- Added cross-format `StandardProperties` class
+- Allowed Tiny v1 to save arbitrary metadata, not only intermediary counters
## [0.5.1] - 2023-11-30
- Improved documentation
diff --git a/src/main/java/net/fabricmc/mappingio/format/MappingFormat.java b/src/main/java/net/fabricmc/mappingio/format/MappingFormat.java
index 4ecf455a..327f6a00 100644
--- a/src/main/java/net/fabricmc/mappingio/format/MappingFormat.java
+++ b/src/main/java/net/fabricmc/mappingio/format/MappingFormat.java
@@ -39,7 +39,7 @@
*
- |
* - |
* - |
- * ✔ (Currently limited support) |
+ * ✔ |
*
*
* Tiny v2 |
diff --git a/src/main/java/net/fabricmc/mappingio/format/StandardProperties.java b/src/main/java/net/fabricmc/mappingio/format/StandardProperties.java
new file mode 100644
index 00000000..d33b8538
--- /dev/null
+++ b/src/main/java/net/fabricmc/mappingio/format/StandardProperties.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2023 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.mappingio.format;
+
+import java.util.AbstractMap;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.Nullable;
+
+public final class StandardProperties {
+ private StandardProperties() {
+ }
+
+ public static Collection values() {
+ return Collections.unmodifiableCollection(valuesById.values());
+ }
+
+ @Nullable
+ public static StandardProperty getByName(MappingFormat format, String name) {
+ return valuesByFormatAndName.get(new AbstractMap.SimpleEntry<>(format, name));
+ }
+
+ @Nullable
+ @ApiStatus.Internal
+ public static StandardProperty getById(String id) {
+ return valuesById.get(id);
+ }
+
+ public static final StandardProperty NEXT_INTERMEDIARY_CLASS;
+ public static final StandardProperty NEXT_INTERMEDIARY_FIELD;
+ public static final StandardProperty NEXT_INTERMEDIARY_METHOD;
+ public static final StandardProperty NEXT_INTERMEDIARY_COMPONENT;
+ public static final StandardProperty MISSING_LVT_INDICES;
+ public static final StandardProperty ESCAPED_NAMES;
+ private static final Map, StandardProperty> valuesByFormatAndName = new HashMap<>();
+ private static final Map valuesById = new HashMap<>();
+
+ static {
+ NEXT_INTERMEDIARY_CLASS = register("next-intermediary-class")
+ .addMapping(MappingFormat.TINY_FILE, "INTERMEDIARY_COUNTER class")
+ .addMapping(MappingFormat.TINY_2_FILE, "next-intermediary-class");
+ NEXT_INTERMEDIARY_FIELD = register("next-intermediary-field")
+ .addMapping(MappingFormat.TINY_FILE, "INTERMEDIARY_COUNTER field")
+ .addMapping(MappingFormat.TINY_2_FILE, "next-intermediary-field");
+ NEXT_INTERMEDIARY_METHOD = register("next-intermediary-method")
+ .addMapping(MappingFormat.TINY_FILE, "INTERMEDIARY_COUNTER method")
+ .addMapping(MappingFormat.TINY_2_FILE, "next-intermediary-method");
+ NEXT_INTERMEDIARY_COMPONENT = register("next-intermediary-component")
+ .addMapping(MappingFormat.TINY_FILE, "INTERMEDIARY_COUNTER component")
+ .addMapping(MappingFormat.TINY_2_FILE, "next-intermediary-component");
+ MISSING_LVT_INDICES = register("missing-lvt-indices")
+ .addMapping(MappingFormat.TINY_2_FILE, "missing-lvt-indices");
+ ESCAPED_NAMES = register("escaped-names")
+ .addMapping(MappingFormat.TINY_2_FILE, "escaped-names");
+ }
+
+ private static StandardPropertyImpl register(String id) {
+ return new StandardPropertyImpl(id);
+ }
+
+ private static class StandardPropertyImpl implements StandardProperty {
+ StandardPropertyImpl(String id) {
+ this.id = id;
+ valuesById.put(id, this);
+ }
+
+ private StandardPropertyImpl addMapping(MappingFormat format, String name) {
+ nameByFormat.put(format, name);
+ valuesByFormatAndName.put(new AbstractMap.SimpleEntry<>(format, name), this);
+ return this;
+ }
+
+ @Override
+ public boolean isApplicableTo(MappingFormat format) {
+ return nameByFormat.containsKey(format);
+ }
+
+ @Override
+ public String getNameFor(MappingFormat format) {
+ return nameByFormat.get(format);
+ }
+
+ @Override
+ public String getId() {
+ return id;
+ }
+
+ private final String id;
+ private final Map nameByFormat = new HashMap<>(4);
+ }
+}
diff --git a/src/main/java/net/fabricmc/mappingio/format/StandardProperty.java b/src/main/java/net/fabricmc/mappingio/format/StandardProperty.java
new file mode 100644
index 00000000..e57fd5b0
--- /dev/null
+++ b/src/main/java/net/fabricmc/mappingio/format/StandardProperty.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2023 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.mappingio.format;
+
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.Nullable;
+
+@ApiStatus.NonExtendable
+public interface StandardProperty {
+ boolean isApplicableTo(MappingFormat format);
+
+ @Nullable
+ String getNameFor(MappingFormat format);
+
+ /**
+ * Used internally by MappingTrees, consistency between JVM sessions or library versions isn't guaranteed!
+ */
+ @ApiStatus.Internal
+ String getId();
+}
diff --git a/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny1FileReader.java b/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny1FileReader.java
index f74f7f7b..19ffe721 100644
--- a/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny1FileReader.java
+++ b/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny1FileReader.java
@@ -19,7 +19,9 @@
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
+import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import net.fabricmc.mappingio.MappedElementKind;
@@ -27,6 +29,8 @@
import net.fabricmc.mappingio.MappingVisitor;
import net.fabricmc.mappingio.format.ColumnFileReader;
import net.fabricmc.mappingio.format.MappingFormat;
+import net.fabricmc.mappingio.format.StandardProperties;
+import net.fabricmc.mappingio.format.StandardProperty;
import net.fabricmc.mappingio.tree.MappingTree;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
@@ -98,11 +102,21 @@ private static void read(ColumnFileReader reader, MappingVisitor visitor) throws
String lastClass = null;
boolean lastClassDstNamed = false;;
boolean visitLastClass = false;
+ Map potentialFooterMetadata = new LinkedHashMap<>();
while (reader.nextLine(0)) {
- boolean isMethod;
+ boolean isClass = false;
+ boolean isMethod = false;
+ boolean isField = false;
+
+ if ((isClass = reader.nextCol("CLASS"))
+ || (isMethod = reader.nextCol("METHOD"))
+ || (isField = reader.nextCol("FIELD"))) {
+ // Footer metadata can't be followed by a new class/method/field, so any stored data was comments
+ potentialFooterMetadata.clear();
+ }
- if (reader.nextCol("CLASS")) { // class: CLASS ...
+ if (isClass) { // class: CLASS ...
String srcName = reader.nextCol();
if (srcName == null || srcName.isEmpty()) throw new IOException("missing class-name-a in line "+reader.getLineNumber());
@@ -116,7 +130,7 @@ private static void read(ColumnFileReader reader, MappingVisitor visitor) throws
visitLastClass = visitor.visitElementContent(MappedElementKind.CLASS);
}
}
- } else if ((isMethod = reader.nextCol("METHOD")) || reader.nextCol("FIELD")) { // method: METHOD cls-a desc-a ... or field: FIELD cls-a desc-a ...
+ } else if (isMethod || isField) { // method: METHOD cls-a desc-a ... or field: FIELD cls-a desc-a ...
String srcOwner = reader.nextCol();
if (srcOwner == null || srcOwner.isEmpty()) throw new IOException("missing class-name-a in line "+reader.getLineNumber());
@@ -141,31 +155,37 @@ private static void read(ColumnFileReader reader, MappingVisitor visitor) throws
}
} else {
String line = reader.nextCol();
- final String prefix = "# INTERMEDIARY-COUNTER ";
- String[] parts;
-
- if (line.startsWith(prefix)
- && (parts = line.substring(prefix.length()).split(" ")).length == 2) {
- String property = null;
-
- switch (parts[0]) {
- case "class":
- property = nextIntermediaryClassProperty;
- break;
- case "field":
- property = nextIntermediaryFieldProperty;
- break;
- case "method":
- property = nextIntermediaryMethodProperty;
- break;
+
+ if (line.startsWith("# ") && line.length() >= 3 && line.charAt(3) != ' ') { // Potentially metadata
+ line = line.substring(2);
+ String[] parts = line.split(" ");
+ String value = parts[parts.length - 1];
+ String key = line.substring(0, line.lastIndexOf(value) - 1);
+
+ if (key.isEmpty()) {
+ String oldValue = value;
+ value = key;
+ key = oldValue;
}
+ StandardProperty property = StandardProperties.getByName(format, key);
+
if (property != null) {
- visitor.visitMetadata(property, parts[1]);
+ key = property.getId();
+ }
+
+ if (lastClass == null) { // Header metadata
+ visitor.visitMetadata(key, value);
+ } else {
+ potentialFooterMetadata.put(key, value);
}
}
}
}
+
+ for (Map.Entry entry : potentialFooterMetadata.entrySet()) {
+ visitor.visitMetadata(entry.getKey(), entry.getValue());
+ }
}
if (visitor.visitEnd()) break;
@@ -187,7 +207,5 @@ private static void readDstNames(ColumnFileReader reader, MappedElementKind subj
}
}
- static final String nextIntermediaryClassProperty = "next-intermediary-class";
- static final String nextIntermediaryFieldProperty = "next-intermediary-field";
- static final String nextIntermediaryMethodProperty = "next-intermediary-method";
+ private static final MappingFormat format = MappingFormat.TINY_FILE;
}
diff --git a/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny1FileWriter.java b/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny1FileWriter.java
index f419e3ef..daab46f9 100644
--- a/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny1FileWriter.java
+++ b/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny1FileWriter.java
@@ -29,6 +29,8 @@
import net.fabricmc.mappingio.MappingFlag;
import net.fabricmc.mappingio.MappingWriter;
import net.fabricmc.mappingio.format.MappingFormat;
+import net.fabricmc.mappingio.format.StandardProperties;
+import net.fabricmc.mappingio.format.StandardProperty;
/**
* {@linkplain MappingFormat#TINY_1 Tiny v1 file} writer.
@@ -65,30 +67,18 @@ public void visitNamespaces(String srcNamespace, List dstNamespaces) thr
@Override
public void visitMetadata(String key, @Nullable String value) throws IOException {
- switch (key) {
- case Tiny1FileReader.nextIntermediaryClassProperty:
- case Tiny1FileReader.nextIntermediaryFieldProperty:
- case Tiny1FileReader.nextIntermediaryMethodProperty:
- write("# INTERMEDIARY-COUNTER ");
-
- switch (key) {
- case Tiny1FileReader.nextIntermediaryClassProperty:
- write("class");
- break;
- case Tiny1FileReader.nextIntermediaryFieldProperty:
- write("field");
- break;
- case Tiny1FileReader.nextIntermediaryMethodProperty:
- write("method");
- break;
- default:
- throw new IllegalStateException();
- }
+ StandardProperty property = StandardProperties.getById(key);
- write(" ");
- write(value);
- writeLn();
+ if (property != null) {
+ if (!property.isApplicableTo(format)) return;
+ key = property.getNameFor(format);
}
+
+ write("# ");
+ write(key);
+ write(" ");
+ write(value);
+ writeLn();
}
@Override
@@ -197,6 +187,7 @@ private void writeTab() throws IOException {
}
private static final Set flags = EnumSet.of(MappingFlag.NEEDS_SRC_FIELD_DESC, MappingFlag.NEEDS_SRC_METHOD_DESC);
+ private static final MappingFormat format = MappingFormat.TINY_FILE;
private final Writer writer;
private String classSrcName;
diff --git a/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny2FileReader.java b/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny2FileReader.java
index be4b408b..707f7b02 100644
--- a/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny2FileReader.java
+++ b/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny2FileReader.java
@@ -26,6 +26,8 @@
import net.fabricmc.mappingio.MappingVisitor;
import net.fabricmc.mappingio.format.ColumnFileReader;
import net.fabricmc.mappingio.format.MappingFormat;
+import net.fabricmc.mappingio.format.StandardProperties;
+import net.fabricmc.mappingio.format.StandardProperty;
/**
* {@linkplain MappingFormat#TINY_2 Tiny v2 file} reader.
@@ -96,16 +98,21 @@ private static void read(ColumnFileReader reader, MappingVisitor visitor) throws
if (visitHeader || firstIteration) {
while (reader.nextLine(1)) {
if (!visitHeader) {
- if (!escapeNames && reader.nextCol(Tiny2Util.escapedNamesProperty)) {
+ if (!escapeNames && reader.nextCol(StandardProperties.ESCAPED_NAMES.getNameFor(format))) {
escapeNames = true;
}
} else {
String key = reader.nextCol();
if (key == null) throw new IOException("missing property key in line "+reader.getLineNumber());
String value = reader.nextEscapedCol(); // may be missing -> null
+ StandardProperty property = StandardProperties.getByName(format, key);
- if (key.equals(Tiny2Util.escapedNamesProperty)) {
- escapeNames = true;
+ if (property != null) {
+ key = property.getId();
+
+ if (property == StandardProperties.ESCAPED_NAMES) {
+ escapeNames = true;
+ }
}
visitor.visitMetadata(key, value);
@@ -222,4 +229,6 @@ private static void readDstNames(ColumnFileReader reader, MappedElementKind subj
if (!name.isEmpty()) visitor.visitDstName(subjectKind, dstNs, name);
}
}
+
+ private static final MappingFormat format = MappingFormat.TINY_2_FILE;
}
diff --git a/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny2FileWriter.java b/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny2FileWriter.java
index f2b5b017..7cfa29c3 100644
--- a/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny2FileWriter.java
+++ b/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny2FileWriter.java
@@ -29,6 +29,8 @@
import net.fabricmc.mappingio.MappingFlag;
import net.fabricmc.mappingio.MappingWriter;
import net.fabricmc.mappingio.format.MappingFormat;
+import net.fabricmc.mappingio.format.StandardProperties;
+import net.fabricmc.mappingio.format.StandardProperty;
/**
* {@linkplain MappingFormat#TINY_2 Tiny v2 file} writer.
@@ -66,9 +68,16 @@ public void visitNamespaces(String srcNamespace, List dstNamespaces) thr
@Override
public void visitMetadata(String key, @Nullable String value) throws IOException {
- if (key.equals(Tiny2Util.escapedNamesProperty)) {
- escapeNames = true;
- wroteEscapedNamesProperty = true;
+ StandardProperty property = StandardProperties.getById(key);
+
+ if (property != null) {
+ if (!property.isApplicableTo(format)) return;
+ key = property.getNameFor(format);
+
+ if (property == StandardProperties.ESCAPED_NAMES) {
+ escapeNames = true;
+ wroteEscapedNamesProperty = true;
+ }
}
writeTab();
@@ -86,7 +95,7 @@ public void visitMetadata(String key, @Nullable String value) throws IOException
public boolean visitContent() throws IOException {
if (escapeNames && !wroteEscapedNamesProperty) {
write("\t");
- write(Tiny2Util.escapedNamesProperty);
+ write(StandardProperties.ESCAPED_NAMES.getNameFor(format));
writeLn();
}
@@ -206,6 +215,7 @@ private void writeTabs(int count) throws IOException {
}
}
+ private static final MappingFormat format = MappingFormat.TINY_2_FILE;
private static final Set flags = EnumSet.of(
MappingFlag.NEEDS_HEADER_METADATA,
MappingFlag.NEEDS_METADATA_UNIQUENESS,
diff --git a/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny2Util.java b/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny2Util.java
index 68bee840..341d297d 100644
--- a/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny2Util.java
+++ b/src/main/java/net/fabricmc/mappingio/format/tiny/Tiny2Util.java
@@ -84,6 +84,4 @@ public static String unescape(String str) {
private static final String toEscape = "\\\n\r\0\t";
private static final String escaped = "\\nr0t";
-
- static final String escapedNamesProperty = "escaped-names";
}
diff --git a/src/main/java/net/fabricmc/mappingio/tree/MappingTree.java b/src/main/java/net/fabricmc/mappingio/tree/MappingTree.java
index df8db66d..4ac8182c 100644
--- a/src/main/java/net/fabricmc/mappingio/tree/MappingTree.java
+++ b/src/main/java/net/fabricmc/mappingio/tree/MappingTree.java
@@ -21,6 +21,8 @@
import org.jetbrains.annotations.Nullable;
+import net.fabricmc.mappingio.format.StandardProperty;
+
/**
* Mutable mapping tree.
*/
@@ -44,6 +46,14 @@ public interface MappingTree extends MappingTreeView {
@Override
List extends MetadataEntry> getMetadata(String key);
+ /**
+ * @return An unmodifiable list of all metadata entries currently present
+ * in the tree whose key is equal to the passed {@link StandardProperty}'s ID.
+ * The list's order is equal to the order in which the entries have been originally added.
+ */
+ @Override
+ List extends MetadataEntry> getMetadata(StandardProperty property);
+
void addMetadata(MetadataEntry entry);
/**
diff --git a/src/main/java/net/fabricmc/mappingio/tree/MappingTreeView.java b/src/main/java/net/fabricmc/mappingio/tree/MappingTreeView.java
index d6f3eac4..d4e1eccb 100644
--- a/src/main/java/net/fabricmc/mappingio/tree/MappingTreeView.java
+++ b/src/main/java/net/fabricmc/mappingio/tree/MappingTreeView.java
@@ -23,6 +23,7 @@
import org.jetbrains.annotations.Nullable;
import net.fabricmc.mappingio.MappingVisitor;
+import net.fabricmc.mappingio.format.StandardProperty;
/**
* Read-only mapping tree.
@@ -72,6 +73,7 @@ default String getNamespaceName(int id) {
List extends MetadataEntryView> getMetadata();
List extends MetadataEntryView> getMetadata(String key);
+ List extends MetadataEntryView> getMetadata(StandardProperty property);
Collection extends ClassMappingView> getClasses();
@Nullable
diff --git a/src/main/java/net/fabricmc/mappingio/tree/MemoryMappingTree.java b/src/main/java/net/fabricmc/mappingio/tree/MemoryMappingTree.java
index fcf6829b..b9c6375e 100644
--- a/src/main/java/net/fabricmc/mappingio/tree/MemoryMappingTree.java
+++ b/src/main/java/net/fabricmc/mappingio/tree/MemoryMappingTree.java
@@ -39,6 +39,7 @@
import net.fabricmc.mappingio.MappedElementKind;
import net.fabricmc.mappingio.MappingFlag;
import net.fabricmc.mappingio.MappingVisitor;
+import net.fabricmc.mappingio.format.StandardProperty;
/**
* {@link VisitableMappingTree} implementation that stores all data in memory.
@@ -227,6 +228,13 @@ public List extends MetadataEntry> getMetadata(String key) {
.collect(Collectors.toList()));
}
+ @Override
+ public List extends MetadataEntry> getMetadata(StandardProperty property) {
+ return Collections.unmodifiableList(metadata.stream()
+ .filter(entry -> entry.getKey().equals(property.getId()))
+ .collect(Collectors.toList()));
+ }
+
@Override
public void addMetadata(MetadataEntry entry) {
metadata.add(entry);