/*
* Nocturne
* Copyright (c) 2015-2016, Lapis <https://github.com/LapisBlue>
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package blue.lapis.nocturne.util.helper;
import static blue.lapis.nocturne.util.Constants.INNER_CLASS_SEPARATOR_CHAR;
import static blue.lapis.nocturne.util.Constants.INNER_CLASS_SEPARATOR_PATTERN;
import blue.lapis.nocturne.Main;
import blue.lapis.nocturne.mapping.MappingContext;
import blue.lapis.nocturne.mapping.model.ClassMapping;
import blue.lapis.nocturne.mapping.model.FieldMapping;
import blue.lapis.nocturne.mapping.model.InnerClassMapping;
import blue.lapis.nocturne.mapping.model.MethodMapping;
import blue.lapis.nocturne.mapping.model.MethodParameterMapping;
import blue.lapis.nocturne.mapping.model.TopLevelClassMapping;
import blue.lapis.nocturne.processor.index.model.IndexedClass;
import blue.lapis.nocturne.processor.index.model.signature.FieldSignature;
import blue.lapis.nocturne.processor.index.model.signature.MethodSignature;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* Static utility class for assisting with mapping retrieval and creation.
*/
public final class MappingsHelper {
public static ClassMapping genClassMapping(MappingContext context, String obf, String deobf,
boolean updateClassViews) {
if (!Main.getLoadedJar().getClass(obf).isPresent()) {
Main.getLogger().warning("Discovered mapping for non-existent class \"" + obf + "\" - ignoring");
return null;
} else if (!StringHelper.isJavaClassIdentifier(obf) || !StringHelper.isJavaClassIdentifier(deobf)) {
Main.getLogger().warning("Discovered class mapping with illegal name - ignoring");
return null;
}
if (obf.contains(INNER_CLASS_SEPARATOR_CHAR + "")) {
String[] obfSplit = INNER_CLASS_SEPARATOR_PATTERN.split(obf);
String[] deobfSplit = INNER_CLASS_SEPARATOR_PATTERN.split(deobf);
if (obfSplit.length != deobfSplit.length) { // non-inner mapped to inner or vice versa
Main.getLogger().warning("Unsupported mapping: " + obf + " -> " + deobf);
return null; // ignore it
}
// get the direct parent class to this inner class
ClassMapping parent = getOrCreateClassMapping(context,
obf.substring(0, obf.lastIndexOf(INNER_CLASS_SEPARATOR_CHAR)));
// atomic validation pass
ClassMapping next = parent;
for (int i = deobfSplit.length - 2; i >= 0; i--) {
if (!next.getObfuscatedName().equals(next.getDeobfuscatedName())
&& !next.getDeobfuscatedName().equals(deobfSplit[i])) {
Main.getLogger().warning("Nonsense mapping " + obf + " -> " + deobf
+ " - conflicts with outer class mapping. Ignoring...");
return null;
}
if (next instanceof InnerClassMapping) {
next = ((InnerClassMapping) next).getParent();
}
}
// application pass
next = parent;
for (int i = deobfSplit.length - 2; i >= 0; i--) {
if (next.getObfuscatedName().equals(next.getDeobfuscatedName())) {
next.setDeobfuscatedName(deobfSplit[i]);
}
if (next instanceof InnerClassMapping) {
next = ((InnerClassMapping) next).getParent();
}
}
String baseObfName = obfSplit[obfSplit.length - 1];
String baseDeobfname = deobfSplit[deobfSplit.length - 1];
if (parent.getInnerClassMappings().containsKey(baseObfName)) {
InnerClassMapping mapping = parent.getInnerClassMappings().get(baseObfName);
mapping.setDeobfuscatedName(baseDeobfname);
return mapping;
} else {
return new InnerClassMapping(parent, baseObfName, baseDeobfname);
}
} else {
if (context.getMappings().containsKey(obf)) {
TopLevelClassMapping mapping = context.getMappings().get(obf);
mapping.setDeobfuscatedName(deobf);
return mapping;
} else {
TopLevelClassMapping mapping = new TopLevelClassMapping(context, obf, deobf);
context.addMapping(mapping, updateClassViews);
return mapping;
}
}
}
public static FieldMapping genFieldMapping(MappingContext context, String owningClass, final FieldSignature sig,
String deobf) {
if (!Main.getLoadedJar().getClass(owningClass).isPresent()) {
Main.getLogger().warning("Discovered mapping for field in non-existent class \"" + owningClass
+ "\" - ignoring");
return null;
} else if (!StringHelper.isJavaIdentifier(sig.getName()) || !StringHelper.isJavaIdentifier(deobf)) {
Main.getLogger().warning("Discovered field mapping with illegal name - ignoring");
return null;
}
ClassMapping parent = getOrCreateClassMapping(context, owningClass);
if (parent.getFieldMappings().containsKey(sig)) {
final FieldMapping fieldMapping = parent.getFieldMappings().get(sig);
fieldMapping.setDeobfuscatedName(deobf);
return fieldMapping;
} else {
FieldSignature finalSig = sig;
if (sig.getType() == null) {
List<FieldSignature> sigList = IndexedClass.INDEXED_CLASSES.get(owningClass).getFields().keySet()
.stream().filter(s -> s.getName().equals(sig.getName())).collect(Collectors.toList());
if (sigList.size() > 1) {
Main.getLogger().warning("Discovered ambiguous field mapping! Ignoring...");
return null;
} else if (sigList.size() == 0) {
Main.getLogger().warning("Discovered field mapping for non-existent field - ignoring...");
return null;
}
finalSig = sigList.get(0);
}
return new FieldMapping(parent, finalSig, deobf);
}
}
public static MethodMapping genMethodMapping(MappingContext context, String owningClass, MethodSignature sig,
String deobf, boolean acceptInitializer) {
if (!Main.getLoadedJar().getClass(owningClass).isPresent()) {
Main.getLogger().warning("Discovered mapping for method in non-existent class \"" + owningClass
+ "\" - ignoring");
return null;
} else if (!(sig.getName().equals("<init>") && acceptInitializer && sig.getName().equals(deobf))
&& (!StringHelper.isJavaIdentifier(sig.getName()) || !StringHelper.isJavaIdentifier(deobf))) {
Main.getLogger().warning("Discovered method mapping with illegal name - ignoring");
return null;
}
ClassMapping parent = getOrCreateClassMapping(context, owningClass);
if (parent.getMethodMappings().containsKey(sig)) {
final MethodMapping methodMapping = parent.getMethodMappings().get(sig);
methodMapping.setDeobfuscatedName(deobf);
return methodMapping;
} else {
return new MethodMapping(parent, sig, deobf);
}
}
public static void genArgumentMapping(MappingContext context, MethodMapping methodMapping, int index,
String deobf) {
if (!StringHelper.isJavaIdentifier(deobf)) {
Main.getLogger().warning("Discovered argument mapping with illegal name - ignoring");
return;
}
Optional<MethodParameterMapping> mapping = methodMapping.getParamMappings().values().stream()
.filter(argumentMapping -> argumentMapping.getIndex() == index).findFirst();
if (mapping.isPresent()) {
mapping.get().setDeobfuscatedName(deobf);
} else {
new MethodParameterMapping(methodMapping, index, deobf, true);
}
}
private static Optional<ClassMapping> getClassMapping(MappingContext context, String qualifiedName,
boolean create) {
String[] arr = INNER_CLASS_SEPARATOR_PATTERN.split(qualifiedName);
ClassMapping mapping = context.getMappings().get(arr[0]);
if (mapping == null) {
if (create) {
mapping = new TopLevelClassMapping(context, arr[0], arr[0]);
context.addMapping((TopLevelClassMapping) mapping, false);
} else {
return Optional.empty();
}
}
for (int i = 1; i < arr.length; i++) {
ClassMapping child = mapping.getInnerClassMappings().get(arr[i]);
if (child == null) {
if (create) {
child = new InnerClassMapping(mapping, arr[i], arr[i]);
} else {
return Optional.empty();
}
}
mapping = child;
}
return Optional.of(mapping);
}
public static Optional<ClassMapping> getClassMapping(MappingContext context, String qualifiedName) {
return getClassMapping(context, qualifiedName, false);
}
/**
* Gets the {@link ClassMapping} for the given qualified name, iteratively
* creating mappings for both outer and inner classes as needed if they do
* not exist.
*
* @param context The {@link MappingContext} to use
* @param qualifiedName The fully-qualified name of the class to get a
* mapping for
* @return The retrieved or created {@link ClassMapping}
*/
public static ClassMapping getOrCreateClassMapping(MappingContext context, String qualifiedName) {
return getClassMapping(context, qualifiedName, true).get();
}
}