package org.coldswap.asm.field;
import org.coldswap.asm.MemberReplacer;
import org.coldswap.transformer.ReferenceReplacerManager;
import org.coldswap.util.*;
import org.objectweb.asm.*;
import org.objectweb.asm.tree.*;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
/**
* (C) Copyright 2013 Faur Ioan-Aurel.
* <p/>
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser General Public License
* (LGPL) version 2.1 which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/lgpl-2.1.html
* <p/>
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
* <p/>
* Contributors:
* faur
* <p/>
* Created at:
* 4:56 PM 5/5/13
*/
public class ProtectedStaticFieldReplacer implements MemberReplacer {
private static final Logger logger = Logger.getLogger(ProtectedStaticFieldReplacer.class.getName());
private static final Map<FieldNode, InsnList> inheritedInitCode = new HashMap<FieldNode, InsnList>();
private final Class<?> aClass;
private ReferenceReplacerManager replacerManager = ReferenceReplacerManager.getInstance();
private byte[] bytes;
private String classPackage = "";
static {
logger.setLevel(ClassUtil.logLevel);
}
public ProtectedStaticFieldReplacer(Class<?> clazz, byte[] bytes) {
this.aClass = clazz;
this.bytes = bytes;
String sPackage = "";
Package p = clazz.getPackage();
if (p != null) {
sPackage = p.getName();
}
classPackage = sPackage.replace(".", ClassUtil.fileSeparator) + ClassUtil.fileSeparator;
}
public byte[] replace() {
final ClassNode cn = new ClassNode(Opcodes.ASM5);
ClassReader cr = new ClassReader(bytes);
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES);
cr.accept(cn, 0);
// get a list of public/protected fields.
Field[] refFields = aClass.getDeclaredFields();
List asmFields = getFields(cn);
Iterator iterator = asmFields.iterator();
while (iterator.hasNext()) {
final FieldNode fNode = (FieldNode) iterator.next();
int protectedStatic = Opcodes.ACC_STATIC | Opcodes.ACC_PROTECTED;
// search only for protected static fields
if ((fNode.access == protectedStatic)) {
// check if this field exist in the old loaded class
// and if not proceed with the trick.
boolean foundIt = false;
for (Field refField : refFields) {
if (fNode.name.equals(refField.getName())) {
if (fNode.desc.equals(Type.getType(refField.getType()).getDescriptor())) {
foundIt = true;
break;
}
}
}
if (!foundIt) {
// register a public static reference replacer
String contClass = cn.name.substring(cn.name.lastIndexOf("/") + 1);
String className = TransformerNameGenerator.getProtectedStaticFieldClassName(contClass, fNode.name);
// make field access as public static
fNode.access = Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC;
replacerManager.registerFieldReferenceReplacer(new ProtectedStaticFieldReferenceReplacer(contClass,
fNode, classPackage + className, contClass));
// remove the static reference from <clinit>
InsnList insnList = cleanClInit(cn, fNode, true);
// because the field might be inherited from superclass we need to copy
// the initializer from the parent.
if ((insnList == null) || (insnList.size() > 0)) {
insnList = inheritedInitCode.get(fNode);
}
// create a new class that contains the field
// before anything change the field access to public static so that it can be referenced
// from other classes.
fNode.access = Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC;
byte[] newBClass = ByteCodeGenerator.newFieldClass(cn, fNode, insnList, classPackage + className);
try {
String cp = ClassUtil.getClassPath(aClass);
ByteCodeClassWriter.setClassPath(cp);
ByteCodeClassWriter.writeClass(className, newBClass);
} catch (IOException e) {
logger.warning(e.toString());
}
iterator.remove();
// replace every reference
replaceReferences(cn, fNode);
}
}
}
cn.accept(cw);
return cw.toByteArray();
}
/**
* Removes any initializing reference of the field.
*
* @param classNode containing the old class.
* @param fieldNode containing the old field.
* @param canRemove <code>true</code> if this method should remove the initializing code and return it
* or <code>false</code> if you only want to return the init code.
* @return the initializing list of instructions.
*/
@SuppressWarnings("unchecked")
private InsnList cleanClInit(ClassNode classNode, FieldNode fieldNode, boolean canRemove) {
List<MethodNode> methodNodes = classNode.methods;
AbstractInsnNode firstInst = null;
int counter = 0;
for (MethodNode methodNode : methodNodes) {
if (methodNode.name.equals("<clinit>")) {
// search for PUTSTATIC
InsnList insnList = methodNode.instructions;
Iterator iterator1 = insnList.iterator();
while (iterator1.hasNext()) {
AbstractInsnNode ins2 = (AbstractInsnNode) iterator1.next();
// if a initializing has been found, then copy everything from
// the coresponding label to the PUTSTATIC
if (ins2.getOpcode() == Opcodes.PUTSTATIC) {
final Boolean[] fieldFound = {false};
final FieldNode fNode = fieldNode;
ins2.accept(new MethodVisitor(Opcodes.ASM5) {
@Override
public void visitFieldInsn(int i, String s, String s2, String s3) {
if (s2.equals(fNode.name)) {
fieldFound[0] = true;
}
super.visitFieldInsn(i, s, s2, s3);
}
});
if (fieldFound[0]) {
// find the first PUTSTATIC before this one.
boolean staticFound = false;
while (!staticFound) {
AbstractInsnNode tmpInst = ins2.getPrevious();
if (tmpInst != null) {
if (tmpInst.getOpcode() != Opcodes.F_NEW) {
if (tmpInst.getOpcode() == Opcodes.PUTSTATIC) {
staticFound = true;
} else {
firstInst = tmpInst;
counter++;
}
}
} else {
staticFound = true;
}
ins2 = tmpInst;
}
break;
}
}
}
if (firstInst != null) {
InsnList iList = new InsnList();
iList.add(firstInst.clone(null));
counter--;
while (counter > 0) {
AbstractInsnNode ain = firstInst.getNext();
iList.add(ain.clone(null));
counter--;
if (canRemove) {
insnList.remove(firstInst);
}
firstInst = ain;
}
if (canRemove) {
// remove last instruction and the putstatic instruction
AbstractInsnNode putStatic = firstInst.getNext();
insnList.remove(firstInst);
insnList.remove(putStatic);
}
return iList;
}
}
}
return null;
}
/**
* Replaces any GETSTATIC/PUTSTATIC call of the field in the old class with the field
* introduced in the new class.
*
* @param classNode containing the old class.
* @param fieldNode containing the old field.
*/
private void replaceReferences(ClassNode classNode, FieldNode fieldNode) {
List<MethodNode> methodNodes = classNode.methods;
String contClass = classNode.name.substring(classNode.name.lastIndexOf("/") + 1);
final String className = classPackage + TransformerNameGenerator.getProtectedStaticFieldClassName(contClass, fieldNode.name);
for (MethodNode method : methodNodes) {
InsnList inst = method.instructions;
Iterator iter = inst.iterator();
while (iter.hasNext()) {
AbstractInsnNode absIns = (AbstractInsnNode) iter.next();
int opcode = absIns.getOpcode();
// check if instruction is GETSTATIC or PUTSTATIC
if (opcode == Opcodes.GETSTATIC) {
// get type
if (absIns.getType() == AbstractInsnNode.FIELD_INSN) {
final Boolean[] foundField = {false};
final ClassNode cNode = classNode;
final FieldNode fNode = fieldNode;
absIns.accept(new MethodVisitor(Opcodes.ASM5) {
@Override
public void visitFieldInsn(int i, String s, String s2, String s3) {
if (cNode.name.equals(s) && fNode.name.equals(s2)) {
foundField[0] = true;
}
super.visitFieldInsn(i, s, s2, s3);
}
});
if (foundField[0]) {
inst.set(absIns, new FieldInsnNode(Opcodes.GETSTATIC, className, fieldNode.name, fieldNode.desc));
}
}
} else if (opcode == Opcodes.PUTSTATIC) {
if (absIns.getType() == AbstractInsnNode.FIELD_INSN) {
final Boolean[] foundField = {false};
final ClassNode cNode = classNode;
final FieldNode fNode = fieldNode;
absIns.accept(new MethodVisitor(Opcodes.ASM5) {
@Override
public void visitFieldInsn(int i, String s, String s2, String s3) {
if (cNode.name.equals(s) && fNode.name.equals(s2)) {
foundField[0] = true;
}
super.visitFieldInsn(i, s, s2, s3);
}
});
if (foundField[0]) {
inst.set(absIns, new FieldInsnNode(Opcodes.PUTSTATIC, className, fieldNode.name, fieldNode.desc));
}
}
}
}
}
}
/**
* Returns an array of fields that where declared in this class. In
* addition there are also the protected fields that were inherited.
*
* @param classNode {@link ClassNode} containing the new class version.
* @return a list of all fields including the inherited fields.
*/
private List<FieldNode> getFields(ClassNode classNode) {
List<FieldNode> toRet = classNode.fields;
// get inherited package fields
try {
String superName = classNode.superName;
// filter superclasses
for (String pack : ClassUtil.skipTransforming) {
if (superName.startsWith(pack)) {
return toRet;
}
}
Class<?> c = Class.forName(superName);
String path = ClassUtil.getClassPath(c);
path = path + ClassUtil.fileSeparator + superName + ".class";
byte[] clazz = ByteCodeClassLoader.loadClassBytes(path);
ClassNode cNode = new ClassNode(Opcodes.ASM5);
ClassReader cr = new ClassReader(clazz);
cr.accept(cNode, 0);
List<FieldNode> superFields = cNode.fields;
for (FieldNode fieldNode : superFields) {
if (fieldNode.access == (Opcodes.ACC_STATIC | Opcodes.ACC_PROTECTED)) {
toRet.add(fieldNode);
InsnList code = cleanClInit(cNode, fieldNode, false);
inheritedInitCode.put(fieldNode, code);
}
}
} catch (ClassNotFoundException e) {
logger.warning(e.toString());
}
return toRet;
}
}