package codechicken.lib.asm;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.ImmutableMap;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.JumpInsnNode;
import org.objectweb.asm.tree.LabelNode;
import java.util.*;
import java.util.Map.Entry;
import static org.objectweb.asm.tree.AbstractInsnNode.*;
public class ASMBlock {
public InsnListSection list;
private BiMap<String, LabelNode> labels;
public ASMBlock(InsnListSection list, BiMap<String, LabelNode> labels) {
this.list = list;
this.labels = labels;
}
public ASMBlock(InsnListSection list) {
this(list, HashBiMap.<String, LabelNode>create());
}
public ASMBlock(InsnList list) {
this(new InsnListSection(list));
}
public ASMBlock() {
this(new InsnListSection());
}
public LabelNode getOrAdd(String s) {
LabelNode l = get(s);
if (l == null) {
labels.put(s, l = new LabelNode());
}
return l;
}
public LabelNode get(String s) {
return labels.get(s);
}
public void replaceLabels(Map<LabelNode, LabelNode> labelMap, Set<LabelNode> usedLabels) {
for (AbstractInsnNode insn : list) {
switch (insn.getType()) {
case LABEL:
AbstractInsnNode insn2 = insn.clone(labelMap);
if (insn2 == insn)//identity mapping
{
continue;
}
if (usedLabels.contains(insn2)) {
throw new IllegalStateException("LabelNode cannot be a part of two InsnLists");
}
list.replace(insn, insn2);
break;
case JUMP_INSN:
case FRAME:
case LOOKUPSWITCH_INSN:
case TABLESWITCH_INSN:
list.replace(insn, insn.clone(labelMap));
}
}
for (Entry<LabelNode, LabelNode> entry : labelMap.entrySet()) {
String key = labels.inverse().get(entry.getKey());
if (key != null) {
labels.put(key, entry.getValue());
}
}
}
public void replaceLabels(Map<LabelNode, LabelNode> labelMap) {
replaceLabels(labelMap, Collections.EMPTY_SET);
}
public void replaceLabel(String s, LabelNode l) {
LabelNode old = get(s);
if (old != null) {
replaceLabels(ImmutableMap.of(old, l));
}
}
/**
* Pulls all common labels from other into this
*
* @return this
*/
public ASMBlock mergeLabels(ASMBlock other) {
if (labels.isEmpty() || other.labels.isEmpty()) {
return this;
}
//common labels, give them our nodes
HashMap<LabelNode, LabelNode> labelMap = list.identityLabelMap();
for (Entry<String, LabelNode> entry : other.labels.entrySet()) {
LabelNode old = labels.get(entry.getKey());
if (old != null) {
labelMap.put(old, entry.getValue());
}
}
HashSet<LabelNode> usedLabels = new HashSet<LabelNode>();
for (AbstractInsnNode insn = other.list.list.getFirst(); insn != null; insn = insn.getNext()) {
if (insn.getType() == LABEL) {
usedLabels.add((LabelNode) insn);
}
}
replaceLabels(labelMap, usedLabels);
return this;
}
/**
* Like mergeLabels but pulls insns from other list into this so LabelNodes can be transferred
*
* @return this
*/
public ASMBlock pullLabels(ASMBlock other) {
other.list.remove();
return mergeLabels(other);
}
public ASMBlock copy() {
BiMap<String, LabelNode> labels = HashBiMap.create();
Map<LabelNode, LabelNode> labelMap = list.cloneLabels();
for (Entry<String, LabelNode> entry : this.labels.entrySet()) {
labels.put(entry.getKey(), labelMap.get(entry.getValue()));
}
return new ASMBlock(list.copy(labelMap), labels);
}
public ASMBlock applyLabels(InsnListSection list2) {
if (labels.isEmpty()) {
return new ASMBlock(list2);
}
Set<LabelNode> cFlowLabels1 = labels.values();
Set<LabelNode> cFlowLabels2 = InsnComparator.getControlFlowLabels(list2);
ASMBlock block = new ASMBlock(list2);
HashMap<LabelNode, LabelNode> labelMap = new HashMap<LabelNode, LabelNode>();
for (int i = 0, k = 0; i < list.size() && k < list2.size(); ) {
AbstractInsnNode insn1 = list.get(i);
if (!InsnComparator.insnImportant(insn1, cFlowLabels1)) {
i++;
continue;
}
AbstractInsnNode insn2 = list2.get(k);
if (!InsnComparator.insnImportant(insn2, cFlowLabels2)) {
k++;
continue;
}
if (insn1.getOpcode() != insn2.getOpcode()) {
throw new IllegalArgumentException("Lists do not match:\n" + list + "\n\n" + list2);
}
switch (insn1.getType()) {
case LABEL:
labelMap.put((LabelNode) insn1, (LabelNode) insn2);
break;
case JUMP_INSN:
labelMap.put(((JumpInsnNode) insn1).label, ((JumpInsnNode) insn2).label);
break;
}
i++;
k++;
}
for (Entry<String, LabelNode> entry : labels.entrySet()) {
block.labels.put(entry.getKey(), labelMap.get(entry.getValue()));
}
return block;
}
public InsnList rawListCopy() {
return list.copy().list;
}
}