├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── pom.xml └── src ├── main └── java │ └── net │ └── techcable │ └── srglib │ ├── ArrayType.java │ ├── FieldData.java │ ├── JavaType.java │ ├── JavaTypeSort.java │ ├── MethodData.java │ ├── MethodSignature.java │ ├── PrimitiveType.java │ ├── ReferenceType.java │ ├── SrgLib.java │ ├── format │ ├── CompactSrgMappingsFormat.java │ ├── MappingsFormat.java │ └── SrgMappingsFormat.java │ ├── mappings │ ├── ImmutableMappings.java │ ├── Mappings.java │ ├── MutableMappings.java │ ├── RenamingMappings.java │ └── SimpleMappings.java │ └── utils │ ├── CheckedBiConsumer.java │ ├── CheckedConsumer.java │ ├── CheckedRunnable.java │ ├── CheckedSupplier.java │ ├── Exceptions.java │ ├── ImmutableLists.java │ └── ImmutableMaps.java └── test └── java └── net └── techcable └── srglib ├── MappingsChainTest.java └── MappingsFormatTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/ntell,intellij,python 3 | 4 | #!! ERROR: ntell is undefined. Use list command to see defined gitignore 5 | types !!# 6 | 7 | ### Intellij ### 8 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, 9 | CLion, Android Studio and Webstorm 10 | 11 | *.iml 12 | 13 | ## Directory-based project format: 14 | .idea/ 15 | # if you remove the above rule, at least ignore the following: 16 | 17 | # User-specific stuff: 18 | # .idea/workspace.xml 19 | # .idea/tasks.xml 20 | # .idea/dictionaries 21 | # .idea/shelf 22 | 23 | # Sensitive or high-churn files: 24 | # .idea/dataSources.ids 25 | # .idea/dataSources.xml 26 | # .idea/sqlDataSources.xml 27 | # .idea/dynamic.xml 28 | # .idea/uiDesigner.xml 29 | 30 | # Gradle: 31 | # .idea/gradle.xml 32 | # .idea/libraries 33 | 34 | # Mongo Explorer plugin: 35 | # .idea/mongoSettings.xml 36 | 37 | ## File-based project format: 38 | *.ipr 39 | *.iws 40 | 41 | ## Plugin-specific files: 42 | 43 | # IntelliJ 44 | /out/ 45 | 46 | # mpeltonen/sbt-idea plugin 47 | .idea_modules/ 48 | 49 | # JIRA plugin 50 | atlassian-ide-plugin.xml 51 | 52 | # Crashlytics plugin (for Android Studio and IntelliJ) 53 | com_crashlytics_export_strings.xml 54 | crashlytics.properties 55 | crashlytics-build.properties 56 | fabric.properties 57 | 58 | 59 | ### Python ### 60 | # Byte-compiled / optimized / DLL files 61 | __pycache__/ 62 | *.py[cod] 63 | *$py.class 64 | 65 | # C extensions 66 | *.so 67 | 68 | # Distribution / packaging 69 | .Python 70 | env/ 71 | build/ 72 | develop-eggs/ 73 | dist/ 74 | downloads/ 75 | eggs/ 76 | .eggs/ 77 | lib/ 78 | lib64/ 79 | parts/ 80 | sdist/ 81 | var/ 82 | *.egg-info/ 83 | .installed.cfg 84 | *.egg 85 | 86 | # PyInstaller 87 | # Usually these files are written by a python script from a template 88 | # before PyInstaller builds the exe, so as to inject date/other infos 89 | into it. 90 | *.manifest 91 | *.spec 92 | 93 | # Installer logs 94 | pip-log.txt 95 | pip-delete-this-directory.txt 96 | 97 | # Unit test / coverage reports 98 | htmlcov/ 99 | .tox/ 100 | .coverage 101 | .coverage.* 102 | .cache 103 | nosetests.xml 104 | coverage.xml 105 | *,cover 106 | .hypothesis/ 107 | 108 | # Translations 109 | *.mo 110 | *.pot 111 | 112 | # Django stuff: 113 | *.log 114 | 115 | # Sphinx documentation 116 | docs/_build/ 117 | 118 | # PyBuilder 119 | target/ 120 | 121 | 122 | # A big file I use to test performance: 123 | joined.srg 124 | 125 | # Maven 126 | target 127 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - '3.5' 4 | script: python test.py 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SrgLib 2 | A python library for handling srg files and related stuff 3 | 4 | ![build status](https://img.shields.io/travis/ProjectTestificate/SrgLib.svg) 5 | ![powered by tacos](https://img.shields.io/badge/powered by-tacos-brightgreen.svg) 6 | 7 | ## Features 8 | - Correctly Parses and Outputs srg files 9 | - Is able to [fully reproduce](test.py#21) the original srg file from its internal representation 10 | - Well documented 11 | - All public methods have complete documentation 12 | - Object oriented 13 | - Types have objects, which have useful utility methods 14 | - Fast 15 | - Parses 27,000 lines in slightly over a second on python 3.5 16 | - Compatible with python 3.5 17 | - This library requires the 'typing' module for type hinting 18 | - Any version before python 3.5 does not have type hinting and won't work -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | net.techcable 4 | srglib 5 | 0.1.2 6 | 7 | 1.8 8 | 9 | 10 | 11 | 12 | com.google.guava 13 | guava 14 | 21.0 15 | 16 | 17 | junit 18 | junit 19 | 4.12 20 | test 21 | 22 | 23 | com.google.code.findbugs 24 | jsr305 25 | 1.3.9 26 | provided 27 | true 28 | 29 | 30 | 31 | 32 | 33 | 34 | org.apache.maven.plugins 35 | maven-compiler-plugin 36 | 3.5.1 37 | 38 | ${java.version} 39 | ${java.version} 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | techcable-repo 48 | https://repo.techcable.net/content/repositories/releases/ 49 | 50 | 51 | techcable-repo 52 | https://repo.techcable.net/content/repositories/snapshots/ 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/main/java/net/techcable/srglib/ArrayType.java: -------------------------------------------------------------------------------- 1 | package net.techcable.srglib; 2 | 3 | import java.util.function.UnaryOperator; 4 | 5 | import static java.util.Objects.*; 6 | 7 | /** 8 | * An array type 9 | */ 10 | /* package */ final class ArrayType implements JavaType { 11 | private final JavaType elementType; 12 | public ArrayType(JavaType elementType) { 13 | this.elementType = requireNonNull(elementType); 14 | } 15 | 16 | /** 17 | * Return the element type of this array. 18 | * 19 | * @return the element type 20 | */ 21 | @Override 22 | public JavaType getElementType() { 23 | return elementType; 24 | } 25 | 26 | @Override 27 | public JavaTypeSort getSort() { 28 | return JavaTypeSort.ARRAY_TYPE; 29 | } 30 | 31 | @Override 32 | public String getInternalName() { 33 | return elementType.getInternalName() + "[]"; 34 | } 35 | 36 | @Override 37 | public String getDescriptor() { 38 | return "[" + elementType.getDescriptor(); 39 | } 40 | 41 | @Override 42 | public String getName() { 43 | return elementType.getName() + "[]"; 44 | } 45 | 46 | @Override 47 | public JavaType mapClass(UnaryOperator func) { 48 | int dimensions = 1; 49 | JavaType elementType = this.getElementType(); 50 | while (elementType.isArrayType()) { 51 | elementType = elementType.getElementType(); 52 | dimensions += 1; 53 | } 54 | return JavaType.createArray(dimensions, elementType.mapClass(func)); 55 | } 56 | 57 | private int hashCode = 0; 58 | @Override 59 | public int hashCode() { 60 | int hashCode = this.hashCode; 61 | if (hashCode == 0) { 62 | JavaType innermostType = this.elementType; 63 | int dimensions = 1; 64 | while (innermostType instanceof ArrayType) { 65 | innermostType = ((ArrayType) innermostType).elementType; 66 | dimensions++; 67 | } 68 | hashCode = dimensions + ~innermostType.hashCode(); 69 | if (hashCode == 0) hashCode = 1; // Make sure it's not zero so we never trigger again 70 | this.hashCode = hashCode; 71 | } 72 | return hashCode; 73 | } 74 | 75 | @Override 76 | public boolean equals(Object obj) { 77 | return obj == this || obj != null 78 | && obj.getClass() == ArrayType.class 79 | && obj.hashCode() == this.hashCode() 80 | && this.getElementType().equals(((ArrayType) obj).getElementType()); 81 | } 82 | 83 | @Override 84 | public String toString() { 85 | return getName(); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/net/techcable/srglib/FieldData.java: -------------------------------------------------------------------------------- 1 | package net.techcable.srglib; 2 | 3 | import java.util.function.UnaryOperator; 4 | 5 | import static com.google.common.base.Preconditions.*; 6 | import static java.util.Objects.*; 7 | 8 | /** 9 | * A field's name and declaring type. 10 | */ 11 | public final class FieldData { 12 | private final JavaType declaringType; 13 | private final String name; 14 | 15 | private FieldData(JavaType declaringType, String name) { 16 | this.declaringType = requireNonNull(declaringType, "Null declaring type"); 17 | this.name = requireNonNull(name, "Null name"); 18 | checkArgument(SrgLib.isValidIdentifier(name), "Invalid name: %s", name); 19 | } 20 | 21 | /** 22 | * Return the declaring type of this field 23 | * 24 | * @return the declaring type 25 | */ 26 | public JavaType getDeclaringType() { 27 | return declaringType; 28 | } 29 | 30 | /** 31 | * Return the name of this field 32 | * 33 | * @return the name 34 | */ 35 | public String getName() { 36 | return name; 37 | } 38 | 39 | /** 40 | * Return the internal name of this field. 41 | *

42 | * Internal method names are in the format ${internal type name}/${field name}. 43 | *

