/*******************************************************************************
* 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.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import cuchaz.enigma.analysis.TranslationIndex;
public class Mappings implements Serializable
{
private static final long serialVersionUID = 4649790259460259026L;
protected Map<String, ClassMapping> m_classesByObf;
protected Map<String, ClassMapping> m_classesByDeobf;
public Mappings()
{
m_classesByObf = Maps.newHashMap();
m_classesByDeobf = Maps.newHashMap();
}
public Mappings(Iterable<ClassMapping> classes)
{
this();
for(ClassMapping classMapping : classes)
{
m_classesByObf.put(classMapping.getObfFullName(), classMapping);
if(classMapping.getDeobfName() != null)
m_classesByDeobf.put(classMapping.getDeobfName(), classMapping);
}
}
public Collection<ClassMapping> classes()
{
assert m_classesByObf.size() >= m_classesByDeobf.size();
return m_classesByObf.values();
}
public void addClassMapping(ClassMapping classMapping)
{
if(m_classesByObf.containsKey(classMapping.getObfFullName()))
throw new Error("Already have mapping for "
+ classMapping.getObfFullName());
boolean obfWasAdded =
m_classesByObf.put(classMapping.getObfFullName(), classMapping) == null;
assert obfWasAdded;
if(classMapping.getDeobfName() != null)
{
if(m_classesByDeobf.containsKey(classMapping.getDeobfName()))
throw new Error("Already have mapping for "
+ classMapping.getDeobfName());
boolean deobfWasAdded =
m_classesByDeobf.put(classMapping.getDeobfName(), classMapping) == null;
assert deobfWasAdded;
}
}
public void removeClassMapping(ClassMapping classMapping)
{
boolean obfWasRemoved =
m_classesByObf.remove(classMapping.getObfFullName()) != null;
assert obfWasRemoved;
if(classMapping.getDeobfName() != null)
{
boolean deobfWasRemoved =
m_classesByDeobf.remove(classMapping.getDeobfName()) != null;
assert deobfWasRemoved;
}
}
public ClassMapping getClassByObf(ClassEntry entry)
{
return getClassByObf(entry.getName());
}
public ClassMapping getClassByObf(String obfName)
{
return m_classesByObf.get(obfName);
}
public ClassMapping getClassByDeobf(ClassEntry entry)
{
return getClassByDeobf(entry.getName());
}
public ClassMapping getClassByDeobf(String deobfName)
{
return m_classesByDeobf.get(deobfName);
}
public void setClassDeobfName(ClassMapping classMapping, String deobfName)
{
if(classMapping.getDeobfName() != null)
{
boolean wasRemoved =
m_classesByDeobf.remove(classMapping.getDeobfName()) != null;
assert wasRemoved;
}
classMapping.setDeobfName(deobfName);
if(deobfName != null)
{
boolean wasAdded =
m_classesByDeobf.put(deobfName, classMapping) == null;
assert wasAdded;
}
}
public Translator getTranslator(TranslationDirection direction,
TranslationIndex index)
{
switch(direction)
{
case Deobfuscating:
return new Translator(direction, m_classesByObf, index);
case Obfuscating:
// fill in the missing deobf class entries with obf entries
Map<String, ClassMapping> classes = Maps.newHashMap();
for(ClassMapping classMapping : classes())
if(classMapping.getDeobfName() != null)
classes.put(classMapping.getDeobfName(), classMapping);
else
classes
.put(classMapping.getObfFullName(), classMapping);
// translate the translation index
// NOTE: this isn't actually recursive
TranslationIndex deobfIndex =
new TranslationIndex(index, getTranslator(
TranslationDirection.Deobfuscating, index));
return new Translator(direction, classes, deobfIndex);
default:
throw new Error("Invalid translation direction!");
}
}
@Override
public String toString()
{
StringBuilder buf = new StringBuilder();
for(ClassMapping classMapping : m_classesByObf.values())
{
buf.append(classMapping.toString());
buf.append("\n");
}
return buf.toString();
}
public void renameObfClass(String oldObfName, String newObfName)
{
for(ClassMapping classMapping : new ArrayList<ClassMapping>(classes()))
if(classMapping.renameObfClass(oldObfName, newObfName))
{
boolean wasRemoved = m_classesByObf.remove(oldObfName) != null;
assert wasRemoved;
boolean wasAdded =
m_classesByObf.put(newObfName, classMapping) == null;
assert wasAdded;
}
}
public Set<String> getAllObfClassNames()
{
final Set<String> classNames = Sets.newHashSet();
for(ClassMapping classMapping : classes())
{
// add the class name
classNames.add(classMapping.getObfFullName());
// add classes from method signatures
for(MethodMapping methodMapping : classMapping.methods())
for(Type type : methodMapping.getObfSignature().types())
if(type.hasClass())
classNames.add(type.getClassEntry().getClassName());
}
return classNames;
}
public boolean containsDeobfClass(String deobfName)
{
return m_classesByDeobf.containsKey(deobfName);
}
public boolean containsDeobfField(ClassEntry obfClassEntry,
String deobfName, Type obfType)
{
ClassMapping classMapping = m_classesByObf.get(obfClassEntry.getName());
if(classMapping != null)
return classMapping.containsDeobfField(deobfName, obfType);
return false;
}
public boolean containsDeobfMethod(ClassEntry obfClassEntry,
String deobfName, Signature deobfSignature)
{
ClassMapping classMapping = m_classesByObf.get(obfClassEntry.getName());
if(classMapping != null)
return classMapping.containsDeobfMethod(deobfName, deobfSignature);
return false;
}
public boolean containsArgument(BehaviorEntry obfBehaviorEntry, String name)
{
ClassMapping classMapping =
m_classesByObf.get(obfBehaviorEntry.getClassName());
if(classMapping != null)
return classMapping.containsArgument(obfBehaviorEntry, name);
return false;
}
public List<ClassMapping> getClassMappingChain(ClassEntry obfClass)
{
List<ClassMapping> mappingChain = Lists.newArrayList();
ClassMapping classMapping = null;
for(ClassEntry obfClassEntry : obfClass.getClassChain())
{
if(mappingChain.isEmpty())
classMapping = m_classesByObf.get(obfClassEntry.getName());
else if(classMapping != null)
classMapping =
classMapping.getInnerClassByObfSimple(obfClassEntry
.getInnermostClassName());
mappingChain.add(classMapping);
}
return mappingChain;
}
}