/*******************************************************************************
* Copyright (c) 2015 Jeff Martin.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser General Public
* License v3.0 which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/lgpl.html
*
* Contributors:
* Jeff Martin - initial API and implementation
******************************************************************************/
package cuchaz.enigma.mapping;
import java.util.List;
import java.util.Map;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import cuchaz.enigma.analysis.TranslationIndex;
public class Translator
{
private TranslationDirection m_direction;
private Map<String, ClassMapping> m_classes;
private TranslationIndex m_index;
private ClassNameReplacer m_classNameReplacer = new ClassNameReplacer()
{
@Override
public String replace(String className)
{
return translateEntry(new ClassEntry(className)).getName();
}
};
public Translator()
{
m_direction = null;
m_classes = Maps.newHashMap();
m_index = new TranslationIndex();
}
public Translator(TranslationDirection direction,
Map<String, ClassMapping> classes, TranslationIndex index)
{
m_direction = direction;
m_classes = classes;
m_index = index;
}
public TranslationDirection getDirection()
{
return m_direction;
}
public TranslationIndex getTranslationIndex()
{
return m_index;
}
@SuppressWarnings("unchecked")
public <T extends Entry> T translateEntry(T entry)
{
if(entry instanceof ClassEntry)
return (T)translateEntry((ClassEntry)entry);
else if(entry instanceof FieldEntry)
return (T)translateEntry((FieldEntry)entry);
else if(entry instanceof MethodEntry)
return (T)translateEntry((MethodEntry)entry);
else if(entry instanceof ConstructorEntry)
return (T)translateEntry((ConstructorEntry)entry);
else if(entry instanceof ArgumentEntry)
return (T)translateEntry((ArgumentEntry)entry);
else
throw new Error("Unknown entry type: " + entry.getClass().getName());
}
public <T extends Entry> String translate(T entry)
{
if(entry instanceof ClassEntry)
return translate((ClassEntry)entry);
else if(entry instanceof FieldEntry)
return translate((FieldEntry)entry);
else if(entry instanceof MethodEntry)
return translate((MethodEntry)entry);
else if(entry instanceof ConstructorEntry)
return translate((ConstructorEntry)entry);
else if(entry instanceof ArgumentEntry)
return translate((ArgumentEntry)entry);
else
throw new Error("Unknown entry type: " + entry.getClass().getName());
}
public String translate(ClassEntry in)
{
ClassEntry translated = translateEntry(in);
if(translated.equals(in))
return null;
return translated.getName();
}
public String translateClass(String className)
{
return translate(new ClassEntry(className));
}
public ClassEntry translateEntry(ClassEntry in)
{
if(in.isInnerClass())
{
// translate as much of the class chain as we can
List<ClassMapping> mappingsChain = getClassMappingChain(in);
String[] obfClassNames = in.getName().split("\\$");
StringBuilder buf = new StringBuilder();
for(int i = 0; i < obfClassNames.length; i++)
{
boolean isFirstClass = buf.length() == 0;
String className = null;
ClassMapping classMapping = mappingsChain.get(i);
if(classMapping != null)
className =
m_direction.choose(classMapping.getDeobfName(),
isFirstClass ? classMapping.getObfFullName()
: classMapping.getObfSimpleName());
if(className == null)
className = obfClassNames[i];
if(!isFirstClass)
buf.append("$");
buf.append(className);
}
return new ClassEntry(buf.toString());
}else
{
// normal classes are easy
ClassMapping classMapping = m_classes.get(in.getName());
if(classMapping == null)
return in;
return m_direction.choose(classMapping.getDeobfName() != null
? new ClassEntry(classMapping.getDeobfName()) : in,
new ClassEntry(classMapping.getObfFullName()));
}
}
public String translate(FieldEntry in)
{
// resolve the class entry
ClassEntry resolvedClassEntry = m_index.resolveEntryClass(in);
if(resolvedClassEntry != null)
{
// look for the class
ClassMapping classMapping = findClassMapping(resolvedClassEntry);
if(classMapping != null)
{
// look for the field
String translatedName =
m_direction.choose(classMapping.getDeobfFieldName(
in.getName(), in.getType()), classMapping
.getObfFieldName(in.getName(),
translateType(in.getType())));
if(translatedName != null)
return translatedName;
}
}
return null;
}
public FieldEntry translateEntry(FieldEntry in)
{
String name = translate(in);
if(name == null)
name = in.getName();
return new FieldEntry(translateEntry(in.getClassEntry()), name,
translateType(in.getType()));
}
public String translate(MethodEntry in)
{
// resolve the class entry
ClassEntry resolvedClassEntry = m_index.resolveEntryClass(in);
if(resolvedClassEntry != null)
{
// look for class
ClassMapping classMapping = findClassMapping(resolvedClassEntry);
if(classMapping != null)
{
// look for the method
MethodMapping methodMapping =
m_direction.choose(classMapping.getMethodByObf(
in.getName(), in.getSignature()), classMapping
.getMethodByDeobf(in.getName(),
translateSignature(in.getSignature())));
if(methodMapping != null)
return m_direction.choose(methodMapping.getDeobfName(),
methodMapping.getObfName());
}
}
return null;
}
public MethodEntry translateEntry(MethodEntry in)
{
String name = translate(in);
if(name == null)
name = in.getName();
return new MethodEntry(translateEntry(in.getClassEntry()), name,
translateSignature(in.getSignature()));
}
public ConstructorEntry translateEntry(ConstructorEntry in)
{
if(in.isStatic())
return new ConstructorEntry(translateEntry(in.getClassEntry()));
else
return new ConstructorEntry(translateEntry(in.getClassEntry()),
translateSignature(in.getSignature()));
}
public BehaviorEntry translateEntry(BehaviorEntry in)
{
if(in instanceof MethodEntry)
return translateEntry((MethodEntry)in);
else if(in instanceof ConstructorEntry)
return translateEntry((ConstructorEntry)in);
throw new Error("Wrong entry type!");
}
public String translate(ArgumentEntry in)
{
// look for the class
ClassMapping classMapping = findClassMapping(in.getClassEntry());
if(classMapping != null)
{
// look for the method
MethodMapping methodMapping =
m_direction.choose(classMapping.getMethodByObf(
in.getMethodName(), in.getMethodSignature()), classMapping
.getMethodByDeobf(in.getMethodName(),
translateSignature(in.getMethodSignature())));
if(methodMapping != null)
return m_direction.choose(
methodMapping.getDeobfArgumentName(in.getIndex()),
methodMapping.getObfArgumentName(in.getIndex()));
}
return null;
}
public ArgumentEntry translateEntry(ArgumentEntry in)
{
String name = translate(in);
if(name == null)
name = in.getName();
return new ArgumentEntry(translateEntry(in.getBehaviorEntry()),
in.getIndex(), name);
}
public Type translateType(Type type)
{
return new Type(type, m_classNameReplacer);
}
public Signature translateSignature(Signature signature)
{
return new Signature(signature, m_classNameReplacer);
}
private ClassMapping findClassMapping(ClassEntry in)
{
List<ClassMapping> mappingChain = getClassMappingChain(in);
return mappingChain.get(mappingChain.size() - 1);
}
private List<ClassMapping> getClassMappingChain(ClassEntry in)
{
// get a list of all the classes in the hierarchy
String[] parts = in.getName().split("\\$");
List<ClassMapping> mappingsChain = Lists.newArrayList();
// get mappings for the outer class
ClassMapping outerClassMapping = m_classes.get(parts[0]);
mappingsChain.add(outerClassMapping);
for(int i = 1; i < parts.length; i++)
{
// get mappings for the inner class
ClassMapping innerClassMapping = null;
if(outerClassMapping != null)
innerClassMapping =
m_direction.choose(outerClassMapping
.getInnerClassByObfSimple(parts[i]), outerClassMapping
.getInnerClassByDeobfThenObfSimple(parts[i]));
mappingsChain.add(innerClassMapping);
outerClassMapping = innerClassMapping;
}
assert mappingsChain.size() == parts.length;
return mappingsChain;
}
}