44 | * 45 | * @return the internal name 46 | */ 47 | public String getInternalName() { 48 | return this.declaringType.getInternalName() + "/" + this.name; 49 | } 50 | 51 | public FieldData withName(String name) { 52 | return new FieldData(declaringType, name); 53 | } 54 | 55 | public boolean hasSameTypes(FieldData other) { 56 | requireNonNull(other, "Null other data!"); 57 | return this.declaringType.equals(other.declaringType); 58 | } 59 | 60 | public FieldData withDeclaringType(JavaType declaringType) { 61 | return new FieldData(declaringType, name); 62 | } 63 | 64 | public FieldData mapTypes(UnaryOperator transformer) { 65 | return new FieldData(transformer.apply(declaringType), name); 66 | } 67 | 68 | 69 | public static FieldData create(JavaType declaringType, String name) { 70 | return new FieldData(declaringType, name); 71 | } 72 | 73 | 74 | @Override 75 | public int hashCode() { 76 | return declaringType.hashCode() ^ name.hashCode(); 77 | } 78 | 79 | @Override 80 | public boolean equals(Object obj) { 81 | return obj == this || obj != null 82 | && obj.getClass() == FieldData.class 83 | && ((FieldData) obj).declaringType.equals(this.declaringType) 84 | && ((FieldData) obj).name.equals(this.name); 85 | } 86 | 87 | @Override 88 | public String toString() { 89 | return getInternalName(); 90 | } 91 | 92 | public static FieldData fromInternalName(String internalName) { 93 | int index = internalName.lastIndexOf('/'); 94 | checkArgument(index >= 0 && index < internalName.length() - 1, "Invalid internal name: %s", internalName); 95 | JavaType declaringType = JavaType.fromInternalName(internalName.substring(0, index)); 96 | String name = internalName.substring(index + 1); 97 | return create(declaringType, name); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/net/techcable/srglib/JavaType.java: -------------------------------------------------------------------------------- 1 | package net.techcable.srglib; 2 | 3 | import java.util.Locale; 4 | import java.util.function.UnaryOperator; 5 | import javax.annotation.Nonnull; 6 | 7 | import static com.google.common.base.Preconditions.*; 8 | import static java.util.Objects.*; 9 | 10 | /** 11 | * A java type/class. 12 | */ 13 | public interface JavaType { 14 | /** 15 | * Return the internal name of this type. 16 | *

17 | * The internal name of a class is its fully qualified name, 18 | * (as returned by Class.getName() where '.' are replaced by '/') 19 | * For primitives, this is identical to get_name() 20 | * 21 | * @return the internal name 22 | */ 23 | String getInternalName(); 24 | 25 | /** 26 | * Return the bytecode descriptor of this type 27 | * 28 | * @return this type's bytecode descriptor 29 | */ 30 | String getDescriptor(); 31 | 32 | /** 33 | * Return the name of this type, like returned by Class.getName() 34 | * 35 | * @return the name of this type 36 | */ 37 | String getName(); 38 | 39 | /** 40 | * Return the name of this type, with the package removed. 41 | *

42 | * If this type doesn't have the package, it just returns its name. 43 | *

44 | * 45 | * @return the simple name 46 | */ 47 | default String getSimpleName() { 48 | return getName(); 49 | } 50 | 51 | /** 52 | * Return if this type is a primitive type. 53 | * 54 | * @return if this type is primitive 55 | */ 56 | default boolean isPrimitiveType() { 57 | return getSort() == JavaTypeSort.PRIMITIVE_TYPE; 58 | } 59 | 60 | /** 61 | * Return if this type is an array tyep. 62 | * 63 | * @return if it's an array. 64 | */ 65 | default boolean isArrayType() { 66 | return getSort() == JavaTypeSort.ARRAY_TYPE; 67 | } 68 | 69 | /** 70 | * Return if this type is a reference type. 71 | * 72 | * @return if it's a reference type 73 | */ 74 | default boolean isReferenceType() { 75 | return getSort() == JavaTypeSort.REFERENCE_TYPE; 76 | } 77 | 78 | JavaTypeSort getSort(); 79 | 80 | /** 81 | * Apply the specified mapping to this type, based on its class name. 82 | *

83 | * If type is an array, it remaps the innermost element type. 84 | * If the type is a class, it invokes the specified function 85 | * If the type is a primitive, it returns the same element type. 86 | *

87 | * 88 | * @param func the mapping function to apply to this type 89 | * @return the new type 90 | */ 91 | JavaType mapClass(UnaryOperator func); 92 | 93 | /** 94 | * Return this reference type's package, or an empty string if in the default package. 95 | * 96 | * @return the package name 97 | * @throws IllegalStateException if this type's not a reference type 98 | */ 99 | default String getPackageName() { 100 | throw new IllegalStateException(getName() + " is not a reference type!"); 101 | } 102 | 103 | /** 104 | * Return this array's element type. 105 | * 106 | * @return the element type 107 | * @throws IllegalStateException if this type's not a array type 108 | */ 109 | default JavaType getElementType() { 110 | throw new IllegalStateException(getName() + " is not an array type!"); 111 | } 112 | 113 | static JavaType createArray(int dimensions, JavaType elementType) { 114 | requireNonNull(elementType, "Null element type"); 115 | if (dimensions == 0) return elementType; 116 | checkArgument(dimensions >= 0, "Negative dimensions: %s", dimensions); 117 | ArrayType result = new ArrayType(elementType); 118 | while (--dimensions > 0) { 119 | result = new ArrayType(result); // Keep nesting 120 | } 121 | return result; 122 | } 123 | 124 | static JavaType createArray(JavaType elementType) { 125 | return createArray(1, elementType); 126 | } 127 | 128 | /** 129 | * Return a {@link JavaType} with the given name. 130 | * 131 | * @param name the name of the type 132 | * @return the type 133 | * @throws IllegalArgumentException if the name is invalid 134 | */ 135 | @Nonnull 136 | static JavaType fromName(String name) { 137 | requireNonNull(name, "Null name"); 138 | if (name.endsWith("[]")) { 139 | int dimensions = 0; 140 | do { 141 | dimensions += 1; 142 | name = name.substring(0, name.length() - 2); 143 | } while (name.endsWith("[]")); 144 | JavaType elementType = fromName(name); 145 | assert !(elementType instanceof ArrayType); 146 | ArrayType result; 147 | do { 148 | result = new ArrayType(elementType); 149 | elementType = result; 150 | } while (--dimensions > 0); 151 | return result; 152 | } 153 | try { 154 | return PrimitiveType.valueOf(name.toUpperCase(Locale.ROOT)); 155 | } catch (IllegalArgumentException ignored) { 156 | // Fallback to treating it as a reference-type/class 157 | try { 158 | return new ReferenceType(name); 159 | } catch (IllegalArgumentException e) { 160 | // Hide the true error ^_^ 161 | throw new IllegalArgumentException("Invalid type name: " + name); 162 | } 163 | } 164 | } 165 | 166 | /** 167 | * Return a JavaType with the given internal name. 168 | * 169 | * @param internalName the internal name of the type. 170 | * @return a new JavaType 171 | */ 172 | static JavaType fromInternalName(String internalName) { 173 | requireNonNull(internalName, "Null internal name"); 174 | return fromName(internalName.replace('/', '.')); 175 | } 176 | 177 | /** 178 | * Return a JavaType with the given descriptor 179 | * 180 | * @param descriptor the descriptor to parse 181 | * @return a new JavaType 182 | * @throws IllegalArgumentException if the descriptor is invalid 183 | */ 184 | static JavaType fromDescriptor(String descriptor) { 185 | final int descriptorLength = requireNonNull(descriptor, "Null descriptor").length(); 186 | switch (descriptorLength) { 187 | case 0: 188 | throw new IllegalArgumentException("Empty descriptor!"); 189 | case 1: 190 | return PrimitiveType.fromDescriptorChar(descriptor.charAt(0)); 191 | default: 192 | char firstChar = descriptor.charAt(0); 193 | switch (firstChar) { 194 | case '[': 195 | int dimensions = 1; 196 | while (descriptor.charAt(dimensions) == '[') { 197 | dimensions++; 198 | } 199 | return createArray(dimensions, fromDescriptor(descriptor.substring(dimensions))); 200 | case 'L': 201 | if (descriptor.charAt(descriptorLength - 1) == ';') { 202 | char[] internalName = new char[descriptorLength - 2]; 203 | descriptor.getChars(1, descriptorLength - 1, internalName, 0); // slice(1, -1) 204 | for (int i = 0; i < internalName.length; i++) { 205 | char c = internalName[i]; 206 | if (c == '.') { 207 | internalName[i] = '/'; // Replace all '.' with '/' 208 | } 209 | } 210 | return fromInternalName(String.valueOf(internalName)); 211 | } 212 | } 213 | } 214 | throw new IllegalArgumentException("Invalid descriptor: " + descriptor); 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /src/main/java/net/techcable/srglib/JavaTypeSort.java: -------------------------------------------------------------------------------- 1 | package net.techcable.srglib; 2 | 3 | public enum JavaTypeSort { 4 | /** 5 | * A reference (object) type 6 | */ 7 | REFERENCE_TYPE, 8 | /** 9 | * An array type 10 | */ 11 | ARRAY_TYPE, 12 | /** 13 | * A primitive type 14 | */ 15 | PRIMITIVE_TYPE; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/net/techcable/srglib/MethodData.java: -------------------------------------------------------------------------------- 1 | package net.techcable.srglib; 2 | 3 | import java.util.function.UnaryOperator; 4 | 5 | import com.google.common.collect.ImmutableList; 6 | 7 | import static com.google.common.base.Preconditions.checkArgument; 8 | import static java.util.Objects.*; 9 | 10 | /** 11 | * A method's declaring type, name, and parameter types, uniquely identifying it in a jar. 12 | */ 13 | public final class MethodData { 14 | private final JavaType declaringType; 15 | private final String name; 16 | private final MethodSignature signature; 17 | 18 | private MethodData(JavaType declaringType, String name, MethodSignature signature) { 19 | this.declaringType = requireNonNull(declaringType, "Null declaring type"); 20 | this.name = requireNonNull(name, "Null name"); 21 | this.signature = requireNonNull(signature, "Null method descriptor"); 22 | if (!SrgLib.isValidIdentifier(name)) { 23 | throw new IllegalArgumentException("Invalid method name: " + name); 24 | } 25 | } 26 | 27 | /** 28 | * Return the type that declared this method. 29 | * 30 | * @return the declaring type 31 | */ 32 | public JavaType getDeclaringType() { 33 | return declaringType; 34 | } 35 | 36 | /** 37 | * Return the name of this method 38 | * 39 | * @return the method name 40 | */ 41 | public String getName() { 42 | return name; 43 | } 44 | 45 | /** 46 | * Get the parameters types of this method. 47 | * 48 | * @return the parameter types. 49 | */ 50 | public ImmutableList getParameterTypes() { 51 | return signature.getParameterTypes(); 52 | } 53 | 54 | /** 55 | * Get the return type of this method. 56 | * 57 | * @return the return type 58 | */ 59 | public JavaType getReturnType() { 60 | return signature.getReturnType(); 61 | } 62 | 63 | /** 64 | * Return the internal name of this method. 65 | *

66 | * Internal method names are in the format {@code ${internal type name}/${method name}} 67 | *

68 | * 69 | * @return the internal name 70 | */ 71 | public String getInternalName() { 72 | return declaringType.getInternalName() + "/" + name; 73 | } 74 | 75 | /** 76 | * Return the method's signature. 77 | * 78 | * @return the method's signature. 79 | */ 80 | public MethodSignature getSignature() { 81 | return signature; 82 | } 83 | 84 | public MethodData withSignature(MethodSignature signature) { 85 | if (signature.equals(this.signature)) { 86 | return this; 87 | } else { 88 | return create(declaringType, name, signature); 89 | } 90 | } 91 | 92 | public MethodData mapSignature(UnaryOperator transformer) { 93 | return withSignature(signature.mapTypes(transformer)); 94 | } 95 | 96 | public MethodData mapTypes(UnaryOperator transformer) { 97 | return mapSignature(transformer).withDeclaringType(transformer.apply(declaringType)); 98 | } 99 | 100 | public boolean hasSameTypes(MethodData other) { 101 | requireNonNull(other, "Null other data!"); 102 | return this.declaringType.equals(other.declaringType) 103 | && this.signature.equals(other.signature); 104 | } 105 | 106 | public MethodData withReturnType(JavaType returnType) { 107 | if (returnType.equals(this.getReturnType())) return this; 108 | return new MethodData(declaringType, name, signature); 109 | } 110 | 111 | public MethodData withName(String name) { 112 | if (name.equals(this.name)) return this; 113 | return new MethodData(declaringType, name, signature); 114 | } 115 | 116 | public MethodData withDeclaringType(JavaType declaringType) { 117 | if (declaringType.equals(this.declaringType)) return this; 118 | return new MethodData(declaringType, name, signature); 119 | } 120 | 121 | @Override 122 | public int hashCode() { 123 | return declaringType.hashCode() ^ name.hashCode() ^ signature.hashCode(); 124 | } 125 | 126 | @Override 127 | public boolean equals(Object other) { 128 | return this == other || other != null 129 | && other.getClass() == MethodData.class 130 | && this.name.equals(((MethodData) other).name) 131 | && this.hasSameTypes((MethodData) other); 132 | } 133 | 134 | @Override 135 | public String toString() { 136 | return this.declaringType.getName() + 137 | "." + 138 | name + 139 | signature.toString(); 140 | } 141 | 142 | private static final JavaType[] EMPTY_TYPE_ARRAY = new JavaType[0]; 143 | 144 | /** 145 | * Create a new method data object with the specified name and type. 146 | * 147 | * @param declaringType the type that declared the method 148 | * @param name the name of the method 149 | * @param parameterTypes the parameter types of this method 150 | * @param returnType the return type of this method 151 | * @return the created method data 152 | */ 153 | public static MethodData create( 154 | JavaType declaringType, 155 | String name, 156 | ImmutableList parameterTypes, 157 | JavaType returnType 158 | ) { 159 | return create(declaringType, name, MethodSignature.create(parameterTypes, returnType)); 160 | } 161 | 162 | 163 | /** 164 | * Create a new method data object with the specified name and signature. 165 | * 166 | * @param declaringType the type that declared the method 167 | * @param name the name of the method 168 | * @param signature the method's signature. 169 | * @return the created method data 170 | */ 171 | public static MethodData create( 172 | JavaType declaringType, 173 | String name, 174 | MethodSignature signature 175 | ) { 176 | return new MethodData(declaringType, name, signature); 177 | } 178 | 179 | public static MethodData fromInternalName(String joinedName, MethodSignature signature) { 180 | int index = joinedName.lastIndexOf('/'); 181 | checkArgument(index >= 0 && index < joinedName.length() - 1, "Invalid internal name: %s", joinedName); 182 | JavaType declaringType = JavaType.fromInternalName(joinedName.substring(0, index)); 183 | String name = joinedName.substring(index + 1); 184 | return create(declaringType, name, signature); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/main/java/net/techcable/srglib/MethodSignature.java: -------------------------------------------------------------------------------- 1 | package net.techcable.srglib; 2 | 3 | import java.util.function.UnaryOperator; 4 | 5 | import com.google.common.collect.ImmutableList; 6 | 7 | import net.techcable.srglib.utils.ImmutableLists; 8 | 9 | import static com.google.common.base.Preconditions.*; 10 | import static java.util.Objects.*; 11 | 12 | /** 13 | * A method's signature, containing its parameter and return types/ 14 | */ 15 | public final class MethodSignature { 16 | private final ImmutableList parameterTypes; 17 | private final JavaType returnType; 18 | 19 | private MethodSignature(ImmutableList parameterTypes, JavaType returnType) { 20 | this.parameterTypes = requireNonNull(parameterTypes, "Null parameter types"); 21 | this.returnType = requireNonNull(returnType, "Null return type"); 22 | for (JavaType parameterType : parameterTypes) { 23 | checkArgument(parameterType != PrimitiveType.VOID, "Void parameter!"); 24 | } 25 | } 26 | 27 | public ImmutableList getParameterTypes() { 28 | return parameterTypes; 29 | } 30 | 31 | public JavaType getReturnType() { 32 | return returnType; 33 | } 34 | 35 | public MethodSignature mapTypes(UnaryOperator transformer) { 36 | JavaType newReturnType = transformer.apply(returnType); 37 | ImmutableList newParameterTypes = ImmutableLists.transform(this.parameterTypes, transformer); 38 | MethodSignature result = create(newParameterTypes, newReturnType); 39 | if (result.equals(this)) { 40 | return this; 41 | } else { 42 | return result; 43 | } 44 | } 45 | 46 | private String descriptor; 47 | 48 | /** 49 | * Return the bytecode descriptor of this method type. 50 | * 51 | * @return the bytecode descriptor 52 | */ 53 | public String getDescriptor() { 54 | if (descriptor == null) { 55 | descriptor = ImmutableLists.joinToString( 56 | parameterTypes, 57 | JavaType::getDescriptor, 58 | "", 59 | "(", 60 | ")" 61 | ) + returnType.getDescriptor(); 62 | } 63 | return descriptor; 64 | } 65 | 66 | public String toString() { 67 | return ImmutableLists.joinToString( 68 | parameterTypes, 69 | JavaType::getSimpleName, 70 | ",", 71 | "(", 72 | ")" 73 | ) + returnType.getSimpleName(); 74 | } 75 | 76 | private int hash; 77 | 78 | @Override 79 | public int hashCode() { 80 | int hash = this.hash; 81 | if (hash == 0) { 82 | hash = returnType == PrimitiveType.VOID ? 0 : returnType.hashCode(); 83 | if (!parameterTypes.isEmpty()) { 84 | hash ^= parameterTypes.hashCode(); 85 | } 86 | this.hash = hash; 87 | } 88 | return hash; 89 | } 90 | 91 | @Override 92 | public boolean equals(Object obj) { 93 | return this == obj || obj != null && obj.getClass() == MethodSignature.class 94 | && this.hashCode() == obj.hashCode() 95 | && this.returnType.equals(((MethodSignature) obj).returnType) 96 | && this.parameterTypes.equals(((MethodSignature) obj).parameterTypes); 97 | } 98 | 99 | /** 100 | * Parse the specified bytecode method descriptor into a signature object. 101 | * 102 | * @param descriptor the bytecode descriptor 103 | * @return a new signature object 104 | * @throws IllegalArgumentException if the signature is invalid 105 | */ 106 | public static MethodSignature fromDescriptor(String descriptor) { 107 | checkArgument(descriptor.length() > 2 && descriptor.charAt(0) == '(', "Invalid descriptor: %s", descriptor); 108 | int lastArgChar = descriptor.indexOf(')'); 109 | checkArgument(lastArgChar >= 0, "Invalid descriptor: %s", descriptor); 110 | ImmutableList.Builder parameterTypes = ImmutableList.builder(); 111 | for (int index = 1; index < lastArgChar; index++) { 112 | char c = descriptor.charAt(index); 113 | final int arrayDimensions; 114 | if (c == '[') { 115 | int dimensions = 1; 116 | while ((c = descriptor.charAt(index + dimensions)) == '[') dimensions++; 117 | index += dimensions; 118 | arrayDimensions = dimensions; 119 | } else { 120 | arrayDimensions = 0; 121 | } 122 | final JavaType result; 123 | if (c == 'L') { 124 | int endIndex = descriptor.indexOf(';', index); 125 | checkArgument(endIndex >= 0 && endIndex < lastArgChar, "Invalid descriptor: %s", descriptor); 126 | String internalName = descriptor.substring(index + 1, endIndex); 127 | result = JavaType.fromInternalName(internalName); 128 | index = endIndex; 129 | } else { 130 | result = PrimitiveType.fromDescriptorChar(c); 131 | } 132 | parameterTypes.add(JavaType.createArray(arrayDimensions, result)); 133 | } 134 | JavaType returnType = JavaType.fromDescriptor(descriptor.substring(lastArgChar + 1)); 135 | return create(parameterTypes.build(), returnType); 136 | } 137 | 138 | public static MethodSignature create(ImmutableList parameterTypes, JavaType returnType) { 139 | return new MethodSignature(parameterTypes, returnType); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/main/java/net/techcable/srglib/PrimitiveType.java: -------------------------------------------------------------------------------- 1 | package net.techcable.srglib; 2 | 3 | import java.util.function.UnaryOperator; 4 | import javax.annotation.Nonnull; 5 | 6 | /** 7 | * An enumeration of java's 8 primitive types and 'VOID. 8 | * 9 | * void is actually a valid java type (believe it or not) 10 | */ 11 | @SuppressWarnings("WeakerAccess") 12 | public enum PrimitiveType implements JavaType { 13 | BYTE('B'), 14 | SHORT('S'), 15 | INT('I'), 16 | LONG('J'), 17 | FLOAT('F'), 18 | DOUBLE('D'), 19 | CHAR('C'), 20 | BOOLEAN('Z'), 21 | VOID('V'); 22 | 23 | @Override 24 | public String getInternalName() { 25 | return getName(); 26 | } 27 | 28 | @Override 29 | public String getDescriptor() { 30 | return String.valueOf(descriptorChar); 31 | } 32 | 33 | @Override 34 | public String getName() { 35 | return this.name().toLowerCase(); 36 | } 37 | 38 | @Override 39 | public JavaTypeSort getSort() { 40 | return JavaTypeSort.PRIMITIVE_TYPE; 41 | } 42 | 43 | @Override 44 | public JavaType mapClass(UnaryOperator func) { 45 | return this; 46 | } 47 | 48 | private final char descriptorChar; 49 | /* private */ PrimitiveType(char descriptorChar) { 50 | this.descriptorChar = descriptorChar; 51 | } 52 | 53 | 54 | private static final PrimitiveType[] byDescriptorChar = new PrimitiveType[128]; 55 | static { 56 | for (PrimitiveType type : values()) { 57 | assert byDescriptorChar[type.descriptorChar] == null : "Duplicate descriptors with char " + type.descriptorChar; 58 | byDescriptorChar[type.descriptorChar] = type; 59 | } 60 | } 61 | @Nonnull 62 | public static PrimitiveType fromDescriptorChar(char descriptorChar) { 63 | PrimitiveType primitiveType; 64 | if (descriptorChar <= byDescriptorChar.length && (primitiveType = byDescriptorChar[descriptorChar]) != null) { 65 | return primitiveType; 66 | } 67 | throw new IllegalArgumentException("Invalid descriptor char: " + descriptorChar); 68 | } 69 | 70 | @Override 71 | public String toString() { 72 | return getName(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/net/techcable/srglib/ReferenceType.java: -------------------------------------------------------------------------------- 1 | package net.techcable.srglib; 2 | 3 | import java.util.function.UnaryOperator; 4 | import java.util.regex.Pattern; 5 | 6 | import static java.util.Objects.*; 7 | 8 | /** 9 | * A java reference type 10 | */ 11 | /* package */ final class ReferenceType implements JavaType { 12 | private final String name, internalName; 13 | private static final Pattern TYPE_NAME_PATTERN = Pattern.compile("([\\w$_]+\\.)*([\\w$_]+)"); 14 | /* package */ ReferenceType(String name) { 15 | this.name = requireNonNull(name, "Null name"); 16 | if (!TYPE_NAME_PATTERN.matcher(name).matches()) { 17 | throw new IllegalArgumentException("Invalid class name: " + name); 18 | } 19 | this.internalName = name.replace('.', '/'); 20 | } 21 | 22 | @Override 23 | public JavaTypeSort getSort() { 24 | return JavaTypeSort.REFERENCE_TYPE; 25 | } 26 | 27 | @Override 28 | public JavaType mapClass(UnaryOperator func) { 29 | return func.apply(this); 30 | } 31 | 32 | @Override 33 | public String getInternalName() { 34 | return internalName; 35 | } 36 | 37 | @Override 38 | public String getDescriptor() { 39 | return "L" + internalName + ";"; 40 | } 41 | 42 | @Override 43 | public String getName() { 44 | return name; 45 | } 46 | 47 | @Override 48 | public String getSimpleName() { 49 | int index = name.lastIndexOf('.'); 50 | return index < 0 ? name : name.substring(index + 1); 51 | } 52 | 53 | @Override 54 | public String getPackageName() { 55 | int index = name.lastIndexOf('.'); 56 | return index < 0 ? "" : name.substring(0, index); 57 | } 58 | 59 | @Override 60 | public int hashCode() { 61 | return name.hashCode(); 62 | } 63 | 64 | @Override 65 | public boolean equals(Object o) { 66 | if (this == o) return true; 67 | if (o == null || getClass() != o.getClass()) return false; 68 | ReferenceType that = (ReferenceType) o; 69 | return name.equals(that.name); 70 | } 71 | 72 | @Override 73 | public String toString() { 74 | return getName(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/net/techcable/srglib/SrgLib.java: -------------------------------------------------------------------------------- 1 | package net.techcable.srglib; 2 | 3 | import com.google.common.base.Preconditions; 4 | 5 | import net.techcable.srglib.mappings.Mappings; 6 | 7 | import static com.google.common.base.Preconditions.*; 8 | 9 | /** 10 | * Static utility methods for handling srg. 11 | */ 12 | public final class SrgLib { 13 | private SrgLib() {} 14 | 15 | /** 16 | * Checks if the given name is a valid java identifier 17 | * 18 | * Java identifiers are used for field or method names 19 | * 20 | * @param name the name to check 21 | */ 22 | public static boolean isValidIdentifier(String name) { 23 | checkArgument(!name.isEmpty(), "Empty name: %s", name); 24 | return Character.isJavaIdentifierStart(name.codePointAt(0)) && name.codePoints() 25 | .skip(1) // Skip the first char, since we already checked it 26 | .allMatch(Character::isJavaIdentifierPart); 27 | } 28 | 29 | /** 30 | * Checks that all fields and methods in the mappings have the correct type information 31 | * 32 | * @param mappings the mappings to check 33 | */ 34 | public static void checkConsistency(Mappings mappings) { 35 | mappings.forEachField((originalField, renamedField) -> checkArgument( 36 | originalField.mapTypes(mappings::getNewType).hasSameTypes(renamedField), 37 | "Remapped field data (%s) doesn't correspond to original types (%s)", 38 | originalField, 39 | renamedField 40 | )); 41 | mappings.forEachMethod((originalMethod, renamedMethod) -> checkArgument( 42 | originalMethod.mapTypes(mappings::getNewType).hasSameTypes(renamedMethod), 43 | "Remapped method data (%s) doesn't correspond to original types (%s)", 44 | originalMethod, 45 | renamedMethod 46 | )); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/net/techcable/srglib/format/CompactSrgMappingsFormat.java: -------------------------------------------------------------------------------- 1 | package net.techcable.srglib.format; 2 | 3 | import java.io.IOException; 4 | import java.io.UncheckedIOException; 5 | import java.util.HashMap; 6 | import java.util.LinkedHashMap; 7 | import java.util.Map; 8 | import javax.annotation.Nonnull; 9 | 10 | import com.google.common.collect.ImmutableBiMap; 11 | import com.google.common.io.LineProcessor; 12 | 13 | import net.techcable.srglib.FieldData; 14 | import net.techcable.srglib.JavaType; 15 | import net.techcable.srglib.MethodData; 16 | import net.techcable.srglib.MethodSignature; 17 | import net.techcable.srglib.mappings.ImmutableMappings; 18 | import net.techcable.srglib.mappings.Mappings; 19 | 20 | /* package */ class CompactSrgMappingsFormat implements MappingsFormat { 21 | public static final CompactSrgMappingsFormat INSTANCE = new CompactSrgMappingsFormat(); 22 | 23 | @Override 24 | public LineProcessor createLineProcessor() { 25 | return new SrgLineProcessor(); 26 | } 27 | 28 | @Override 29 | public void write(Mappings mappings, Appendable output) throws IOException { 30 | try { 31 | mappings.forEachClass((original, renamed) -> { 32 | try { 33 | output.append(original.getInternalName()); 34 | output.append(' '); 35 | output.append(renamed.getInternalName()); 36 | output.append('\n'); 37 | } catch (IOException e) { 38 | throw new UncheckedIOException(e); 39 | } 40 | }); 41 | mappings.forEachField((original, renamed) -> { 42 | try { 43 | output.append(original.getDeclaringType().getInternalName()); 44 | output.append(' '); 45 | output.append(original.getName()); 46 | output.append(' '); 47 | output.append(renamed.getName()); 48 | output.append('\n'); 49 | } catch (IOException e) { 50 | throw new UncheckedIOException(e); 51 | } 52 | }); 53 | mappings.forEachMethod((original, renamed) -> { 54 | try { 55 | output.append(original.getDeclaringType().getInternalName()); 56 | output.append(' '); 57 | output.append(original.getName()); 58 | output.append(' '); 59 | output.append(original.getSignature().getDescriptor()); 60 | output.append(' '); 61 | output.append(renamed.getName()); 62 | output.append('\n'); 63 | } catch (IOException e) { 64 | throw new UncheckedIOException(e); 65 | } 66 | }); 67 | } catch (UncheckedIOException e) { 68 | throw e.getCause(); 69 | } 70 | } 71 | 72 | /* package */ static class SrgLineProcessor implements LineProcessor { 73 | private final Map types = new LinkedHashMap<>(); 74 | // We have to queue the methods and fields, since the signatures of the renamed types need to be remapped 75 | private final Map methods = new LinkedHashMap<>(); 76 | private final Map fields = new LinkedHashMap<>(); 77 | 78 | @Override 79 | public boolean processLine(@Nonnull String line) throws IOException { 80 | parseLine(line); 81 | return true; 82 | } 83 | 84 | public void parseLine(@Nonnull String line) { 85 | line = line.trim(); // Strip whitespace 86 | if (line.startsWith("#") || line.isEmpty()) return; 87 | String[] args = line.split(" "); 88 | String originalName, newName; 89 | JavaType originalDeclaringType; 90 | switch (args.length) { 91 | case 2: 92 | JavaType originalType = JavaType.fromInternalName(args[0]); 93 | JavaType renamedType = JavaType.fromInternalName(args[1]); 94 | types.put(originalType, renamedType); 95 | break; 96 | case 3: 97 | originalDeclaringType = JavaType.fromInternalName(args[0]); 98 | originalName = args[1]; 99 | newName = args[2]; 100 | fields.put(FieldData.create(originalDeclaringType, originalName), newName); 101 | break; 102 | case 4: 103 | originalDeclaringType = JavaType.fromInternalName(args[0]); 104 | originalName = args[1]; 105 | MethodSignature signature = MethodSignature.fromDescriptor(args[2]); 106 | newName = args[3]; 107 | methods.put(MethodData.create(originalDeclaringType, originalName, signature), newName); 108 | break; 109 | default: 110 | throw new IllegalArgumentException("Invalid line: " + line); 111 | } 112 | } 113 | 114 | @Override 115 | public Mappings getResult() { 116 | ImmutableBiMap types = ImmutableBiMap.copyOf(this.types); 117 | ImmutableBiMap.Builder methods = ImmutableBiMap.builder(); 118 | ImmutableBiMap.Builder fields = ImmutableBiMap.builder(); 119 | this.methods.forEach((originalData, newName) -> methods.put(originalData, originalData 120 | .mapTypes(original -> types.getOrDefault(original, original)) 121 | .withName(newName))); 122 | this.fields.forEach((originalData, newName) -> fields.put(originalData, originalData 123 | .mapTypes(original -> types.getOrDefault(original, original)) 124 | .withName(newName))); 125 | return ImmutableMappings.create(types, methods.build(), fields.build()); 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/main/java/net/techcable/srglib/format/MappingsFormat.java: -------------------------------------------------------------------------------- 1 | package net.techcable.srglib.format; 2 | 3 | import java.io.BufferedWriter; 4 | import java.io.CharArrayReader; 5 | import java.io.CharArrayWriter; 6 | import java.io.File; 7 | import java.io.FileInputStream; 8 | import java.io.FileOutputStream; 9 | import java.io.IOException; 10 | import java.io.InputStreamReader; 11 | import java.io.OutputStreamWriter; 12 | import java.io.Reader; 13 | import java.io.Writer; 14 | import java.nio.file.Files; 15 | import java.util.Arrays; 16 | import java.util.Iterator; 17 | import java.util.List; 18 | 19 | import com.google.common.base.Charsets; 20 | import com.google.common.collect.ImmutableList; 21 | import com.google.common.collect.ImmutableMap; 22 | import com.google.common.io.CharStreams; 23 | import com.google.common.io.LineProcessor; 24 | import com.google.common.io.LineReader; 25 | 26 | import net.techcable.srglib.mappings.Mappings; 27 | import net.techcable.srglib.utils.Exceptions; 28 | 29 | import static net.techcable.srglib.utils.Exceptions.*; 30 | 31 | /** 32 | * A format for serializing mappings to and from text. 33 | */ 34 | public interface MappingsFormat { 35 | MappingsFormat SEARGE_FORMAT = SrgMappingsFormat.INSTANCE; 36 | MappingsFormat COMPACT_SEARGE_FORMAT = CompactSrgMappingsFormat.INSTANCE; 37 | 38 | default Mappings parse(Readable readable) throws IOException { 39 | LineReader lineReader = new LineReader(readable); 40 | LineProcessor lineProcessor = createLineProcessor(); 41 | String line; 42 | while ((line = lineReader.readLine()) != null) { 43 | if (!lineProcessor.processLine(line)) { 44 | break; 45 | } 46 | } 47 | return lineProcessor.getResult(); 48 | } 49 | 50 | default Mappings parseFile(File file) throws IOException { 51 | try (Reader in = new InputStreamReader(new FileInputStream(file), Charsets.UTF_8)) { 52 | // Don't worry, parse(Readable) buffers internally 53 | return parse(in); 54 | } 55 | } 56 | 57 | default Mappings parseLines(String... lines) { 58 | return parseLines(Arrays.asList(lines)); 59 | } 60 | 61 | default Mappings parseLines(Iterable lines) { 62 | return parseLines(lines.iterator()); 63 | } 64 | 65 | default Mappings parseLines(Iterator lines) { 66 | LineProcessor lineProcessor = createLineProcessor(); 67 | lines.forEachRemaining(Exceptions.sneakyThrowing(lineProcessor::processLine)); 68 | return lineProcessor.getResult(); 69 | } 70 | 71 | LineProcessor createLineProcessor(); 72 | 73 | void write(Mappings mappings, Appendable output) throws IOException; 74 | 75 | default void writeToFile(Mappings mappings, File file) throws IOException { 76 | try (Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), Charsets.UTF_8))) { 77 | write(mappings, out); 78 | } 79 | } 80 | 81 | default List toLines(Mappings mappings) { 82 | CharArrayWriter result = new CharArrayWriter(); 83 | return sneakyThrowing(() -> { 84 | this.write(mappings, result); 85 | return CharStreams.readLines(new CharArrayReader(result.toCharArray())); 86 | }).get(); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/net/techcable/srglib/format/SrgMappingsFormat.java: -------------------------------------------------------------------------------- 1 | package net.techcable.srglib.format; 2 | 3 | import java.io.IOException; 4 | import java.io.UncheckedIOException; 5 | import javax.annotation.Nonnull; 6 | 7 | import com.google.common.io.LineProcessor; 8 | 9 | import net.techcable.srglib.FieldData; 10 | import net.techcable.srglib.JavaType; 11 | import net.techcable.srglib.MethodData; 12 | import net.techcable.srglib.MethodSignature; 13 | import net.techcable.srglib.mappings.Mappings; 14 | import net.techcable.srglib.mappings.MutableMappings; 15 | import net.techcable.srglib.utils.Exceptions; 16 | 17 | import static com.google.common.base.Preconditions.*; 18 | 19 | /* package */ class SrgMappingsFormat implements MappingsFormat { 20 | public static final SrgMappingsFormat INSTANCE = new SrgMappingsFormat(); 21 | 22 | private SrgMappingsFormat() { 23 | } 24 | 25 | @Override 26 | public LineProcessor createLineProcessor() { 27 | return new SrgLineProcessor(); 28 | } 29 | 30 | @Override 31 | public void write(Mappings mappings, Appendable output) throws IOException { 32 | try { 33 | mappings.forEachClass(Exceptions.sneakyThrowing((original, renamed) -> { 34 | output.append("CL: "); 35 | output.append(original.getInternalName()); 36 | output.append(' '); 37 | output.append(renamed.getInternalName()); 38 | output.append('\n'); 39 | })); 40 | mappings.forEachField(Exceptions.sneakyThrowing((original, renamed) -> { 41 | output.append("FD: "); 42 | output.append(original.getInternalName()); 43 | output.append(' '); 44 | output.append(renamed.getInternalName()); 45 | output.append('\n'); 46 | })); 47 | mappings.forEachMethod(Exceptions.sneakyThrowing((original, renamed) -> { 48 | output.append("MD: "); 49 | output.append(original.getInternalName()); 50 | output.append(' '); 51 | output.append(original.getSignature().getDescriptor()); 52 | output.append(' '); 53 | output.append(renamed.getInternalName()); 54 | output.append(' '); 55 | output.append(renamed.getSignature().getDescriptor()); 56 | output.append('\n'); 57 | })); 58 | } catch (UncheckedIOException e) { 59 | throw e.getCause(); 60 | } 61 | } 62 | 63 | /* package */ static class SrgLineProcessor implements LineProcessor { 64 | private final MutableMappings result = MutableMappings.create(); 65 | 66 | @Override 67 | public boolean processLine(@Nonnull String line) throws IOException { 68 | parseLine(line); 69 | return true; 70 | } 71 | 72 | public void parseLine(@Nonnull String line) { 73 | line = line.trim(); // Strip whitespace 74 | if (line.startsWith("#") || line.isEmpty()) return; 75 | checkArgument(line.length() >= 4, "Invalid line: %s", line); 76 | String id = line.substring(0, 2); 77 | String[] args = line.substring(4).split(" "); 78 | final String originalInternalName, renamedInternalName; 79 | switch (id) { 80 | case "MD": 81 | checkArgument(args.length == 4, "Invalid line: %s", line); 82 | originalInternalName = args[0]; 83 | MethodSignature originalSignature = MethodSignature.fromDescriptor(args[1]); 84 | renamedInternalName = args[2]; 85 | MethodSignature renamedSignature = MethodSignature.fromDescriptor(args[3]); 86 | MethodData originalMethodData = MethodData.fromInternalName(originalInternalName, originalSignature); 87 | MethodData renamedMethodData = MethodData.fromInternalName(renamedInternalName, renamedSignature); 88 | result.putMethod(originalMethodData, renamedMethodData); 89 | return; 90 | case "FD": 91 | checkArgument(args.length == 2, "Invalid line: %s", line); 92 | originalInternalName = args[0]; 93 | renamedInternalName = args[1]; 94 | FieldData originalFieldData = FieldData.fromInternalName(originalInternalName); 95 | FieldData renamedFieldData = FieldData.fromInternalName(renamedInternalName); 96 | result.putField(originalFieldData, renamedFieldData); 97 | return; 98 | case "CL": 99 | checkArgument(args.length == 2, "Invalid line: %s", line); 100 | originalInternalName = args[0]; 101 | renamedInternalName = args[1]; 102 | JavaType originalType = JavaType.fromInternalName(originalInternalName); 103 | JavaType renamedType = JavaType.fromInternalName(renamedInternalName); 104 | result.putClass(originalType, renamedType); 105 | return; 106 | case "PK": 107 | return; // Ignore packages, because they are stupid 108 | default: 109 | throw new IllegalArgumentException("Invalid line: " + line); 110 | } 111 | } 112 | 113 | @Override 114 | public Mappings getResult() { 115 | return result; 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/net/techcable/srglib/mappings/ImmutableMappings.java: -------------------------------------------------------------------------------- 1 | package net.techcable.srglib.mappings; 2 | 3 | import java.util.Map; 4 | import java.util.Set; 5 | import java.util.function.BiConsumer; 6 | import java.util.function.UnaryOperator; 7 | import javax.annotation.Nullable; 8 | 9 | import com.google.common.base.MoreObjects; 10 | import com.google.common.collect.ImmutableBiMap; 11 | 12 | import net.techcable.srglib.FieldData; 13 | import net.techcable.srglib.JavaType; 14 | import net.techcable.srglib.MethodData; 15 | import net.techcable.srglib.SrgLib; 16 | import net.techcable.srglib.utils.ImmutableMaps; 17 | 18 | import static com.google.common.base.Preconditions.*; 19 | import static java.util.Objects.*; 20 | 21 | public final class ImmutableMappings implements Mappings { 22 | private final ImmutableBiMap classes; 23 | private final ImmutableBiMap methods; 24 | private final ImmutableBiMap fields; 25 | /* package */ static final ImmutableMappings EMPTY = new ImmutableMappings(ImmutableBiMap.of(), ImmutableBiMap.of(), ImmutableBiMap.of()); 26 | 27 | private ImmutableMappings( 28 | ImmutableBiMap classes, 29 | ImmutableBiMap methods, 30 | ImmutableBiMap fields 31 | ) { 32 | this.classes = requireNonNull(classes, "Null types"); 33 | this.methods = requireNonNull(methods, "Null methods"); 34 | this.fields = requireNonNull(fields, "Null fields"); 35 | } 36 | 37 | @Override 38 | public JavaType getNewClass(JavaType original) { 39 | checkArgument(original.isReferenceType(), "Type isn't a reference type: %s", original); 40 | return classes.getOrDefault(requireNonNull(original), original); 41 | } 42 | 43 | @Override 44 | public MethodData getNewMethod(MethodData original) { 45 | MethodData result = methods.get(requireNonNull(original)); 46 | if (result != null) { 47 | return result; 48 | } else { 49 | return original.mapTypes(this::getNewType); 50 | } 51 | } 52 | 53 | @Override 54 | public FieldData getNewField(FieldData original) { 55 | FieldData result = fields.get(requireNonNull(original)); 56 | if (result != null) { 57 | return result; 58 | } else { 59 | return original.mapTypes(this::getNewType); 60 | } 61 | } 62 | 63 | @Override 64 | public Set classes() { 65 | return classes.keySet(); 66 | } 67 | 68 | @Override 69 | public Set methods() { 70 | return methods.keySet(); 71 | } 72 | 73 | @Override 74 | public Set fields() { 75 | return fields.keySet(); 76 | } 77 | 78 | @Override 79 | public ImmutableMappings snapshot() { 80 | return this; 81 | } 82 | 83 | @Nullable 84 | private ImmutableMappings inverted; 85 | @Override 86 | public ImmutableMappings inverted() { 87 | ImmutableMappings inverted = this.inverted; 88 | return inverted != null ? inverted : (this.inverted = invert0()); 89 | } 90 | 91 | private ImmutableMappings invert0() { 92 | ImmutableMappings inverted = new ImmutableMappings(this.classes.inverse(), this.methods.inverse(), this.fields.inverse()); 93 | inverted.inverted = this; 94 | return inverted; 95 | } 96 | 97 | public static ImmutableMappings copyOf( 98 | Map originalClasses, 99 | Map methodNames, 100 | Map fieldNames 101 | ) { 102 | ImmutableBiMap classes = ImmutableBiMap.copyOf(originalClasses); // Defensive copy to an ImmutableBiMap 103 | // No consistency check needed since we're building type-information from scratch 104 | ImmutableBiMap.Builder methods = ImmutableBiMap.builder(); 105 | ImmutableBiMap.Builder fields = ImmutableBiMap.builder(); 106 | methodNames.forEach((originalData, newName) -> { 107 | MethodData newData = originalData 108 | .mapTypes((oldType) -> oldType.mapClass(oldClass -> classes.getOrDefault(oldClass, oldClass))) 109 | .withName(newName); 110 | methods.put(originalData, newData); 111 | }); 112 | fieldNames.forEach((originalData, newName) -> { 113 | FieldData newData = FieldData.create( 114 | originalData.getDeclaringType().mapClass(oldClass -> classes.getOrDefault(oldClass, oldClass)), 115 | newName 116 | ); 117 | fields.put(originalData, newData); 118 | }); 119 | return new ImmutableMappings( 120 | ImmutableBiMap.copyOf(classes), 121 | methods.build(), 122 | fields.build() 123 | ); 124 | } 125 | 126 | /** 127 | * Create new ImmutableMappings with the specified data. 128 | *

129 | * NOTE: {@link #copyOf(Map, Map, Map)} may be preferable, 130 | * as it automatically remaps method signatures for you. 131 | *

132 | * 133 | * @param classes the class data mappings 134 | * @param methods the method data mappings 135 | * @param fields the field data mappings 136 | * @throws IllegalArgumentException if any of the types in the fields or methods don't match the type data 137 | * @return immutable mappings with the specified data 138 | */ 139 | public static ImmutableMappings create( 140 | ImmutableBiMap classes, 141 | ImmutableBiMap methods, 142 | ImmutableBiMap fields 143 | ) { 144 | ImmutableMappings result = new ImmutableMappings(classes, methods, fields); 145 | SrgLib.checkConsistency(result); 146 | return result; 147 | } 148 | 149 | public static ImmutableMappings copyOf(Mappings other) { 150 | if (other instanceof ImmutableMappings) { 151 | return (ImmutableMappings) other; 152 | } else if (other instanceof SimpleMappings) { 153 | return other.snapshot(); 154 | } else { 155 | return create( 156 | ImmutableMaps.createBiMap(other.classes(), other::getNewType), 157 | ImmutableMaps.createBiMap(other.methods(), other::getNewMethod), 158 | ImmutableMaps.createBiMap(other.fields(), other::getNewField) 159 | ); 160 | } 161 | } 162 | 163 | @Override 164 | public void forEachClass(BiConsumer action) { 165 | classes.forEach(action); 166 | } 167 | 168 | @Override 169 | public void forEachMethod(BiConsumer action) { 170 | methods.forEach(action); 171 | } 172 | 173 | @Override 174 | public void forEachField(BiConsumer action) { 175 | fields.forEach(action); 176 | } 177 | 178 | @Override 179 | public int hashCode() { 180 | return classes.hashCode() ^ methods.hashCode() ^ fields.hashCode(); 181 | } 182 | 183 | @Override 184 | public boolean equals(Object obj) { 185 | if (obj == this) { 186 | return true; 187 | } else if (obj == null) { 188 | return false; 189 | } else if (obj.getClass() == ImmutableMappings.class) { 190 | return this.classes.equals(((ImmutableMappings) obj).classes) 191 | && this.methods.equals(((ImmutableMappings) obj).methods) 192 | && this.fields.equals(((ImmutableMappings) obj).fields); 193 | } else if (obj instanceof Mappings) { 194 | return this.equals(((Mappings) obj).snapshot()); 195 | } else { 196 | return false; 197 | } 198 | } 199 | 200 | @Override 201 | public String toString() { 202 | return MoreObjects.toStringHelper(Mappings.class) 203 | .add("classes", ImmutableMaps.joinToString( 204 | classes, 205 | (original, renamed) -> String.format(" %s = %s", original.getName(), renamed.getName()), 206 | "\n", "{", "}" 207 | )) 208 | .add("methods", ImmutableMaps.joinToString( 209 | methods, 210 | (original, renamed) -> String.format(" %s = %s", original, renamed), 211 | "\n", "{\n", "\n}" 212 | )) 213 | .add("fields", ImmutableMaps.joinToString( 214 | fields, 215 | (original, renamed) -> String.format(" %s = %s", original, renamed), 216 | "\n", "{\n", "\n}" 217 | )) 218 | .toString(); 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /src/main/java/net/techcable/srglib/mappings/Mappings.java: -------------------------------------------------------------------------------- 1 | package net.techcable.srglib.mappings; 2 | 3 | import java.lang.reflect.Field; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | import java.util.Set; 7 | import java.util.function.BiConsumer; 8 | import java.util.function.Function; 9 | import java.util.function.UnaryOperator; 10 | import javax.annotation.Nullable; 11 | 12 | import com.google.common.collect.BiMap; 13 | import com.google.common.collect.HashBiMap; 14 | import com.google.common.collect.ImmutableBiMap; 15 | import com.google.common.collect.ImmutableList; 16 | import com.google.common.collect.ImmutableMap; 17 | 18 | import net.techcable.srglib.FieldData; 19 | import net.techcable.srglib.JavaType; 20 | import net.techcable.srglib.MethodData; 21 | 22 | import static java.util.Objects.*; 23 | 24 | /** 25 | * A mapping from one set of source names to another. 26 | */ 27 | public interface Mappings { 28 | /** 29 | * Get the remapped class name, given the original class name. 30 | *

31 | * Returns the original class if no mapping is found. 32 | *

33 | * 34 | * @param original the original class name 35 | * @return the new class name 36 | */ 37 | default JavaType getNewClass(String original) { 38 | return getNewClass(JavaType.fromName(original)); 39 | } 40 | 41 | 42 | /** 43 | * Get the remapped class, given the original class, or the original if no class is found. 44 | * 45 | * @param original the original class 46 | * @return the new class 47 | * @throws IllegalArgumentException if the class isn't a reference type 48 | */ 49 | JavaType getNewClass(JavaType original); 50 | 51 | 52 | /** 53 | * Get the remapped type, given the original type, or the original if the type is found. 54 | *

55 | * If type is an array, it remaps the innermost element type. 56 | * If the type is a class, the result is the same as invoking {@link #getNewClass(JavaType)} 57 | * If the type is a primitive, it returns the same element type. 58 | *

59 | * 60 | * @param original the original type 61 | * @return the new type 62 | */ 63 | default JavaType getNewType(JavaType original) { 64 | return requireNonNull(original, "Null type").mapClass(this::getNewClass); 65 | } 66 | 67 | /** 68 | * Get the remapped method data, given the original data. 69 | *

70 | * Automatically remaps class names in the signature as needed, 71 | * even if the method name remains the same. 72 | *

73 | * 74 | * @param original the original method data 75 | * @return the remapped method data 76 | */ 77 | MethodData getNewMethod(MethodData original); 78 | 79 | /** 80 | * Get the remapped field data, given the original data. 81 | *

82 | * Automatically remaps class names in the signature as needed, 83 | * even if the field name remains the same. 84 | *

85 | * 86 | * @param original the original field data 87 | * @return the remapped field data 88 | */ 89 | FieldData getNewField(FieldData original); 90 | 91 | /** 92 | * Return an immutable snapshot of these mappings. 93 | * 94 | * @return an immutable snapshot 95 | */ 96 | default ImmutableMappings snapshot() { 97 | return ImmutableMappings.copyOf(this); 98 | } 99 | 100 | /** 101 | * Return an inverted copy of the mappings, switching the original and renamed. 102 | *

103 | * Even if this mapping's underlying source is mutable, 104 | * changes in this mapping will not be reflected in the resulting view. 105 | *

106 | * 107 | * @return an inverted copy 108 | */ 109 | default Mappings inverted() { 110 | return snapshot().inverted(); 111 | } 112 | 113 | /** 114 | * Return the original classes known to these mappings. 115 | * 116 | * @return the original classes. 117 | */ 118 | Set classes(); 119 | 120 | 121 | /** 122 | * Return the original methods known to these mappings. 123 | * 124 | * @return the original methods. 125 | */ 126 | Set methods(); 127 | 128 | /** 129 | * Return the original fields known to these mappings. 130 | * 131 | * @return the original fields. 132 | */ 133 | Set fields(); 134 | 135 | default boolean contains(JavaType type) { 136 | return classes().contains(type); 137 | } 138 | 139 | default boolean contains(MethodData methodData) { 140 | return methods().contains(methodData); 141 | } 142 | 143 | default boolean contains(FieldData fieldData) { 144 | return fields().contains(fieldData); 145 | } 146 | 147 | default void forEachClass(BiConsumer action) { 148 | classes().forEach((original) -> action.accept(original, getNewType(original))); 149 | } 150 | 151 | default void forEachMethod(BiConsumer action) { 152 | methods().forEach((original) -> action.accept(original, getNewMethod(original))); 153 | } 154 | 155 | default void forEachField(BiConsumer action) { 156 | fields().forEach((original) -> action.accept(original, getNewField(original))); 157 | } 158 | 159 | /** 160 | * Transform all the original data in the specified mapping, using this mapping. 161 | * 162 | * This is useful for {@link #createRenamingMappings(UnaryOperator, Function, Function)}, 163 | * since renaming mappings have no 'original' data of their own, and so can't be directly output to a file. 164 | * The returned mapping data is guaranteed to have the same originals as the data of the old mapping data. 165 | * 166 | * @return the transformed data 167 | */ 168 | default Mappings transform(Mappings original) { 169 | ImmutableBiMap.Builder types = ImmutableBiMap.builder(); 170 | ImmutableBiMap.Builder methods = ImmutableBiMap.builder(); 171 | ImmutableBiMap.Builder fields = ImmutableBiMap.builder(); 172 | original.classes().forEach(originalType -> { 173 | JavaType newType = this.getNewType(originalType); 174 | types.put(originalType, newType); 175 | }); 176 | original.methods().forEach(originalMethodData -> { 177 | MethodData newMethodData = this.getNewMethod(originalMethodData); 178 | methods.put(originalMethodData, newMethodData); 179 | }); 180 | original.fields().forEach(originalFieldData -> { 181 | FieldData newFieldData = this.getNewField(originalFieldData); 182 | fields.put(originalFieldData, newFieldData); 183 | }); 184 | return ImmutableMappings.create(types.build(), methods.build(), fields.build()); 185 | } 186 | 187 | /** 188 | * Return an immutable empty mappings instance. 189 | * 190 | * @return an immutable empty mappings object. 191 | */ 192 | static ImmutableMappings empty() { 193 | return ImmutableMappings.EMPTY; 194 | } 195 | 196 | /** 197 | * Chain the specified mappings together, using the renamed result of each mapping as the original for the next 198 | * 199 | * @param mappings the mappings to chain together 200 | */ 201 | static Mappings chain(Mappings... mappings) { 202 | return chain(ImmutableList.copyOf(mappings)); 203 | } 204 | 205 | /** 206 | * Chain the specified mappings together, using the renamed result of each mapping as the original for the next 207 | * 208 | * @param mappings the mappings to chain together 209 | */ 210 | static Mappings chain(ImmutableList mappings) { 211 | ImmutableMappings chained = empty(); 212 | for (int i = 0; i < mappings.size(); i++) { 213 | Mappings mapping = mappings.get(i); 214 | ImmutableBiMap.Builder classes = ImmutableBiMap.builder(); 215 | ImmutableBiMap.Builder methods = ImmutableBiMap.builder(); 216 | ImmutableBiMap.Builder fields = ImmutableBiMap.builder(); 217 | ImmutableMappings inverted = chained.inverted(); 218 | 219 | // If we encounter a new name, add it to the set 220 | mapping.forEachClass((original, renamed) -> { 221 | if (!inverted.contains(original)) { 222 | classes.put(original, renamed); 223 | } 224 | }); 225 | mapping.forEachField((original, renamed) -> { 226 | if (!inverted.contains(original)) { 227 | // We need to make sure the originals we put in the map have the oldest possible type name to remain consistent 228 | // Since inverted is a map of new->old, use the old type name if we've ever seen this class before 229 | fields.put(original.mapTypes(inverted::getNewType), renamed); 230 | } 231 | }); 232 | mapping.forEachMethod((original, renamed) -> { 233 | if (!inverted.contains(original)) { 234 | methods.put(original.mapTypes(inverted::getNewType), renamed); 235 | } 236 | }); 237 | // Now run all our current chain through the mapping to get our new result 238 | chained.forEachClass((original, renamed) -> { 239 | renamed = mapping.getNewType(renamed); 240 | classes.put(original, renamed); 241 | }); 242 | chained.forEachField((original, renamed) -> { 243 | renamed = mapping.getNewField(renamed); 244 | fields.put(original, renamed); 245 | }); 246 | chained.forEachMethod((original, renamed) -> { 247 | renamed = mapping.getNewMethod(renamed); 248 | methods.put(original, renamed); 249 | }); 250 | chained = ImmutableMappings.create(classes.build(), methods.build(), fields.build()); 251 | } 252 | return chained; 253 | } 254 | 255 | 256 | /** 257 | * Mappings which rename classes/methods/fields dynamically, based entirely on transformer functions. 258 | *

259 | * Unlike most other mappings, these mappings have no fields, methods, or classes of their own, 260 | * and just rename whatever they are given. 261 | * In order to use them with class data, use them to {@link #transform(Mappings)} some other set of mappings. 262 | *

263 | *

264 | * The functions are expected to be 'pure', and always give the same output for any input. 265 | * The type transformer is only called for references, and can't remap primitives. 266 | * A 'null' transformer does nothing, and simply returns the existing name. 267 | * Method and field signatures are automatically remapped. 268 | *

269 | * 270 | * @param typeTransformer the function to transform/rename the classes 271 | * @param methodRenamer the function to rename the methods 272 | * @param fieldRenamer the function to rename the fields 273 | * @return a mapping which remaps members using the specified methods 274 | */ 275 | static Mappings createRenamingMappings( 276 | @Nullable UnaryOperator typeTransformer, 277 | @Nullable Function methodRenamer, 278 | @Nullable Function fieldRenamer 279 | ) { 280 | return new RenamingMappings(typeTransformer, methodRenamer, fieldRenamer); 281 | } 282 | 283 | /** 284 | * Mappings which dynamically remap classes from one package into another. 285 | *

286 | * Unlike other mappings, these mappings have no fields, methods, or classes of their own, 287 | * and just rename whatever they are given, similar to {@link #createRenamingMappings(UnaryOperator, Function, Function)}. 288 | * Method and field signatures are automatically remapped. 289 | *

290 | * 291 | * @param packages the packages to remap 292 | * @return a package mapping 293 | */ 294 | static Mappings createPackageMappings(ImmutableMap packages) { 295 | return createRenamingMappings((original) -> { 296 | String originalPackage = original.getPackageName(); 297 | String newPackage = packages.get(originalPackage); 298 | if (newPackage != null) { 299 | return JavaType.fromName(newPackage + "." + original.getSimpleName()); 300 | } else { 301 | return original; 302 | } 303 | }, null, null); 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /src/main/java/net/techcable/srglib/mappings/MutableMappings.java: -------------------------------------------------------------------------------- 1 | package net.techcable.srglib.mappings; 2 | 3 | import java.util.HashMap; 4 | 5 | import com.google.common.collect.HashBiMap; 6 | 7 | import net.techcable.srglib.FieldData; 8 | import net.techcable.srglib.JavaType; 9 | import net.techcable.srglib.MethodData; 10 | 11 | import static com.google.common.base.Preconditions.*; 12 | 13 | /** 14 | * Mappings that can be modified 15 | */ 16 | public interface MutableMappings extends Mappings { 17 | 18 | /** 19 | * Set a class's new name. 20 | * 21 | * @param original the original name 22 | * @param renamed the class's new name 23 | * @throws IllegalArgumentException if the class isn't a reference type 24 | */ 25 | void putClass(JavaType original, JavaType renamed); 26 | 27 | /** 28 | * Set a method's new name, ensuring the signatures match. 29 | *

30 | * After mapping the method's signature to the new type names the signatures must match, 31 | * so that {@code original.mapTypes(mappings::getNewType)} equals the new types. 32 | *

33 | * 34 | * @param original the original method data 35 | * @param renamed the new method data 36 | * @throws IllegalArgumentException if the signatures mismatch 37 | */ 38 | default void putMethod(MethodData original, MethodData renamed) { 39 | checkArgument( 40 | original.mapTypes(this::getNewType).hasSameTypes(renamed), 41 | "Remapped method data types (%s) don't correspond to original types (%s)", 42 | renamed, 43 | original 44 | ); 45 | putMethod(original, renamed.getName()); 46 | } 47 | 48 | /** 49 | * Set the method's new name. 50 | * 51 | * @param original the original method data 52 | * @param newName the new method name 53 | */ 54 | void putMethod(MethodData original, String newName); 55 | 56 | /** 57 | * Set a fields's new name, ensuring the signatures match. 58 | *

59 | * After mapping the method's signature to the new type names the signatures must match, 60 | * so that {@code original.mapTypes(mappings::getNewType)} equals the new types. 61 | *

62 | * 63 | * @param original the original method data 64 | * @param renamed the new method data 65 | * @throws IllegalArgumentException if the signatures mismatch 66 | */ 67 | default void putField(FieldData original, FieldData renamed) { 68 | checkArgument( 69 | original.mapTypes(this::getNewType).hasSameTypes(renamed), 70 | "Remapped field data (%s) doesn't correspond to original types (%s)", 71 | renamed, 72 | original 73 | ); 74 | putField(original, renamed.getName()); 75 | } 76 | 77 | /** 78 | * Set a fields's new name. 79 | * 80 | * @param original the original method data 81 | * @param newName the new name 82 | */ 83 | void putField(FieldData original, String newName); 84 | 85 | /** 86 | * Return an inverted copy of the mappings, switching the original and renamed. 87 | *

88 | * Changes in this mapping will not be reflected in the resulting view 89 | *

90 | * 91 | * @return an inverted copy 92 | */ 93 | @Override 94 | Mappings inverted(); 95 | 96 | /** 97 | * Create a new mutable mappings object, with no contents. 98 | * 99 | * @return a new mutable mappings 100 | */ 101 | static MutableMappings create() { 102 | return new SimpleMappings(HashBiMap.create(), new HashMap<>(), new HashMap<>()); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/net/techcable/srglib/mappings/RenamingMappings.java: -------------------------------------------------------------------------------- 1 | package net.techcable.srglib.mappings; 2 | 3 | import java.util.Set; 4 | import java.util.function.Function; 5 | import java.util.function.UnaryOperator; 6 | import javax.annotation.Nullable; 7 | 8 | import com.google.common.collect.ImmutableSet; 9 | 10 | import net.techcable.srglib.FieldData; 11 | import net.techcable.srglib.JavaType; 12 | import net.techcable.srglib.MethodData; 13 | 14 | import static com.google.common.base.Preconditions.checkArgument; 15 | 16 | /* package */ final class RenamingMappings implements Mappings { 17 | private final UnaryOperator typeTransformer; 18 | private final Function methodRenamer; 19 | private final Function fieldRenamer; 20 | public RenamingMappings( 21 | @Nullable UnaryOperator typeTransformer, 22 | @Nullable Function methodRenamer, 23 | @Nullable Function fieldRenamer 24 | ) { 25 | this.typeTransformer = typeTransformer != null ? typeTransformer : UnaryOperator.identity(); 26 | this.methodRenamer = methodRenamer != null ? methodRenamer : MethodData::getName; 27 | this.fieldRenamer = methodRenamer != null ? fieldRenamer : FieldData::getName; 28 | } 29 | 30 | @Override 31 | public JavaType getNewClass(JavaType original) { 32 | checkArgument(original.isReferenceType(), "Type isn't a reference type: %s", original); 33 | JavaType result = typeTransformer.apply(original); 34 | return result == null ? original : result; 35 | } 36 | 37 | @Override 38 | public MethodData getNewMethod(MethodData original) { 39 | return original 40 | .mapTypes(this::getNewType) 41 | .withName(methodRenamer.apply(original)); 42 | } 43 | 44 | @Override 45 | public FieldData getNewField(FieldData original) { 46 | return original 47 | .mapTypes(this::getNewType) 48 | .withName(fieldRenamer.apply(original)); 49 | } 50 | 51 | @Override 52 | public Set classes() { 53 | return ImmutableSet.of(); 54 | } 55 | 56 | @Override 57 | public Set methods() { 58 | return ImmutableSet.of(); 59 | } 60 | 61 | @Override 62 | public Set fields() { 63 | return ImmutableSet.of(); 64 | } 65 | 66 | @Override 67 | public int hashCode() { 68 | return typeTransformer.hashCode() ^ methodRenamer.hashCode() ^ fieldRenamer.hashCode(); 69 | } 70 | 71 | @Override 72 | public Mappings inverted() { 73 | throw new UnsupportedOperationException(); // Doesn't make much sense 74 | } 75 | 76 | @Override 77 | public ImmutableMappings snapshot() { 78 | throw new UnsupportedOperationException(); // Doesn't make much sense 79 | } 80 | 81 | @Override 82 | public boolean equals(Object obj) { 83 | return this == obj || obj.getClass() == RenamingMappings.class 84 | && ((RenamingMappings) obj).typeTransformer.equals(this.typeTransformer) 85 | && ((RenamingMappings) obj).methodRenamer.equals(this.methodRenamer) 86 | && ((RenamingMappings) obj).fieldRenamer.equals(this.fieldRenamer); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/net/techcable/srglib/mappings/SimpleMappings.java: -------------------------------------------------------------------------------- 1 | package net.techcable.srglib.mappings; 2 | 3 | import java.util.Map; 4 | import java.util.Set; 5 | import java.util.function.BiConsumer; 6 | 7 | import com.google.common.collect.BiMap; 8 | 9 | import net.techcable.srglib.FieldData; 10 | import net.techcable.srglib.JavaType; 11 | import net.techcable.srglib.MethodData; 12 | 13 | import static com.google.common.base.Preconditions.*; 14 | import static java.util.Objects.*; 15 | 16 | /* package */ class SimpleMappings implements MutableMappings { 17 | private final BiMap classes; 18 | private final Map methodNames; 19 | private final Map fieldNames; 20 | 21 | /* package */ SimpleMappings( 22 | BiMap classes, 23 | Map methodNames, 24 | Map fieldNames 25 | ) { 26 | this.classes = requireNonNull(classes, "Null types"); 27 | this.methodNames = requireNonNull(methodNames, "Null methods"); 28 | this.fieldNames = requireNonNull(fieldNames, "Null fields"); 29 | } 30 | 31 | @Override 32 | public void putClass(JavaType original, JavaType renamed) { 33 | checkArgument(original.isReferenceType(), "Original type isn't a reference type: %s", original); 34 | checkArgument(renamed.isReferenceType(), "Renamed type isn't a reference type: %s", renamed); 35 | if (original.equals(renamed)) { 36 | classes.remove(original); 37 | } else { 38 | classes.put(original, renamed); 39 | } 40 | } 41 | 42 | @Override 43 | public void putMethod(MethodData original, String newName) { 44 | methodNames.put(checkNotNull(original, "Null original"), checkNotNull(newName, "Null newName")); 45 | } 46 | 47 | @Override 48 | public void putField(FieldData original, String newName) { 49 | fieldNames.put(checkNotNull(original, "Null original"), checkNotNull(newName, "Null newName")); 50 | } 51 | 52 | @Override 53 | public JavaType getNewClass(JavaType original) { 54 | checkArgument(original.isReferenceType(), "Type isn't a reference type: %s", original); 55 | return classes.getOrDefault(requireNonNull(original), original); 56 | } 57 | 58 | @Override 59 | public MethodData getNewMethod(MethodData original) { 60 | String newName = methodNames.getOrDefault(original, original.getName()); 61 | return original.mapTypes(this::getNewType).withName(newName); 62 | } 63 | 64 | @Override 65 | public FieldData getNewField(FieldData original) { 66 | String newName = fieldNames.getOrDefault(original, original.getName()); 67 | return FieldData.create(getNewType(original.getDeclaringType()), newName); 68 | } 69 | 70 | @Override 71 | public ImmutableMappings snapshot() { 72 | return ImmutableMappings.copyOf( 73 | this.classes, 74 | this.methodNames, 75 | this.fieldNames 76 | ); 77 | } 78 | 79 | @Override 80 | public Set classes() { 81 | return classes.keySet(); 82 | } 83 | 84 | @Override 85 | public Set methods() { 86 | return methodNames.keySet(); 87 | } 88 | 89 | @Override 90 | public Set fields() { 91 | return fieldNames.keySet(); 92 | } 93 | 94 | @Override 95 | public Mappings inverted() { 96 | return snapshot().inverted(); 97 | } 98 | 99 | @Override 100 | public void forEachClass(BiConsumer action) { 101 | classes.forEach(action); 102 | } 103 | 104 | @Override 105 | public void forEachMethod(BiConsumer action) { 106 | methodNames.forEach((originalData, newName) -> { 107 | MethodData newData = originalData.mapTypes(this::getNewType).withName(newName); 108 | action.accept(originalData, newData); 109 | }); 110 | } 111 | 112 | @Override 113 | public void forEachField(BiConsumer action) { 114 | fieldNames.forEach((originalData, newName) -> { 115 | FieldData newData = FieldData.create(getNewType(originalData.getDeclaringType()), newName); 116 | action.accept(originalData, newData); 117 | }); 118 | } 119 | 120 | @Override 121 | public boolean equals(Object otherObj) { 122 | if (this == otherObj) return true; 123 | if (otherObj == null) return false; 124 | if (otherObj.getClass() == SimpleMappings.class) { 125 | SimpleMappings other = (SimpleMappings) otherObj; 126 | return classes.equals(other.classes) && methodNames.equals(other.methodNames) && fieldNames.equals(other.fieldNames); 127 | } else if (otherObj instanceof Mappings) { 128 | return this.snapshot().equals(((Mappings) otherObj).snapshot()); 129 | } else { 130 | return false; 131 | } 132 | } 133 | 134 | @Override 135 | public int hashCode() { 136 | int result = classes.hashCode(); 137 | result = 31 * result + methodNames.hashCode(); 138 | result = 31 * result + fieldNames.hashCode(); 139 | return result; 140 | } 141 | 142 | @Override 143 | public String toString() { 144 | return snapshot().toString(); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/main/java/net/techcable/srglib/utils/CheckedBiConsumer.java: -------------------------------------------------------------------------------- 1 | package net.techcable.srglib.utils; 2 | 3 | public interface CheckedBiConsumer { 4 | void accept(T first, U second) throws E; 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/net/techcable/srglib/utils/CheckedConsumer.java: -------------------------------------------------------------------------------- 1 | package net.techcable.srglib.utils; 2 | 3 | public interface CheckedConsumer { 4 | void accept(T obj) throws E; 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/net/techcable/srglib/utils/CheckedRunnable.java: -------------------------------------------------------------------------------- 1 | package net.techcable.srglib.utils; 2 | 3 | @FunctionalInterface 4 | public interface CheckedRunnable { 5 | void run() throws E; 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/net/techcable/srglib/utils/CheckedSupplier.java: -------------------------------------------------------------------------------- 1 | package net.techcable.srglib.utils; 2 | 3 | public interface CheckedSupplier { 4 | T get() throws E; 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/net/techcable/srglib/utils/Exceptions.java: -------------------------------------------------------------------------------- 1 | package net.techcable.srglib.utils; 2 | 3 | import java.util.function.BiConsumer; 4 | import java.util.function.Consumer; 5 | import java.util.function.Supplier; 6 | 7 | public final class Exceptions { 8 | private Exceptions() {} 9 | 10 | public static BiConsumer sneakyThrowing(CheckedBiConsumer consumer) { 11 | return (first, second) -> { 12 | try { 13 | consumer.accept(first, second); 14 | } catch (Throwable e) { 15 | throw sneakyThrow(e); 16 | } 17 | }; 18 | } 19 | 20 | public static Consumer sneakyThrowing(CheckedConsumer consumer) { 21 | return t -> { 22 | try { 23 | consumer.accept(t); 24 | } catch (Throwable e) { 25 | throw sneakyThrow(e); 26 | } 27 | }; 28 | } 29 | 30 | public static Runnable sneakyThrowing(CheckedRunnable r) { 31 | return () -> { 32 | try { 33 | r.run(); 34 | } catch (Throwable t) { 35 | throw sneakyThrow(t); 36 | } 37 | }; 38 | } 39 | 40 | public static Supplier sneakyThrowing(CheckedSupplier r) { 41 | return () -> { 42 | try { 43 | return r.get(); 44 | } catch (Throwable t) { 45 | throw sneakyThrow(t); 46 | } 47 | }; 48 | } 49 | 50 | public static AssertionError sneakyThrow(Throwable t) { 51 | throw sneakyThrow0(t); 52 | } 53 | 54 | @SuppressWarnings("unchecked") // This is intentional :p 55 | private static AssertionError sneakyThrow0(Throwable t) throws T { 56 | throw (T) t; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/net/techcable/srglib/utils/ImmutableLists.java: -------------------------------------------------------------------------------- 1 | package net.techcable.srglib.utils; 2 | 3 | import java.util.Set; 4 | import java.util.function.BiConsumer; 5 | import java.util.function.BinaryOperator; 6 | import java.util.function.Function; 7 | import java.util.function.Supplier; 8 | import java.util.stream.Collector; 9 | 10 | import com.google.common.collect.ImmutableList; 11 | import com.google.common.collect.ImmutableSet; 12 | 13 | import static com.google.common.base.Preconditions.checkNotNull; 14 | import static java.util.Objects.requireNonNull; 15 | 16 | public final class ImmutableLists { 17 | private ImmutableLists() {} 18 | 19 | 20 | private static class ImmutableListCollector implements Collector, ImmutableList> { 21 | private ImmutableListCollector() {} 22 | public static ImmutableListCollector INSTANCE = new ImmutableListCollector(); 23 | @Override 24 | public Supplier> supplier() { 25 | return ImmutableList::builder; 26 | } 27 | 28 | @Override 29 | public BiConsumer, E> accumulator() { 30 | return ImmutableList.Builder::add; 31 | } 32 | 33 | private static ImmutableList.Builder combine(ImmutableList.Builder first, ImmutableList.Builder second) { 34 | return first.addAll(second.build()); 35 | } 36 | 37 | @Override 38 | public BinaryOperator> combiner() { 39 | return ImmutableListCollector::combine; 40 | } 41 | 42 | @Override 43 | public Function, ImmutableList> finisher() { 44 | return ImmutableList.Builder::build; 45 | } 46 | 47 | @Override 48 | public Set characteristics() { 49 | return ImmutableSet.of(); 50 | } 51 | } 52 | 53 | @SuppressWarnings("unchecked") 54 | public static Collector> collector() { 55 | return ImmutableListCollector.INSTANCE; 56 | } 57 | 58 | @SuppressWarnings("unchecked") 59 | public static ImmutableList transform(ImmutableList original, Function transformer) { 60 | int size = requireNonNull(original, "Null original list").size(); 61 | Object[] result = new Object[size]; 62 | for (int i = 0; i < size; i++) { 63 | T originalElement = original.get(i); 64 | U newElement = transformer.apply(originalElement); 65 | checkNotNull(newElement, "Transformer produced null value for input: %s", originalElement); 66 | result[i] = newElement; 67 | } 68 | return (ImmutableList) ImmutableList.copyOf(result); 69 | } 70 | 71 | 72 | public static String joinToString(ImmutableList list, Function asString, String delimiter) { 73 | return joinToString(list, asString, delimiter, "", ""); 74 | } 75 | 76 | public static String joinToString( 77 | ImmutableList list, 78 | Function asString, 79 | String delimiter, 80 | String prefix, 81 | String suffix 82 | ) { 83 | int size = requireNonNull(list, "Null list").size(); 84 | int delimiterLength = requireNonNull(delimiter, "Null delimiter").length(); 85 | int prefixLength = requireNonNull(prefix, "Null prefix").length(); 86 | int suffixLength = requireNonNull(suffix, "Null suffix").length(); 87 | String[] strings = new String[size]; 88 | int neededChars = prefixLength + suffixLength + (Math.max(0, size - 1)) * delimiterLength; 89 | for (int i = 0; i < size; i++) { 90 | T element = list.get(i); 91 | String str = asString.apply(element); 92 | strings[i] = str; 93 | neededChars += str.length(); 94 | } 95 | char[] result = new char[neededChars]; 96 | int resultSize = 0; 97 | prefix.getChars(0, prefixLength, result, resultSize); 98 | resultSize += prefixLength; 99 | for (int i = 0; i < size; i++) { 100 | String str = strings[i]; 101 | if (i > 0) { 102 | // Prefix it with the delimiter 103 | delimiter.getChars(0, delimiterLength, result, resultSize); 104 | resultSize += delimiterLength; 105 | } 106 | int length = str.length(); 107 | str.getChars(0, length, result, resultSize); 108 | resultSize += length; 109 | } 110 | suffix.getChars(0, suffixLength, result, resultSize); 111 | resultSize += suffixLength; 112 | assert result.length == resultSize; 113 | return String.valueOf(result); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/net/techcable/srglib/utils/ImmutableMaps.java: -------------------------------------------------------------------------------- 1 | package net.techcable.srglib.utils; 2 | 3 | import java.util.Map; 4 | import java.util.Set; 5 | import java.util.function.BiFunction; 6 | import java.util.function.Function; 7 | 8 | import com.google.common.collect.ImmutableBiMap; 9 | import com.google.common.collect.ImmutableMap; 10 | 11 | import static java.util.Objects.*; 12 | 13 | public final class ImmutableMaps { 14 | private ImmutableMaps() { 15 | } 16 | 17 | public static ImmutableBiMap createBiMap(Set keys, Function valueFunction) { 18 | ImmutableBiMap.Builder builder = ImmutableBiMap.builder(); 19 | keys.forEach((key) -> builder.put(key, valueFunction.apply(key))); 20 | return builder.build(); 21 | } 22 | 23 | public static ImmutableMap createMap(Set keys, Function valueFunction) { 24 | ImmutableMap.Builder builder = ImmutableMap.builder(); 25 | keys.forEach((key) -> builder.put(key, valueFunction.apply(key))); 26 | return builder.build(); 27 | } 28 | 29 | public static ImmutableBiMap inverse(Map input) { 30 | return ImmutableBiMap.copyOf(input).inverse(); 31 | } 32 | 33 | public static String joinToString( 34 | ImmutableMap map, 35 | BiFunction asString, 36 | String delimiter, 37 | String prefix, 38 | String suffix 39 | ) { 40 | int size = requireNonNull(map, "Null list").size(); 41 | int delimiterLength = requireNonNull(delimiter, "Null delimiter").length(); 42 | int prefixLength = requireNonNull(prefix, "Null prefix").length(); 43 | int suffixLength = requireNonNull(suffix, "Null suffix").length(); 44 | String[] strings = new String[size]; 45 | int neededChars = prefixLength + suffixLength + (size - 1) * delimiterLength; 46 | int index = 0; 47 | for (Map.Entry entry : map.entrySet()) { 48 | K key = entry.getKey(); 49 | V value = entry.getValue(); 50 | String str = asString.apply(key, value); 51 | strings[index++] = str; 52 | neededChars += str.length(); 53 | } 54 | char[] result = new char[neededChars]; 55 | int resultSize = 0; 56 | prefix.getChars(0, prefixLength, result, resultSize); 57 | resultSize += prefixLength; 58 | for (int i = 0; i < size; i++) { 59 | String str = strings[i]; 60 | if (i > 0) { 61 | // Prefix it with the delimiter 62 | delimiter.getChars(0, delimiterLength, result, resultSize); 63 | resultSize += delimiterLength; 64 | } 65 | int length = str.length(); 66 | str.getChars(0, length, result, resultSize); 67 | resultSize += length; 68 | } 69 | suffix.getChars(0, suffixLength, result, resultSize); 70 | resultSize += suffixLength; 71 | assert result.length == resultSize; 72 | return String.valueOf(result); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/test/java/net/techcable/srglib/MappingsChainTest.java: -------------------------------------------------------------------------------- 1 | package net.techcable.srglib; 2 | 3 | import java.util.Arrays; 4 | 5 | import com.google.common.collect.ImmutableList; 6 | import com.google.common.collect.ImmutableMap; 7 | 8 | import net.techcable.srglib.format.MappingsFormat; 9 | import net.techcable.srglib.mappings.ImmutableMappings; 10 | import net.techcable.srglib.mappings.Mappings; 11 | import net.techcable.srglib.utils.ImmutableLists; 12 | 13 | import org.junit.Test; 14 | import org.junit.runner.RunWith; 15 | import org.junit.runners.Parameterized; 16 | 17 | import static org.junit.Assert.*; 18 | 19 | @RunWith(Parameterized.class) 20 | public class MappingsChainTest { 21 | @Parameterized.Parameters 22 | public static Object[][] testData() { 23 | return new Object[][] { 24 | { 25 | new Mappings[]{ 26 | MappingsFormat.SEARGE_FORMAT.parseLines( 27 | "CL: aa Entity", 28 | "CL: ab Cow", 29 | "CL: ac EntityPlayer", 30 | "CL: ad World", 31 | "CL: ae Server" 32 | ), 33 | MappingsFormat.SEARGE_FORMAT.parseLines( 34 | "CL: af ForgetfulClass", 35 | "FD: Entity/a Entity/dead", 36 | "MD: Cow/a (LCow;)V Cow/love (LCow;)V", 37 | "MD: EntityPlayer/a (Ljava/lang/String;)V EntityPlayer/disconnect (Ljava/lang/String;)V", 38 | "FD: World/a World/time", 39 | "MD: World/a ()V World/tick ()V", 40 | "FD: Server/a Server/ticks", 41 | "MD: Server/a ()V Server/tick ()V" 42 | ), 43 | MappingsFormat.SEARGE_FORMAT.parseLines( 44 | "CL: ForgetfulClass me/stupid/ChangedMind", 45 | "FD: World/time World/numTicks", 46 | "MD: World/tick ()V World/pulse ()V" 47 | ), 48 | Mappings.createPackageMappings(ImmutableMap.of("", "net.minecraft.server")) 49 | }, 50 | MappingsFormat.SEARGE_FORMAT.parseLines( 51 | "CL: aa net/minecraft/server/Entity", 52 | "CL: ab net/minecraft/server/Cow", 53 | "CL: ac net/minecraft/server/EntityPlayer", 54 | "CL: ad net/minecraft/server/World", 55 | "CL: ae net/minecraft/server/Server", 56 | "CL: af me/stupid/ChangedMind", 57 | "FD: aa/a net/minecraft/server/Entity/dead", 58 | "MD: ab/a (Lab;)V net/minecraft/server/Cow/love (Lnet/minecraft/server/Cow;)V", 59 | "MD: ac/a (Ljava/lang/String;)V net/minecraft/server/EntityPlayer/disconnect (Ljava/lang/String;)V", 60 | "FD: ad/a net/minecraft/server/World/numTicks", 61 | "MD: ad/a ()V net/minecraft/server/World/pulse ()V", 62 | "FD: ae/a net/minecraft/server/Server/ticks", 63 | "MD: ae/a ()V net/minecraft/server/Server/tick ()V" 64 | ) 65 | } 66 | }; 67 | } 68 | 69 | private final ImmutableList mappings; 70 | private final ImmutableMappings expectedOutput; 71 | 72 | public MappingsChainTest(Mappings[] mappings, Mappings expectedOutput) { 73 | this.mappings = ImmutableList.copyOf(mappings); 74 | this.expectedOutput = expectedOutput.snapshot(); 75 | } 76 | 77 | @Test 78 | public void testChaining() { 79 | ImmutableMappings chained = Mappings.chain(mappings).snapshot(); 80 | assertEquals(expectedOutput, chained); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/test/java/net/techcable/srglib/MappingsFormatTest.java: -------------------------------------------------------------------------------- 1 | package net.techcable.srglib; 2 | 3 | import java.util.List; 4 | 5 | import com.google.common.collect.ImmutableList; 6 | 7 | import net.techcable.srglib.format.MappingsFormat; 8 | import net.techcable.srglib.mappings.Mappings; 9 | 10 | import org.junit.Test; 11 | import org.junit.runner.RunWith; 12 | import org.junit.runners.Parameterized; 13 | 14 | import static org.junit.Assert.*; 15 | 16 | @RunWith(Parameterized.class) 17 | public class MappingsFormatTest { 18 | private static final ImmutableList TEST_LINES = ImmutableList.of( 19 | "CL: org/spigotmc/XRay net/techcable/xray/XRay", 20 | "CL: org/spigotmc/XRay$Manager net/techcable/xray/XRayManager", 21 | "CL: org/spigotmc/XRay$Injector net/techcable/xray/injector/Injector", 22 | "CL: org/spigotmc/XRay$Injector$Manager net/techcable/xray/injector/InjectorManager", 23 | "CL: obfs net/techcable/minecraft/NoHax", 24 | "CL: obf4 net/techcable/minecraft/Player", 25 | "FD: obf4/a net/techcable/minecraft/Player/dead", 26 | "FD: obf4/b net/techcable/minecraft/Player/blood", 27 | "FD: obf4/c net/techcable/minecraft/Player/health", 28 | "FD: obf4/d net/techcable/minecraft/Player/speed", 29 | "FD: org/spigotmc/XRay$Injector$Manager/taco net/techcable/xray/injector/InjectorManager/seriousVariableName", 30 | "MD: obfs/a (Lobf4;ID)Z net/techcable/minecraft/NoHax/isHacking (Lnet/techcable/minecraft/Player;ID)Z", 31 | "MD: org/spigotmc/XRay/deobfuscate ([BLjava/util/Set;)I net/techcable/xray/XRay/doAFunkyDance ([BLjava/util/Set;)I", 32 | "MD: org/spigotmc/XRay$Manager/aquire ()Lorg/spigotmc/XRay; net/techcable/xray/XRayManager/get ()Lnet/techcable/xray/XRay;" 33 | ); 34 | private static final ImmutableList COMPACT_TEST_LINES = ImmutableList.of( 35 | "org/spigotmc/XRay net/techcable/xray/XRay", 36 | "org/spigotmc/XRay$Manager net/techcable/xray/XRayManager", 37 | "org/spigotmc/XRay$Injector net/techcable/xray/injector/Injector", 38 | "org/spigotmc/XRay$Injector$Manager net/techcable/xray/injector/InjectorManager", 39 | "obfs net/techcable/minecraft/NoHax", 40 | "obf4 net/techcable/minecraft/Player", 41 | "obf4 a dead", 42 | "obf4 b blood", 43 | "obf4 c health", 44 | "obf4 d speed", 45 | "org/spigotmc/XRay$Injector$Manager taco seriousVariableName", 46 | "obfs a (Lobf4;ID)Z isHacking", 47 | "org/spigotmc/XRay deobfuscate ([BLjava/util/Set;)I doAFunkyDance", 48 | "org/spigotmc/XRay$Manager aquire ()Lorg/spigotmc/XRay; get" 49 | ); 50 | @Parameterized.Parameters 51 | public static Object[][] mappingFormats() { 52 | return new Object[][] { 53 | new Object[] { MappingsFormat.SEARGE_FORMAT, TEST_LINES }, 54 | new Object[] { MappingsFormat.COMPACT_SEARGE_FORMAT, COMPACT_TEST_LINES } 55 | }; 56 | } 57 | private final MappingsFormat mappingsFormat; 58 | private final ImmutableList testLines; 59 | public MappingsFormatTest(MappingsFormat mappingsFormat, ImmutableList testLines) { 60 | this.mappingsFormat = mappingsFormat; 61 | this.testLines = testLines; 62 | } 63 | 64 | @Test 65 | public void testParse() { 66 | Mappings result = mappingsFormat.parseLines(testLines); 67 | SrgLib.checkConsistency(result.snapshot()); 68 | assertEquals("net.techcable.xray.XRay", result.getNewClass("org.spigotmc.XRay").getName()); 69 | assertEquals("net.techcable.minecraft.Player", result.getNewClass("obf4").getName()); 70 | assertEquals( 71 | MethodData.create( 72 | JavaType.fromName("net.techcable.minecraft.NoHax"), 73 | "isHacking", 74 | ImmutableList.of( 75 | JavaType.fromName("net.techcable.minecraft.Player"), 76 | PrimitiveType.INT, 77 | PrimitiveType.DOUBLE 78 | ), 79 | PrimitiveType.BOOLEAN 80 | ), 81 | result.getNewMethod(MethodData.create( 82 | JavaType.fromName("obfs"), 83 | "a", 84 | ImmutableList.of( 85 | JavaType.fromName("obf4"), 86 | PrimitiveType.INT, 87 | PrimitiveType.DOUBLE 88 | ), 89 | PrimitiveType.BOOLEAN 90 | )) 91 | ); 92 | assertEquals( 93 | FieldData.create(JavaType.fromName("net.techcable.minecraft.Player"), "dead"), 94 | result.getNewField(FieldData.create(JavaType.fromName("obf4"), "a")) 95 | ); 96 | } 97 | 98 | @Test 99 | public void testSerialize() { 100 | Mappings expected = mappingsFormat.parseLines(testLines); 101 | List serialized = mappingsFormat.toLines(expected); 102 | Mappings actual = mappingsFormat.parseLines(serialized); 103 | assertEquals(expected, actual); 104 | } 105 | } 106 | --------------------------------------------------------------------------------