public class Constants {
	public static final String Name = "Enigma VK";
	public static final String Version = "0.12 beta(VK Fork)";
	public static final String Url = "https://github.com/cccssw/enigma-vk";
	public static final int MiB = 1024 * 1024; // 1 mebibyte
	public static final int KiB = 1024; // 1 kebibyte
	public static final String NonePackage = "none";
} public class BehaviorReferenceTreeNode extends DefaultMutableTreeNode implements ReferenceTreeNode<BehaviorEntry,BehaviorEntry> {
	
	private static final long serialVersionUID = -3658163700783307520L;
	
	private Translator m_deobfuscatingTranslator;
	private BehaviorEntry m_entry;
	private EntryReference<BehaviorEntry,BehaviorEntry> m_reference;
	private Access m_access;
	
	public BehaviorReferenceTreeNode(Translator deobfuscatingTranslator, BehaviorEntry entry) {
		m_deobfuscatingTranslator = deobfuscatingTranslator;
		m_entry = entry;
		m_reference = null;
	}
	
	public BehaviorReferenceTreeNode(Translator deobfuscatingTranslator, EntryReference<BehaviorEntry,BehaviorEntry> reference, Access access) {
		m_deobfuscatingTranslator = deobfuscatingTranslator;
		m_entry = reference.entry;
		m_reference = reference;
		m_access = access;
	}
	
	@Override
	public BehaviorEntry getEntry() {
		return m_entry;
	}
	
	@Override
	public EntryReference<BehaviorEntry,BehaviorEntry> getReference() {
		return m_reference;
	}
	
	@Override
	public String toString() {
		if (m_reference != null) {
			return String.format("%s (%s)", m_deobfuscatingTranslator.translateEntry(m_reference.context), m_access);
		}
		return m_deobfuscatingTranslator.translateEntry(m_entry).toString();
	}
	
	public void load(JarIndex index, boolean recurse) {
		// get all the child nodes
		for (EntryReference<BehaviorEntry,BehaviorEntry> reference : index.getBehaviorReferences(m_entry)) {
			add(new BehaviorReferenceTreeNode(m_deobfuscatingTranslator, reference, index.getAccess(m_entry)));
		}
		
		if (recurse && children != null) {
			for (Object child : children) {
				if (child instanceof BehaviorReferenceTreeNode) {
					BehaviorReferenceTreeNode node = (BehaviorReferenceTreeNode)child;
					
					// don't recurse into ancestor
					Set<Entry> ancestors = Sets.newHashSet();
					TreeNode n = (TreeNode)node;
					while (n.getParent() != null) {
						n = n.getParent();
						if (n instanceof BehaviorReferenceTreeNode) {
							ancestors.add( ((BehaviorReferenceTreeNode)n).getEntry());
						}
					}
					if (ancestors.contains(node.getEntry())) {
						continue;
					}
					
					node.load(index, true);
				}
			}
		}
	}
} public class ClassInheritanceTreeNode extends DefaultMutableTreeNode {
	
	private static final long serialVersionUID = 4432367405826178490L;
	
	private Translator m_deobfuscatingTranslator;
	private String m_obfClassName;
	
	public ClassInheritanceTreeNode(Translator deobfuscatingTranslator, String obfClassName) {
		m_deobfuscatingTranslator = deobfuscatingTranslator;
		m_obfClassName = obfClassName;
	}
	
	public String getObfClassName() {
		return m_obfClassName;
	}
	
	public String getDeobfClassName() {
		return m_deobfuscatingTranslator.translateClass(m_obfClassName);
	}
	
	@Override
	public String toString() {
		String deobfClassName = getDeobfClassName();
		if (deobfClassName != null) {
			return deobfClassName;
		}
		return m_obfClassName;
	}
	
	public void load(TranslationIndex ancestries, boolean recurse) {
		// get all the child nodes
		List<ClassInheritanceTreeNode> nodes = Lists.newArrayList();
		for (ClassEntry subclassEntry : ancestries.getSubclass(new ClassEntry(m_obfClassName))) {
			nodes.add(new ClassInheritanceTreeNode(m_deobfuscatingTranslator, subclassEntry.getName()));
		}
		
		// add them to this node
		for (ClassInheritanceTreeNode node : nodes) {
			this.add(node);
		}
		
		if (recurse) {
			for (ClassInheritanceTreeNode node : nodes) {
				node.load(ancestries, true);
			}
		}
	}
	
	public static ClassInheritanceTreeNode findNode(ClassInheritanceTreeNode node, ClassEntry entry) {
		// is this the node?
		if (node.getObfClassName().equals(entry.getName())) {
			return node;
		}
		
		// recurse
		for (int i = 0; i < node.getChildCount(); i++) {
			ClassInheritanceTreeNode foundNode = findNode((ClassInheritanceTreeNode)node.getChildAt(i), entry);
			if (foundNode != null) {
				return foundNode;
			}
		}
		return null;
	}
} public class EntryReference<E extends Entry,C extends Entry> {
	
	private static final List<String> ConstructorNonNames = Arrays.asList("this", "super", "static");
	public E entry;
	public C context;
	
	private boolean m_isNamed;
	
	public EntryReference(E entry, String sourceName) {
		this(entry, sourceName, null);
	}
	
	public EntryReference(E entry, String sourceName, C context) {
		if (entry == null) {
			throw new IllegalArgumentException("Entry cannot be null!");
		}
		
		this.entry = entry;
		this.context = context;
		
		m_isNamed = sourceName != null && sourceName.length() > 0;
		if (entry instanceof ConstructorEntry && ConstructorNonNames.contains(sourceName)) {
			m_isNamed = false;
		}
	}
	
	public EntryReference(E entry, C context, EntryReference<E,C> other) {
		this.entry = entry;
		this.context = context;
		m_isNamed = other.m_isNamed;
	}
	
	public ClassEntry getLocationClassEntry() {
		if (context != null) {
			return context.getClassEntry();
		}
		return entry.getClassEntry();
	}
	
	public boolean isNamed() {
		return m_isNamed;
	}
	
	public Entry getNameableEntry() {
		if (entry instanceof ConstructorEntry) {
			// renaming a constructor really means renaming the class
			return entry.getClassEntry();
		}
		return entry;
	}
	
	public String getNamableName() {
		if (getNameableEntry() instanceof ClassEntry) {
			ClassEntry classEntry = (ClassEntry)getNameableEntry();
			if (classEntry.isInnerClass()) {
				// make sure we only rename the inner class name
				return classEntry.getInnermostClassName();
			}
		}
		
		return getNameableEntry().getName();
	}
	
	@Override
	public int hashCode() {
		if (context != null) {
			return Util.combineHashesOrdered(entry.hashCode(), context.hashCode());
		}
		return entry.hashCode();
	}
	
	@Override
	public boolean equals(Object other) {
		if (other instanceof EntryReference) {
			return equals((EntryReference<?,?>)other);
		}
		return false;
	}
	
	public boolean equals(EntryReference<?,?> other) {
		// check entry first
		boolean isEntrySame = entry.equals(other.entry);
		if (!isEntrySame) {
			return false;
		}
		
		// check caller
		if (context == null && other.context == null) {
			return true;
		} else if (context != null && other.context != null) {
			return context.equals(other.context);
		}
		return false;
	}
	
	@Override
	public String toString() {
		StringBuilder buf = new StringBuilder();
		buf.append(entry);
		if (context != null) {
			buf.append(" called from ");
			buf.append(context);
		}
		return buf.toString();
	}
} public class FieldReferenceTreeNode extends DefaultMutableTreeNode implements ReferenceTreeNode<FieldEntry,BehaviorEntry> {
	
	private static final long serialVersionUID = -7934108091928699835L;
	
	private Translator m_deobfuscatingTranslator;
	private FieldEntry m_entry;
	private EntryReference<FieldEntry,BehaviorEntry> m_reference;
	private Access m_access;
	
	public FieldReferenceTreeNode(Translator deobfuscatingTranslator, FieldEntry entry) {
		m_deobfuscatingTranslator = deobfuscatingTranslator;
		m_entry = entry;
		m_reference = null;
	}
	
	private FieldReferenceTreeNode(Translator deobfuscatingTranslator, EntryReference<FieldEntry,BehaviorEntry> reference, Access access) {
		m_deobfuscatingTranslator = deobfuscatingTranslator;
		m_entry = reference.entry;
		m_reference = reference;
		m_access = access;
	}
	
	@Override
	public FieldEntry getEntry() {
		return m_entry;
	}
	
	@Override
	public EntryReference<FieldEntry,BehaviorEntry> getReference() {
		return m_reference;
	}
	
	@Override
	public String toString() {
		if (m_reference != null) {
			return String.format("%s (%s)", m_deobfuscatingTranslator.translateEntry(m_reference.context), m_access);
		}
		return m_deobfuscatingTranslator.translateEntry(m_entry).toString();
	}
	
	public void load(JarIndex index, boolean recurse) {
		// get all the child nodes
		if (m_reference == null) {
			for (EntryReference<FieldEntry,BehaviorEntry> reference : index.getFieldReferences(m_entry)) {
				add(new FieldReferenceTreeNode(m_deobfuscatingTranslator, reference, index.getAccess(m_entry)));
			}
		} else {
			for (EntryReference<BehaviorEntry,BehaviorEntry> reference : index.getBehaviorReferences(m_reference.context)) {
				add(new BehaviorReferenceTreeNode(m_deobfuscatingTranslator, reference, index.getAccess(m_reference.context)));
			}
		}
		
		if (recurse && children != null) {
			for (Object node : children) {
				if (node instanceof BehaviorReferenceTreeNode) {
					((BehaviorReferenceTreeNode)node).load(index, true);
				} else if (node instanceof FieldReferenceTreeNode) {
					((FieldReferenceTreeNode)node).load(index, true);
				}
			}
		}
	}
} public class MethodImplementationsTreeNode extends DefaultMutableTreeNode {
	
	private static final long serialVersionUID = 3781080657461899915L;
	
	private Translator m_deobfuscatingTranslator;
	private MethodEntry m_entry;
	
	public MethodImplementationsTreeNode(Translator deobfuscatingTranslator, MethodEntry entry) {
		if (entry == null) {
			throw new IllegalArgumentException("entry cannot be null!");
		}
		
		m_deobfuscatingTranslator = deobfuscatingTranslator;
		m_entry = entry;
	}
	
	public MethodEntry getMethodEntry() {
		return m_entry;
	}
	
	public String getDeobfClassName() {
		return m_deobfuscatingTranslator.translateClass(m_entry.getClassName());
	}
	
	public String getDeobfMethodName() {
		return m_deobfuscatingTranslator.translate(m_entry);
	}
	
	@Override
	public String toString() {
		String className = getDeobfClassName();
		if (className == null) {
			className = m_entry.getClassName();
		}
		
		String methodName = getDeobfMethodName();
		if (methodName == null) {
			methodName = m_entry.getName();
		}
		return className + "." + methodName + "()";
	}
	
	public void load(JarIndex index) {
		
		// get all method implementations
		List<MethodImplementationsTreeNode> nodes = Lists.newArrayList();
		for (String implementingClassName : index.getImplementingClasses(m_entry.getClassName())) {
			MethodEntry methodEntry = new MethodEntry(
				new ClassEntry(implementingClassName),
				m_entry.getName(),
				m_entry.getSignature()
			);
			if (index.containsObfBehavior(methodEntry)) {
				nodes.add(new MethodImplementationsTreeNode(m_deobfuscatingTranslator, methodEntry));
			}
		}
		
		// add them to this node
		for (MethodImplementationsTreeNode node : nodes) {
			this.add(node);
		}
	}
	
	public static MethodImplementationsTreeNode findNode(MethodImplementationsTreeNode node, MethodEntry entry) {
		// is this the node?
		if (node.getMethodEntry().equals(entry)) {
			return node;
		}
		
		// recurse
		for (int i = 0; i < node.getChildCount(); i++) {
			MethodImplementationsTreeNode foundNode = findNode((MethodImplementationsTreeNode)node.getChildAt(i), entry);
			if (foundNode != null) {
				return foundNode;
			}
		}
		return null;
	}
} public class MethodInheritanceTreeNode extends DefaultMutableTreeNode {
	
	private static final long serialVersionUID = 1096677030991810007L;
	
	private Translator m_deobfuscatingTranslator;
	private MethodEntry m_entry;
	private boolean m_isImplemented;
	
	public MethodInheritanceTreeNode(Translator deobfuscatingTranslator, MethodEntry entry, boolean isImplemented) {
		m_deobfuscatingTranslator = deobfuscatingTranslator;
		m_entry = entry;
		m_isImplemented = isImplemented;
	}
	
	public MethodEntry getMethodEntry() {
		return m_entry;
	}
	
	public String getDeobfClassName() {
		return m_deobfuscatingTranslator.translateClass(m_entry.getClassName());
	}
	
	public String getDeobfMethodName() {
		return m_deobfuscatingTranslator.translate(m_entry);
	}
	
	public boolean isImplemented() {
		return m_isImplemented;
	}
	
	@Override
	public String toString() {
		String className = getDeobfClassName();
		if (className == null) {
			className = m_entry.getClassName();
		}
		
		if (!m_isImplemented) {
			return className;
		} else {
			String methodName = getDeobfMethodName();
			if (methodName == null) {
				methodName = m_entry.getName();
			}
			return className + "." + methodName + "()";
		}
	}
	
	public void load(JarIndex index, boolean recurse) {
		// get all the child nodes
		List<MethodInheritanceTreeNode> nodes = Lists.newArrayList();
		for (ClassEntry subclassEntry : index.getTranslationIndex().getSubclass(m_entry.getClassEntry())) {
			MethodEntry methodEntry = new MethodEntry(
				subclassEntry,
				m_entry.getName(),
				m_entry.getSignature()
			);
			nodes.add(new MethodInheritanceTreeNode(
				m_deobfuscatingTranslator,
				methodEntry,
				index.containsObfBehavior(methodEntry)
			));
		}
		
		// add them to this node
		for (MethodInheritanceTreeNode node : nodes) {
			this.add(node);
		}
		
		if (recurse) {
			for (MethodInheritanceTreeNode node : nodes) {
				node.load(index, true);
			}
		}
	}
	
	public static MethodInheritanceTreeNode findNode(MethodInheritanceTreeNode node, MethodEntry entry) {
		// is this the node?
		if (node.getMethodEntry().equals(entry)) {
			return node;
		}
		
		// recurse
		for (int i = 0; i < node.getChildCount(); i++) {
			MethodInheritanceTreeNode foundNode = findNode((MethodInheritanceTreeNode)node.getChildAt(i), entry);
			if (foundNode != null) {
				return foundNode;
			}
		}
		return null;
	}
} public class ArgumentEntry implements Entry, Serializable {
	
	private static final long serialVersionUID = 4472172468162696006L;
	
	private BehaviorEntry m_behaviorEntry;
	private int m_index;
	private String m_name;
	
	public ArgumentEntry(BehaviorEntry behaviorEntry, int index, String name) {
		if (behaviorEntry == null) {
			throw new IllegalArgumentException("Behavior cannot be null!");
		}
		if (index < 0) {
			throw new IllegalArgumentException("Index must be non-negative!");
		}
		if (name == null) {
			throw new IllegalArgumentException("Argument name cannot be null!");
		}
		
		m_behaviorEntry = behaviorEntry;
		m_index = index;
		m_name = name;
	}
	
	public ArgumentEntry(ArgumentEntry other) {
		m_behaviorEntry = (BehaviorEntry)m_behaviorEntry.cloneToNewClass(getClassEntry());
		m_index = other.m_index;
		m_name = other.m_name;
	}
	
	public ArgumentEntry(ArgumentEntry other, String newClassName) {
		m_behaviorEntry = (BehaviorEntry)other.m_behaviorEntry.cloneToNewClass(new ClassEntry(newClassName));
		m_index = other.m_index;
		m_name = other.m_name;
	}
	
	public BehaviorEntry getBehaviorEntry() {
		return m_behaviorEntry;
	}
	
	public int getIndex() {
		return m_index;
	}
	
	@Override
	public String getName() {
		return m_name;
	}
	
	@Override
	public ClassEntry getClassEntry() {
		return m_behaviorEntry.getClassEntry();
	}
	
	@Override
	public String getClassName() {
		return m_behaviorEntry.getClassName();
	}
	
	@Override
	public ArgumentEntry cloneToNewClass(ClassEntry classEntry) {
		return new ArgumentEntry(this, classEntry.getName());
	}
	
	public String getMethodName() {
		return m_behaviorEntry.getName();
	}
	
	public Signature getMethodSignature() {
		return m_behaviorEntry.getSignature();
	}
	
	@Override
	public int hashCode() {
		return Util.combineHashesOrdered(
			m_behaviorEntry,
			Integer.valueOf(m_index).hashCode(),
			m_name.hashCode()
		);
	}
	
	@Override
	public boolean equals(Object other) {
		if (other instanceof ArgumentEntry) {
			return equals((ArgumentEntry)other);
		}
		return false;
	}
	
	public boolean equals(ArgumentEntry other) {
		return m_behaviorEntry.equals(other.m_behaviorEntry)
			&& m_index == other.m_index
			&& m_name.equals(other.m_name);
	}
	
	@Override
	public String toString() {
		return m_behaviorEntry.toString() + "(" + m_index + ":" + m_name + ")";
	}
} public class ClassEntry implements Entry, Serializable {
	
	private static final long serialVersionUID = 4235460580973955811L;
	
	private String m_name;
	
	public ClassEntry(String className) {
		if (className == null) {
			throw new IllegalArgumentException("Class name cannot be null!");
		}
		if (className.indexOf('.') >= 0) {
			throw new IllegalArgumentException("Class name must be in JVM format. ie, path/to/package/class$inner : " + className);
		}
		
		m_name = className;
		
		if (isInnerClass() && getInnermostClassName().indexOf('/') >= 0) {
			throw new IllegalArgumentException("Inner class must not have a package: " + className);
		}
	}
	
	public ClassEntry(ClassEntry other) {
		m_name = other.m_name;
	}
	
	@Override
	public String getName() {
		return m_name;
	}
	
	@Override
	public String getClassName() {
		return m_name;
	}
	
	@Override
	public ClassEntry getClassEntry() {
		return this;
	}
	
	@Override
	public ClassEntry cloneToNewClass(ClassEntry classEntry) {
		return classEntry;
	}
	
	@Override
	public int hashCode() {
		return m_name.hashCode();
	}
	
	@Override
	public boolean equals(Object other) {
		if (other instanceof ClassEntry) {
			return equals((ClassEntry)other);
		}
		return false;
	}
	
	public boolean equals(ClassEntry other) {
		return m_name.equals(other.m_name);
	}
	
	@Override
	public String toString() {
		return m_name;
	}
	
	public boolean isInnerClass() {
		return m_name.lastIndexOf('$') >= 0;
	}
	
	public List<String> getClassChainNames() {
		return Lists.newArrayList(m_name.split("\\$"));
	}
	
	public List<ClassEntry> getClassChain() {
		List<ClassEntry> entries = Lists.newArrayList();
		StringBuilder buf = new StringBuilder();
		for (String name : getClassChainNames()) {
			if (buf.length() > 0) {
				buf.append("$");
			}
			buf.append(name);
			entries.add(new ClassEntry(buf.toString()));
		}
		return entries;
	}
	
	public String getOutermostClassName() {
		if (isInnerClass()) {
			return m_name.substring(0, m_name.indexOf('$'));
		}
		return m_name;
	}

	public String getOutermostClassInnerName() {
		if (isInnerClass()) {
			String className = m_name.substring(m_name.indexOf('$')+1, m_name.length());
			String pkgname = getPackageName();
			return pkgname+"/"+className;
		}
		return m_name;
	}
	
	public ClassEntry getOutermostClassEntry() {
		return new ClassEntry(getOutermostClassName());
	}

	public ClassEntry getOutermostInnerClassEntry(){
		return new ClassEntry(getOutermostClassInnerName());
	}
	
	public String getOuterClassName() {
		if (!isInnerClass()) {
			throw new Error("This is not an inner class!");
		}
		return m_name.substring(0, m_name.lastIndexOf('$'));
	}
	
	public ClassEntry getOuterClassEntry() {
		return new ClassEntry(getOuterClassName());
	}
	
	public String getInnermostClassName() {
		if (!isInnerClass()) {
			throw new Error("This is not an inner class!");
		}
		return m_name.substring(m_name.lastIndexOf('$') + 1);
	}
	
	public boolean isInDefaultPackage() {
		return m_name.indexOf('/') < 0;
	}
	
	public String getPackageName() {
		int pos = m_name.lastIndexOf('/');
		if (pos > 0) {
			return m_name.substring(0, pos);
		}
		return null;
	}
	
	public String getSimpleName() {
		int pos = m_name.lastIndexOf('/');
		if (pos > 0) {
			return m_name.substring(pos + 1);
		}
		return m_name;
	}
	
	public ClassEntry buildClassEntry(List<ClassEntry> classChain) {
		assert(classChain.contains(this));
		StringBuilder buf = new StringBuilder();
		for (ClassEntry chainEntry : classChain) {
			if (buf.length() == 0) {
				buf.append(chainEntry.getName());
			} else {
				buf.append("$");
				buf.append(chainEntry.isInnerClass() ? chainEntry.getInnermostClassName() : chainEntry.getSimpleName());
			}
			
			if (chainEntry == this) {
				break;
			}
		}
		return new ClassEntry(buf.toString());
	}
} public class ConstructorEntry implements BehaviorEntry, Serializable {
	
	private static final long serialVersionUID = -868346075317366758L;
	
	private ClassEntry m_classEntry;
	private Signature m_signature;
	
	public ConstructorEntry(ClassEntry classEntry) {
		this(classEntry, null);
	}
	
	public ConstructorEntry(ClassEntry classEntry, Signature signature) {
		if (classEntry == null) {
			throw new IllegalArgumentException("Class cannot be null!");
		}
		
		m_classEntry = classEntry;
		m_signature = signature;
	}
	
	public ConstructorEntry(ConstructorEntry other) {
		m_classEntry = new ClassEntry(other.m_classEntry);
		m_signature = other.m_signature;
	}
	
	public ConstructorEntry(ConstructorEntry other, String newClassName) {
		m_classEntry = new ClassEntry(newClassName);
		m_signature = other.m_signature;
	}
	
	@Override
	public ClassEntry getClassEntry() {
		return m_classEntry;
	}
	
	@Override
	public String getName() {
		if (isStatic()) {
			return "<clinit>";
		}
		return "<init>";
	}
	
	public boolean isStatic() {
		return m_signature == null;
	}
	
	@Override
	public Signature getSignature() {
		return m_signature;
	}
	
	@Override
	public String getClassName() {
		return m_classEntry.getName();
	}
	
	@Override
	public ConstructorEntry cloneToNewClass(ClassEntry classEntry) {
		return new ConstructorEntry(this, classEntry.getName());
	}
	
	@Override
	public int hashCode() {
		if (isStatic()) {
			return Util.combineHashesOrdered(m_classEntry);
		} else {
			return Util.combineHashesOrdered(m_classEntry, m_signature);
		}
	}
	
	@Override
	public boolean equals(Object other) {
		if (other instanceof ConstructorEntry) {
			return equals((ConstructorEntry)other);
		}
		return false;
	}
	
	public boolean equals(ConstructorEntry other) {
		if (isStatic() != other.isStatic()) {
			return false;
		}
		
		if (isStatic()) {
			return m_classEntry.equals(other.m_classEntry);
		} else {
			return m_classEntry.equals(other.m_classEntry) && m_signature.equals(other.m_signature);
		}
	}
	
	@Override
	public String toString() {
		if (isStatic()) {
			return m_classEntry.getName() + "." + getName();
		} else {
			return m_classEntry.getName() + "." + getName() + m_signature;
		}
	}
} public class FieldEntry implements Entry, Serializable {
	
	private static final long serialVersionUID = 3004663582802885451L;
	
	private ClassEntry m_classEntry;
	private String m_name;
	private Type m_type;
	
	// NOTE: this argument order is important for the MethodReader/MethodWriter
	public FieldEntry(ClassEntry classEntry, String name, Type type) {
		if (classEntry == null) {
			throw new IllegalArgumentException("Class cannot be null!");
		}
		if (name == null) {
			throw new IllegalArgumentException("Field name cannot be null!");
		}
		if (type == null) {
			throw new IllegalArgumentException("Field type cannot be null!");
		}
		
		m_classEntry = classEntry;
		m_name = name;
		m_type = type;
	}
	
	public FieldEntry(FieldEntry other) {
		this(other, new ClassEntry(other.m_classEntry));
	}
	
	public FieldEntry(FieldEntry other, ClassEntry newClassEntry) {
		m_classEntry = newClassEntry;
		m_name = other.m_name;
		m_type = other.m_type;
	}
	
	@Override
	public ClassEntry getClassEntry() {
		return m_classEntry;
	}
	
	@Override
	public String getName() {
		return m_name;
	}
	
	@Override
	public String getClassName() {
		return m_classEntry.getName();
	}
	
	public Type getType() {
		return m_type;
	}
	
	@Override
	public FieldEntry cloneToNewClass(ClassEntry classEntry) {
		return new FieldEntry(this, classEntry);
	}
	
	@Override
	public int hashCode() {
		return Util.combineHashesOrdered(m_classEntry, m_name, m_type);
	}
	
	@Override
	public boolean equals(Object other) {
		if (other instanceof FieldEntry) {
			return equals((FieldEntry)other);
		}
		return false;
	}
	
	public boolean equals(FieldEntry other) {
		return m_classEntry.equals(other.m_classEntry)
			&& m_name.equals(other.m_name)
			&& m_type.equals(other.m_type);
	}
	
	@Override
	public String toString() {
		return m_classEntry.getName() + "." + m_name + ":" + m_type;
	}
} public class MethodEntry implements BehaviorEntry, Serializable {
	
	private static final long serialVersionUID = 4770915224467247458L;
	
	private ClassEntry m_classEntry;
	private String m_name;
	private Signature m_signature;
	
	public MethodEntry(ClassEntry classEntry, String name, Signature signature) {
		if (classEntry == null) {
			throw new IllegalArgumentException("Class cannot be null!");
		}
		if (name == null) {
			throw new IllegalArgumentException("Method name cannot be null!");
		}
		if (signature == null) {
			throw new IllegalArgumentException("Method signature cannot be null!");
		}
		if (name.startsWith("<")) {
			throw new IllegalArgumentException("Don't use MethodEntry for a constructor!");
		}
		
		m_classEntry = classEntry;
		m_name = name;
		m_signature = signature;
	}
	
	public MethodEntry(MethodEntry other) {
		m_classEntry = new ClassEntry(other.m_classEntry);
		m_name = other.m_name;
		m_signature = other.m_signature;
	}
	
	public MethodEntry(MethodEntry other, String newClassName) {
		m_classEntry = new ClassEntry(newClassName);
		m_name = other.m_name;
		m_signature = other.m_signature;
	}
	
	@Override
	public ClassEntry getClassEntry() {
		return m_classEntry;
	}
	
	@Override
	public String getName() {
		return m_name;
	}
	
	@Override
	public Signature getSignature() {
		return m_signature;
	}
	
	@Override
	public String getClassName() {
		return m_classEntry.getName();
	}
	
	@Override
	public MethodEntry cloneToNewClass(ClassEntry classEntry) {
		return new MethodEntry(this, classEntry.getName());
	}
	
	@Override
	public int hashCode() {
		return Util.combineHashesOrdered(m_classEntry, m_name, m_signature);
	}
	
	@Override
	public boolean equals(Object other) {
		if (other instanceof MethodEntry) {
			return equals((MethodEntry)other);
		}
		return false;
	}
	
	public boolean equals(MethodEntry other) {
		return m_classEntry.equals(other.m_classEntry)
			&& m_name.equals(other.m_name)
			&& m_signature.equals(other.m_signature);
	}
	
	@Override
	public String toString() {
		return m_classEntry.getName() + "." + m_name + m_signature;
	}
}
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.IOException;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Vector;
import java.util.jar.JarFile;
import javax.swing.BorderFactory;
import javax.swing.JEditorPane;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextField;
import javax.swing.JTree;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.WindowConstants;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Highlighter;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import com.google.common.collect.Lists;
import cuchaz.enigma.Constants;
import cuchaz.enigma.ExceptionIgnorer;
import cuchaz.enigma.analysis.BehaviorReferenceTreeNode;
import cuchaz.enigma.analysis.ClassImplementationsTreeNode;
import cuchaz.enigma.analysis.ClassInheritanceTreeNode;
import cuchaz.enigma.analysis.EntryReference;
import cuchaz.enigma.analysis.FieldReferenceTreeNode;
import cuchaz.enigma.analysis.MethodImplementationsTreeNode;
import cuchaz.enigma.analysis.MethodInheritanceTreeNode;
import cuchaz.enigma.analysis.ReferenceTreeNode;
import cuchaz.enigma.analysis.Token;
import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener;
import cuchaz.enigma.mapping.ArgumentEntry;
import cuchaz.enigma.mapping.ClassEntry;
import cuchaz.enigma.mapping.ConstructorEntry;
import cuchaz.enigma.mapping.Entry;
import cuchaz.enigma.mapping.FieldEntry;
import cuchaz.enigma.mapping.IllegalNameException;
import cuchaz.enigma.mapping.MappingParseException;
import cuchaz.enigma.mapping.MethodEntry;
import cuchaz.enigma.mapping.Signature;
import de.sciss.syntaxpane.DefaultSyntaxKit;
		setObfClasses(null);
		setDeobfClasses(null);
		setSource(null);
		m_classesPanel.removeAll();
		
		// update menu
		m_closeJarMenu.setEnabled(false);
		m_openMappingsMenu.setEnabled(false);
		m_saveMappingsMenu.setEnabled(false);
		m_saveMappingsAsMenu.setEnabled(false);
		m_closeMappingsMenu.setEnabled(false);
		m_exportSourceMenu.setEnabled(false);
		m_exportJarMenu.setEnabled(false);
		
		redraw();
	}
	
	public void setObfClasses(Collection<ClassEntry> obfClasses) {
		m_obfClasses.setClasses(obfClasses);
	}
	
	public void setDeobfClasses(Collection<ClassEntry> deobfClasses) {
		m_deobfClasses.setClasses(deobfClasses);
	}

	public void setNonePkgClasses(Collection<ClassEntry> bfClasses){
		m_nonePackageClasses.setClasses(bfClasses);
	}

	public void setInnerClasses(Collection<ClassEntry> bfClasses){
		m_innerClasses.setClasses(bfClasses);
	}
	
	public void setMappingsFile(File file) {
		m_mappingsFileChooser.setSelectedFile(file);
		m_saveMappingsMenu.setEnabled(file != null);
	}
	
	public void setSource(String source) {
		m_editor.getHighlighter().removeAllHighlights();
		m_editor.setText(source);
	}
	
	public void showToken(final Token token) {
		if (token == null) {
			throw new IllegalArgumentException("Token cannot be null!");
		}
		CodeReader.navigateToToken(m_editor, token, m_selectionHighlightPainter);
		redraw();
	}
	
	public void showTokens(Collection<Token> tokens) {
		Vector<Token> sortedTokens = new Vector<Token>(tokens);
		Collections.sort(sortedTokens);
		if (sortedTokens.size() > 1) {
			// sort the tokens and update the tokens panel
			m_tokens.setListData(sortedTokens);
			m_tokens.setSelectedIndex(0);
		} else {
			m_tokens.setListData(new Vector<Token>());
		}
		
		// show the first token
		showToken(sortedTokens.get(0));
	}
	
	public void setHighlightedTokens(Iterable<Token> obfuscatedTokens, Iterable<Token> deobfuscatedTokens, Iterable<Token> otherTokens) {
		
		// remove any old highlighters
		m_editor.getHighlighter().removeAllHighlights();
		
		// color things based on the index
		if (obfuscatedTokens != null) {
			setHighlightedTokens(obfuscatedTokens, m_obfuscatedHighlightPainter);
		}
		if (deobfuscatedTokens != null) {
			setHighlightedTokens(deobfuscatedTokens, m_deobfuscatedHighlightPainter);
		}
		if (otherTokens != null) {
			setHighlightedTokens(otherTokens, m_otherHighlightPainter);
		}
		
		redraw();
	}
	
	private void setHighlightedTokens(Iterable<Token> tokens, Highlighter.HighlightPainter painter) {
		for (Token token : tokens) {
			try {
				m_editor.getHighlighter().addHighlight(token.start, token.end, painter);
			} catch (BadLocationException ex) {
				throw new IllegalArgumentException(ex);
			}
		}
	}
	
	private void clearReference() {
		m_infoPanel.removeAll();
		JLabel label = new JLabel("No identifier selected");
		GuiTricks.unboldLabel(label);
		label.setHorizontalAlignment(JLabel.CENTER);
		m_infoPanel.add(label);
		
		redraw();
	}
	
	private void showReference(EntryReference<Entry,Entry> reference) {
		if (reference == null) {
			clearReference();
			return;
		}
		
		m_reference = reference;
		
		m_infoPanel.removeAll();
		if (reference.entry instanceof ClassEntry) {
			showClassEntry((ClassEntry)m_reference.entry);
		} else if (m_reference.entry instanceof FieldEntry) {
			showFieldEntry((FieldEntry)m_reference.entry);
		} else if (m_reference.entry instanceof MethodEntry) {
			showMethodEntry((MethodEntry)m_reference.entry);
		} else if (m_reference.entry instanceof ConstructorEntry) {