move to springloaded
This commit is contained in:
10
springloaded/src/main/java/META-INF/MANIFEST.MF
Normal file
10
springloaded/src/main/java/META-INF/MANIFEST.MF
Normal file
@@ -0,0 +1,10 @@
|
||||
Manifest-Version: 1.0
|
||||
Specification-Title: SpringLoaded Agent
|
||||
Specification-Version: 1.0.0
|
||||
Specification-Vendor: SpringSource
|
||||
Implementation-Title: org.springsource.loaded
|
||||
Implementation-Version: 1.0.0
|
||||
Implementation-Vendor: SpringSource
|
||||
Premain-Class: org.springsource.loaded.agent.SpringLoadedAgent
|
||||
Agent-Class: org.springsource.loaded.agent.SpringLoadedAgent
|
||||
Can-Redefine-Classes: true
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
/**
|
||||
* Simple implementation of Member which could represent a method, field or constructor.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public abstract class AbstractMember implements Constants {
|
||||
|
||||
protected final int modifiers;
|
||||
protected final String name;
|
||||
protected final String descriptor; // this is the erased descriptor. There is no generic descriptor.
|
||||
// Members have a well known id within their type - ids are unique per kind of member (methods/fields/constructors)
|
||||
protected int id = -1;
|
||||
// For generic methods, contains generic signature
|
||||
protected final String signature;
|
||||
private final boolean isPrivate; // gets asked a lot so made into a flag
|
||||
|
||||
protected AbstractMember(int modifiers, String name, String descriptor, String signature) {
|
||||
this.modifiers = modifiers;
|
||||
this.name = name;
|
||||
this.descriptor = descriptor;
|
||||
this.signature = signature;
|
||||
this.isPrivate = Modifier.isPrivate(modifiers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the name of the member
|
||||
*/
|
||||
public final String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the member descriptor. methods/constructors: "()Ljava/lang/String;" fields: "Ljava/lang/String;"
|
||||
*/
|
||||
public final String getDescriptor() {
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the generics related signature. May be null if this method is non-generic.
|
||||
*/
|
||||
public String getGenericSignature() {
|
||||
return signature;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the modifiers of the member
|
||||
*/
|
||||
public final int getModifiers() {
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the allocated ID for this member
|
||||
*/
|
||||
public final int getId() {
|
||||
if (id == -1) {
|
||||
throw new IllegalStateException("id not yet allocated");
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param id the id number to assign to this member for later quick reference.
|
||||
*/
|
||||
public final void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
// helpers
|
||||
|
||||
public final boolean isStatic() {
|
||||
return Modifier.isStatic(getModifiers());
|
||||
}
|
||||
|
||||
public final boolean isFinal() {
|
||||
return Modifier.isFinal(getModifiers());
|
||||
}
|
||||
|
||||
public final boolean isPrivate() {
|
||||
return isPrivate;
|
||||
}
|
||||
|
||||
public final boolean isProtected() {
|
||||
return Modifier.isProtected(getModifiers());
|
||||
}
|
||||
|
||||
public final boolean isPublic() {
|
||||
return Modifier.isPublic(getModifiers());
|
||||
}
|
||||
|
||||
public boolean isPrivateStaticFinal() {
|
||||
return (modifiers & ACC_PRIVATE_STATIC_FINAL) != 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
/**
|
||||
* Represents '*' type pattern.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class AnyTypePattern extends TypePattern {
|
||||
|
||||
public AnyTypePattern() {
|
||||
}
|
||||
|
||||
protected boolean internalMatches(String input) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "text:*";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.8.2
|
||||
*/
|
||||
public class Asserts {
|
||||
|
||||
public static boolean assertNotDotted(String name) {
|
||||
if (name.indexOf('.') != -1) {
|
||||
throw new IllegalStateException("Did not expect a dotted name " + name);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
26
springloaded/src/main/java/org/springsource/loaded/C.java
Normal file
26
springloaded/src/main/java/org/springsource/loaded/C.java
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
// TODO [moredoc]
|
||||
/**
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
// marker for generated ctors
|
||||
public class C {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
|
||||
/**
|
||||
* The ChildClassLoader will load the generated dispatchers and executors which change for each reload. Instances of this can be
|
||||
* discarded which will cause 'old' dispatchers/executors to be candidates for GC too (avoiding memory leaks when lots of reloads
|
||||
* occur).
|
||||
*/
|
||||
public class ChildClassLoader extends URLClassLoader {
|
||||
|
||||
private static URL[] NO_URLS = new URL[0];
|
||||
private int definedCount = 0;
|
||||
|
||||
public ChildClassLoader(ClassLoader classloader) {
|
||||
super(NO_URLS, classloader);
|
||||
}
|
||||
|
||||
public Class<?> defineClass(String name, byte[] bytes) {
|
||||
definedCount++;
|
||||
return super.defineClass(name, bytes, 0, bytes.length);
|
||||
}
|
||||
|
||||
public int getDefinedCount() {
|
||||
return definedCount;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.objectweb.asm.ClassAdapter;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.FieldVisitor;
|
||||
import org.objectweb.asm.MethodAdapter;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.Type;
|
||||
|
||||
/**
|
||||
* Modify a class by changing it from one name to another. References to other types can also be changed. Basically used in the test
|
||||
* suite.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class ClassRenamer {
|
||||
|
||||
/**
|
||||
* Rename a type - changing it to specified new name (which should be the dotted form of the name). Retargets are an optional
|
||||
* sequence of retargets to also perform during the rename. Retargets take the form of "a.b:a.c" which will change all
|
||||
* references to a.b to a.c.
|
||||
*
|
||||
* @param dottedNewName dotted name, e.g. com.foo.Bar
|
||||
* @param classbytes the bytecode for the class to be renamed
|
||||
* @param retargets retarget rules for references, of the form "a.b:b.a","c.d:d.c"
|
||||
* @return bytecode for the modified class
|
||||
*/
|
||||
public static byte[] rename(String dottedNewName, byte[] classbytes, String... retargets) {
|
||||
ClassReader fileReader = new ClassReader(classbytes);
|
||||
RenameAdapter renameAdapter = new RenameAdapter(dottedNewName, retargets);
|
||||
fileReader.accept(renameAdapter, 0);
|
||||
byte[] renamed = renameAdapter.getBytes();
|
||||
return renamed;
|
||||
}
|
||||
|
||||
static class RenameAdapter extends ClassAdapter implements Opcodes {
|
||||
|
||||
private ClassWriter cw;
|
||||
private String oldname;
|
||||
private String newname;
|
||||
private Map<String, String> retargets = new HashMap<String, String>();
|
||||
|
||||
public RenameAdapter(String newname, String[] retargets) {
|
||||
super(new ClassWriter(0));
|
||||
cw = (ClassWriter) cv;
|
||||
this.newname = newname.replace('.', '/');
|
||||
if (retargets != null) {
|
||||
for (String retarget : retargets) {
|
||||
int i = retarget.indexOf(":");
|
||||
this.retargets.put(retarget.substring(0, i).replace('.', '/'), retarget.substring(i + 1).replace('.', '/'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
return cw.toByteArray();
|
||||
}
|
||||
|
||||
private String retargetIfNecessary(String string) {
|
||||
String value = retargets.get(string);
|
||||
return value == null ? string : value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
|
||||
oldname = name;
|
||||
if (superName != null) {
|
||||
superName = retargetIfNecessary(superName);
|
||||
}
|
||||
if (interfaces != null) {
|
||||
for (int i = 0; i < interfaces.length; i++) {
|
||||
interfaces[i] = retargetIfNecessary(interfaces[i]);
|
||||
}
|
||||
}
|
||||
super.visit(version, access, newname, signature, superName, interfaces);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitInnerClass(String name, String outername, String innerName, int access) {
|
||||
super.visitInnerClass(renameRetargetIfNecessary(name), renameRetargetIfNecessary(outername), renameRetargetIfNecessary(innerName), access);
|
||||
}
|
||||
|
||||
private String renameRetargetIfNecessary(String string) {
|
||||
String value = retargets.get(string);
|
||||
if (value!=null) {
|
||||
return value;
|
||||
}
|
||||
if (string.indexOf(oldname) != -1) {
|
||||
return string.replace(oldname, newname);
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodVisitor visitMethod(int flags, String name, String descriptor, String signature, String[] exceptions) {
|
||||
if (descriptor.indexOf(oldname) != -1) {
|
||||
descriptor = descriptor.replace(oldname, newname);
|
||||
} else {
|
||||
for (String s : retargets.keySet()) {
|
||||
if (descriptor.indexOf(s) != -1) {
|
||||
descriptor = descriptor.replace(s, retargets.get(s));
|
||||
}
|
||||
}
|
||||
}
|
||||
MethodVisitor mv = super.visitMethod(flags, name, descriptor, signature, exceptions);
|
||||
return new RenameMethodAdapter(mv, oldname, newname);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
|
||||
if (desc.indexOf(oldname) != -1) {
|
||||
desc = desc.replace(oldname, newname);
|
||||
} else {
|
||||
for (String s : retargets.keySet()) {
|
||||
if (desc.indexOf(s) != -1) {
|
||||
desc = desc.replace(s, retargets.get(s));
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.visitField(access, name, desc, signature, value);
|
||||
}
|
||||
|
||||
class RenameMethodAdapter extends MethodAdapter implements Opcodes {
|
||||
|
||||
String oldname;
|
||||
String newname;
|
||||
|
||||
public RenameMethodAdapter(MethodVisitor mv, String oldname, String newname) {
|
||||
super(mv);
|
||||
this.oldname = oldname;
|
||||
this.newname = newname;
|
||||
}
|
||||
|
||||
public void visitFieldInsn(int opcode, String owner, String name, String desc) {
|
||||
if (owner.equals(oldname)) {
|
||||
owner = newname;
|
||||
} else {
|
||||
String retarget = retargets.get(owner);
|
||||
if (retarget != null) {
|
||||
owner = retarget;
|
||||
}
|
||||
}
|
||||
if (desc.indexOf(oldname) != -1) {
|
||||
desc = desc.replace(oldname, newname);
|
||||
} else {
|
||||
desc = checkIfShouldBeRewritten(desc);
|
||||
}
|
||||
mv.visitFieldInsn(opcode, owner, name, desc);
|
||||
}
|
||||
|
||||
public void visitTypeInsn(int opcode, String type) {
|
||||
if (type.equals(oldname)) {
|
||||
type = newname;
|
||||
} else {
|
||||
String retarget = retargets.get(type);
|
||||
if (retarget != null) {
|
||||
type = retarget;
|
||||
} else {
|
||||
if (type.startsWith("[")) {
|
||||
if (type.indexOf(oldname) != -1) {
|
||||
type = type.replaceFirst(oldname, newname);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
mv.visitTypeInsn(opcode, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitLdcInsn(Object obj) {
|
||||
// System.out.println("Possibly remapping "+obj);
|
||||
if (obj instanceof Type) {
|
||||
Type t = (Type) obj;
|
||||
String s = t.getInternalName();
|
||||
String retarget = retargets.get(s);
|
||||
if (retarget != null) {
|
||||
mv.visitLdcInsn(Type.getObjectType(retarget));
|
||||
} else {
|
||||
mv.visitLdcInsn(obj);
|
||||
}
|
||||
} else if (obj instanceof String) {
|
||||
String s = (String) obj;
|
||||
String retarget = retargets.get(s.replace('.', '/'));
|
||||
if (retarget != null) {
|
||||
mv.visitLdcInsn(retarget.replace('/', '.'));
|
||||
} else {
|
||||
String oldnameDotted = oldname.replace('/', '.');
|
||||
if (s.equals(oldnameDotted)) {
|
||||
String nname = newname.replace('/', '.');
|
||||
mv.visitLdcInsn(nname);
|
||||
return;
|
||||
} else if (s.startsWith("[")) {
|
||||
// might be array of oldname
|
||||
if (s.indexOf(oldnameDotted) != -1) {
|
||||
mv.visitLdcInsn(s.replaceFirst(oldnameDotted, newname.replace('/', '.')));
|
||||
return;
|
||||
}
|
||||
}
|
||||
mv.visitLdcInsn(obj);
|
||||
}
|
||||
} else {
|
||||
mv.visitLdcInsn(obj);
|
||||
}
|
||||
}
|
||||
|
||||
public void visitMethodInsn(int opcode, String owner, String name, String desc) {
|
||||
if (owner.equals(oldname)) {
|
||||
owner = newname;
|
||||
} else {
|
||||
owner = retargetIfNecessary(owner);
|
||||
}
|
||||
if (desc.indexOf(oldname) != -1) {
|
||||
desc = desc.replace(oldname, newname);
|
||||
} else {
|
||||
desc = checkIfShouldBeRewritten(desc);
|
||||
}
|
||||
mv.visitMethodInsn(opcode, owner, name, desc);
|
||||
}
|
||||
|
||||
private String checkIfShouldBeRewritten(String desc) {
|
||||
for (String s : retargets.keySet()) {
|
||||
if (desc.indexOf(s) != -1) {
|
||||
desc = desc.replace(s, retargets.get(s));
|
||||
}
|
||||
}
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,332 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
// TODO does not yet support the new constant pool entry types that come with Java 7
|
||||
|
||||
// http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html
|
||||
/**
|
||||
* Quickly checks the constant pool for class references, it skips everything else as fast as it can. The class references are then
|
||||
* available for checking.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.7.3
|
||||
*/
|
||||
public class ConstantPoolChecker {
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private final static byte CONSTANT_Utf8 = 1;
|
||||
private final static byte CONSTANT_Integer = 3;
|
||||
private final static byte CONSTANT_Float = 4;
|
||||
private final static byte CONSTANT_Long = 5;
|
||||
private final static byte CONSTANT_Double = 6;
|
||||
private final static byte CONSTANT_Class = 7;
|
||||
private final static byte CONSTANT_String = 8;
|
||||
private final static byte CONSTANT_Fieldref = 9;
|
||||
private final static byte CONSTANT_Methodref = 10;
|
||||
private final static byte CONSTANT_InterfaceMethodref = 11;
|
||||
private final static byte CONSTANT_NameAndType = 12;
|
||||
|
||||
// Test entry point just goes through all the code in the bin folder
|
||||
public static void main(String[] args) throws Exception {
|
||||
// File[] fs = new File("./bin").listFiles();
|
||||
// File[] fs = new File("../testdata-groovy/bin").listFiles();
|
||||
// checkThemAll(fs);
|
||||
// System.out.println("total=" + total / 1000000d);
|
||||
}
|
||||
|
||||
// static long total = 0;
|
||||
|
||||
// private static void checkThemAll(File[] fs) throws Exception {
|
||||
// for (File f : fs) {
|
||||
// if (f.isDirectory()) {
|
||||
// checkThemAll(f.listFiles());
|
||||
// } else if (f.getName().endsWith(".class")) {
|
||||
// System.out.println(f);
|
||||
// byte[] data = Utils.loadFromStream(new FileInputStream(f));
|
||||
// long stime = System.nanoTime();
|
||||
// List<String> ls = getReferencedClasses(data);
|
||||
// // total += (System.nanoTime() - stime);
|
||||
// System.out.println(ls);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// ClassFile {
|
||||
// u4 magic;
|
||||
// u2 minor_version;
|
||||
// u2 major_version;
|
||||
// u2 constant_pool_count;
|
||||
// cp_info constant_pool[constant_pool_count-1];
|
||||
// u2 access_flags;
|
||||
// u2 this_class;
|
||||
// u2 super_class;
|
||||
// u2 interfaces_count;
|
||||
// u2 interfaces[interfaces_count];
|
||||
// u2 fields_count;
|
||||
// field_info fields[fields_count];
|
||||
// u2 methods_count;
|
||||
// method_info methods[methods_count];
|
||||
// u2 attributes_count;
|
||||
// attribute_info attributes[attributes_count];
|
||||
// }
|
||||
|
||||
static List<String> getReferencedClasses(byte[] bytes) {
|
||||
return new ConstantPoolChecker(bytes).referencedClasses;
|
||||
}
|
||||
|
||||
// Filled with strings and int[]
|
||||
private Object[] cpdata;
|
||||
private int cpsize;
|
||||
private int[] type;
|
||||
// Does not need to be a set as there are no dups in the ConstantPool (for a class from a decent compiler...)
|
||||
private List<String> referencedClasses = new ArrayList<String>();
|
||||
|
||||
private ConstantPoolChecker(byte[] bytes) {
|
||||
readConstantPool(bytes);
|
||||
computeReferences();
|
||||
}
|
||||
|
||||
public void computeReferences() {
|
||||
for (int i = 0; i < cpsize; i++) {
|
||||
switch (type[i]) {
|
||||
case CONSTANT_Class:
|
||||
int classindex = ((Integer) cpdata[i]);
|
||||
String classname = (String) cpdata[classindex];
|
||||
if (classname == null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
referencedClasses.add(classname);
|
||||
break;
|
||||
// private final static byte CONSTANT_Utf8 = 1;
|
||||
// private final static byte CONSTANT_Integer = 3;
|
||||
// private final static byte CONSTANT_Float = 4;
|
||||
// private final static byte CONSTANT_Long = 5;
|
||||
// private final static byte CONSTANT_Double = 6;
|
||||
// private final static byte CONSTANT_String = 8;
|
||||
// private final static byte CONSTANT_Fieldref = 9;
|
||||
// private final static byte CONSTANT_Methodref = 10;
|
||||
// private final static byte CONSTANT_InterfaceMethodref = 11;
|
||||
// private final static byte CONSTANT_NameAndType = 12;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void readConstantPool(byte[] bytes) {
|
||||
try {
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
|
||||
DataInputStream dis = new DataInputStream(bais);
|
||||
|
||||
int magic = dis.readInt(); // magic 0xCAFEBABE
|
||||
if (magic != 0xCAFEBABE) {
|
||||
throw new IllegalStateException("not bytecode, magic was 0x" + Integer.toString(magic, 16));
|
||||
}
|
||||
dis.skip(4);
|
||||
// dis.readShort(); // minor
|
||||
// dis.readShort(); // major
|
||||
cpsize = dis.readShort();
|
||||
if (DEBUG) {
|
||||
System.out.println("Constant Pool Size =" + cpsize);
|
||||
}
|
||||
cpdata = new Object[cpsize];
|
||||
type = new int[cpsize];
|
||||
// int max = cpsize - 1;
|
||||
for (int cpentry = 1; cpentry < cpsize; cpentry++) {
|
||||
boolean doubleSlot = processConstantPoolEntry(cpentry, dis);
|
||||
if (doubleSlot) {
|
||||
cpentry++;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Unexpected problem processing bytes for class", e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean processConstantPoolEntry(int index, DataInputStream dis) throws IOException {
|
||||
byte b = dis.readByte();
|
||||
type[index] = b;
|
||||
switch (b) {
|
||||
case CONSTANT_Utf8:
|
||||
// CONSTANT_Utf8_info {
|
||||
// u1 tag;
|
||||
// u2 length;
|
||||
// u1 bytes[length];
|
||||
// }
|
||||
cpdata[index] = dis.readUTF();
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":UTF8[" + cpdata[index] + "]");
|
||||
}
|
||||
break;
|
||||
case CONSTANT_Integer:
|
||||
// CONSTANT_Integer_info {
|
||||
// u1 tag;
|
||||
// u4 bytes;
|
||||
// }
|
||||
if (DEBUG) {
|
||||
int i = dis.readInt();
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":INTEGER[" + i + "]");
|
||||
}
|
||||
} else {
|
||||
dis.skip(4);
|
||||
}
|
||||
break;
|
||||
case CONSTANT_Float:
|
||||
// CONSTANT_Float_info {
|
||||
// u1 tag;
|
||||
// u4 bytes;
|
||||
// }
|
||||
if (DEBUG) {
|
||||
float f = dis.readFloat();
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":FLOAT[" + f + "]");
|
||||
}
|
||||
} else {
|
||||
dis.skip(4);
|
||||
}
|
||||
break;
|
||||
case CONSTANT_Long:
|
||||
// CONSTANT_Long_info {
|
||||
// u1 tag;
|
||||
// u4 high_bytes;
|
||||
// u4 low_bytes;
|
||||
// }
|
||||
if (DEBUG) {
|
||||
long l = dis.readLong();
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":LONG[" + l + "]");
|
||||
}
|
||||
} else {
|
||||
dis.skip(8);
|
||||
}
|
||||
return true;
|
||||
case CONSTANT_Double:
|
||||
// CONSTANT_Double_info {
|
||||
// u1 tag;
|
||||
// u4 high_bytes;
|
||||
// u4 low_bytes;
|
||||
// }
|
||||
if (DEBUG) {
|
||||
double d = dis.readDouble();
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":DOUBLE[" + d + "]");
|
||||
}
|
||||
} else {
|
||||
dis.skip(8);
|
||||
}
|
||||
return true;
|
||||
case CONSTANT_Class:
|
||||
// CONSTANT_Class_info {
|
||||
// u1 tag;
|
||||
// u2 name_index;
|
||||
// }
|
||||
cpdata[index] = (int) dis.readShort();
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":CLASS[name_index=" + cpdata[index] + "]");
|
||||
}
|
||||
break;
|
||||
case CONSTANT_String:
|
||||
// CONSTANT_String_info {
|
||||
// u1 tag;
|
||||
// u2 string_index;
|
||||
// }
|
||||
if (DEBUG) {
|
||||
cpdata[index] = (int) dis.readShort();
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":STRING[string_index=" + cpdata[index] + "]");
|
||||
}
|
||||
} else {
|
||||
dis.skip(2);
|
||||
}
|
||||
break;
|
||||
case CONSTANT_Fieldref:
|
||||
// CONSTANT_Fieldref_info {
|
||||
// u1 tag;
|
||||
// u2 class_index;
|
||||
// u2 name_and_type_index;
|
||||
// }
|
||||
if (DEBUG) {
|
||||
cpdata[index] = new int[] { dis.readShort(), dis.readShort() };
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":FIELDREF[class_index=" + ((int[]) cpdata[index])[0] + ",name_and_type_index="
|
||||
+ ((int[]) cpdata[index])[1] + "]");
|
||||
}
|
||||
} else {
|
||||
dis.skip(4);
|
||||
}
|
||||
break;
|
||||
case CONSTANT_Methodref:
|
||||
// CONSTANT_Methodref_info {
|
||||
// u1 tag;
|
||||
// u2 class_index;
|
||||
// u2 name_and_type_index;
|
||||
// }
|
||||
if (DEBUG) {
|
||||
cpdata[index] = new int[] { dis.readShort(), dis.readShort() };
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":METHODREF[class_index=" + ((int[]) cpdata[index])[0] + ",name_and_type_index="
|
||||
+ ((int[]) cpdata[index])[1] + "]");
|
||||
}
|
||||
} else {
|
||||
dis.skip(4);
|
||||
}
|
||||
break;
|
||||
case CONSTANT_InterfaceMethodref:
|
||||
// CONSTANT_InterfaceMethodref_info {
|
||||
// u1 tag;
|
||||
// u2 class_index;
|
||||
// u2 name_and_type_index;
|
||||
// }
|
||||
if (DEBUG) {
|
||||
cpdata[index] = new int[] { dis.readShort(), dis.readShort() };
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":INTERFACEMETHODREF[class_index=" + ((int[]) cpdata[index])[0]
|
||||
+ ",name_and_type_index=" + ((int[]) cpdata[index])[1] + "]");
|
||||
}
|
||||
} else {
|
||||
dis.skip(4);
|
||||
}
|
||||
break;
|
||||
case CONSTANT_NameAndType:
|
||||
// The CONSTANT_NameAndType_info structure is used to represent a field or method, without indicating which class or interface type it belongs to:
|
||||
// CONSTANT_NameAndType_info {
|
||||
// u1 tag;
|
||||
// u2 name_index;
|
||||
// u2 descriptor_index;
|
||||
// }
|
||||
if (DEBUG) {
|
||||
cpdata[index] = new int[] { dis.readShort(), dis.readShort() };
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":NAMEANDTYPE[name_index=" + ((int[]) cpdata[index])[0] + ",descriptor_index="
|
||||
+ ((int[]) cpdata[index])[1] + "]");
|
||||
}
|
||||
} else {
|
||||
dis.skip(4);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Entry: " + index + " " + Byte.toString(b));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,339 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
// TODO try to recall why I created ConstantPoolChecker2, what was up with ConstantPoolChecker?
|
||||
|
||||
// http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html
|
||||
/**
|
||||
* Enables us to check things quickly in the constant pool. This version accumulates the class references and the method references,
|
||||
* for classes that start with 'j' (we want to catch: java/lang). It skips everything it can and the end result is a list of class
|
||||
* references and a list of method references. The former look like this 'a/b/C' whilst the latter look like this
|
||||
* 'java/lang/Foo.bar' (the descriptor for the method is not included). Interface methods are skipped.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.7.3
|
||||
*/
|
||||
public class ConstantPoolChecker2 {
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private final static byte CONSTANT_Utf8 = 1;
|
||||
private final static byte CONSTANT_Integer = 3;
|
||||
private final static byte CONSTANT_Float = 4;
|
||||
private final static byte CONSTANT_Long = 5;
|
||||
private final static byte CONSTANT_Double = 6;
|
||||
private final static byte CONSTANT_Class = 7;
|
||||
private final static byte CONSTANT_String = 8;
|
||||
private final static byte CONSTANT_Fieldref = 9;
|
||||
private final static byte CONSTANT_Methodref = 10;
|
||||
private final static byte CONSTANT_InterfaceMethodref = 11;
|
||||
private final static byte CONSTANT_NameAndType = 12;
|
||||
|
||||
// Test entry point just goes through all the code in the bin folder
|
||||
public static void main(String[] args) throws Exception {
|
||||
File[] fs = new File("./bin").listFiles();
|
||||
// File[] fs = new File("../testdata-groovy/bin").listFiles();
|
||||
// File[] fs = new File("/Users/aclement/grailsreload/foo/target/classes").listFiles();
|
||||
|
||||
checkThemAll(fs);
|
||||
System.out.println("total=" + total / 1000000d);
|
||||
}
|
||||
|
||||
private static void checkThemAll(File[] fs) throws Exception {
|
||||
for (File f : fs) {
|
||||
if (f.isDirectory()) {
|
||||
checkThemAll(f.listFiles());
|
||||
} else if (f.getName().endsWith(".class")) {
|
||||
System.out.println(f);
|
||||
byte[] data = Utils.loadFromStream(new FileInputStream(f));
|
||||
long stime = System.nanoTime();
|
||||
References refs = getReferences(data);
|
||||
total += (System.nanoTime() - stime);
|
||||
System.out.println(refs.referencedClasses);
|
||||
System.out.println(refs.referencedMethods);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static long total = 0;
|
||||
|
||||
// ClassFile {
|
||||
// u4 magic;
|
||||
// u2 minor_version;
|
||||
// u2 major_version;
|
||||
// u2 constant_pool_count;
|
||||
// cp_info constant_pool[constant_pool_count-1];
|
||||
// u2 access_flags;
|
||||
// u2 this_class;
|
||||
// u2 super_class;
|
||||
// u2 interfaces_count;
|
||||
// u2 interfaces[interfaces_count];
|
||||
// u2 fields_count;
|
||||
// field_info fields[fields_count];
|
||||
// u2 methods_count;
|
||||
// method_info methods[methods_count];
|
||||
// u2 attributes_count;
|
||||
// attribute_info attributes[attributes_count];
|
||||
// }
|
||||
|
||||
static References getReferences(byte[] bytes) {
|
||||
ConstantPoolChecker2 cpc2 = new ConstantPoolChecker2(bytes);
|
||||
return new References(cpc2.slashedclassname, cpc2.referencedClasses, cpc2.referencedMethods);
|
||||
}
|
||||
|
||||
static class References {
|
||||
String slashedClassName;
|
||||
List<String> referencedClasses;
|
||||
List<String> referencedMethods;
|
||||
|
||||
References(String slashedClassName, List<String> rc, List<String> rm) {
|
||||
this.slashedClassName = slashedClassName;
|
||||
this.referencedClasses = rc;
|
||||
this.referencedMethods = rm;
|
||||
}
|
||||
}
|
||||
|
||||
// Filled with strings and int[]
|
||||
private Object[] cpdata;
|
||||
private int cpsize;
|
||||
private int[] type;
|
||||
// Does not need to be a set as there are no dups in the ConstantPool (for a class from a decent compiler...)
|
||||
private List<String> referencedClasses = new ArrayList<String>();
|
||||
private List<String> referencedMethods = new ArrayList<String>();
|
||||
private String slashedclassname;
|
||||
|
||||
private ConstantPoolChecker2(byte[] bytes) {
|
||||
readConstantPool(bytes);
|
||||
computeReferences();
|
||||
}
|
||||
|
||||
public void computeReferences() {
|
||||
for (int i = 0; i < cpsize; i++) {
|
||||
switch (type[i]) {
|
||||
case CONSTANT_Class:
|
||||
int classindex = ((Integer) cpdata[i]);
|
||||
String classname = (String) cpdata[classindex];
|
||||
if (classname == null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
referencedClasses.add(classname);
|
||||
break;
|
||||
case CONSTANT_Methodref:
|
||||
int[] indexes = (int[]) cpdata[i];
|
||||
int classindex2 = indexes[0];
|
||||
int nameAndTypeIndex = indexes[1];
|
||||
StringBuilder s = new StringBuilder();
|
||||
String theClassName = (String) cpdata[(Integer) cpdata[classindex2]];
|
||||
if (theClassName.charAt(0) == 'j') {
|
||||
s.append(theClassName);
|
||||
s.append(".");
|
||||
s.append((String) cpdata[(Integer) cpdata[nameAndTypeIndex]]);
|
||||
referencedMethods.add(s.toString());
|
||||
}
|
||||
break;
|
||||
// private final static byte CONSTANT_Utf8 = 1;
|
||||
// private final static byte CONSTANT_Integer = 3;
|
||||
// private final static byte CONSTANT_Float = 4;
|
||||
// private final static byte CONSTANT_Long = 5;
|
||||
// private final static byte CONSTANT_Double = 6;
|
||||
// private final static byte CONSTANT_String = 8;
|
||||
// private final static byte CONSTANT_Fieldref = 9;
|
||||
// private final static byte CONSTANT_InterfaceMethodref = 11;
|
||||
// private final static byte CONSTANT_NameAndType = 12;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void readConstantPool(byte[] bytes) {
|
||||
try {
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
|
||||
DataInputStream dis = new DataInputStream(bais);
|
||||
|
||||
int magic = dis.readInt(); // magic 0xCAFEBABE
|
||||
if (magic != 0xCAFEBABE) {
|
||||
throw new IllegalStateException("not bytecode, magic was 0x" + Integer.toString(magic, 16));
|
||||
}
|
||||
dis.skip(4); // skip minor and major versions
|
||||
cpsize = dis.readShort();
|
||||
if (DEBUG) {
|
||||
System.out.println("Constant Pool Size =" + cpsize);
|
||||
}
|
||||
cpdata = new Object[cpsize];
|
||||
type = new int[cpsize];
|
||||
for (int cpentry = 1; cpentry < cpsize; cpentry++) {
|
||||
boolean doubleSlot = processConstantPoolEntry(cpentry, dis);
|
||||
if (doubleSlot) {
|
||||
cpentry++;
|
||||
}
|
||||
}
|
||||
dis.skip(2); // access flags
|
||||
int thisclassname = dis.readShort();
|
||||
int classindex = ((Integer) cpdata[thisclassname]);
|
||||
slashedclassname = (String) cpdata[classindex];
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Unexpected problem processing bytes for class", e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean processConstantPoolEntry(int index, DataInputStream dis) throws IOException {
|
||||
byte b = dis.readByte();
|
||||
switch (b) {
|
||||
case CONSTANT_Utf8:
|
||||
// CONSTANT_Utf8_info { u1 tag; u2 length; u1 bytes[length]; }
|
||||
cpdata[index] = dis.readUTF();
|
||||
// type[index] = b;
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":UTF8[" + cpdata[index] + "]");
|
||||
}
|
||||
break;
|
||||
case CONSTANT_Integer:
|
||||
// CONSTANT_Integer_info { u1 tag; u4 bytes; }
|
||||
if (DEBUG) {
|
||||
int i = dis.readInt();
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":INTEGER[" + i + "]");
|
||||
}
|
||||
} else {
|
||||
dis.skip(4);
|
||||
}
|
||||
break;
|
||||
case CONSTANT_Float:
|
||||
// CONSTANT_Float_info { u1 tag; u4 bytes; }
|
||||
if (DEBUG) {
|
||||
float f = dis.readFloat();
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":FLOAT[" + f + "]");
|
||||
}
|
||||
} else {
|
||||
dis.skip(4);
|
||||
}
|
||||
break;
|
||||
case CONSTANT_Long:
|
||||
// CONSTANT_Long_info {
|
||||
// u1 tag;
|
||||
// u4 high_bytes;
|
||||
// u4 low_bytes;
|
||||
// }
|
||||
if (DEBUG) {
|
||||
long l = dis.readLong();
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":LONG[" + l + "]");
|
||||
}
|
||||
} else {
|
||||
dis.skip(8);
|
||||
}
|
||||
return true;
|
||||
case CONSTANT_Double:
|
||||
// CONSTANT_Double_info {
|
||||
// u1 tag;
|
||||
// u4 high_bytes;
|
||||
// u4 low_bytes;
|
||||
// }
|
||||
if (DEBUG) {
|
||||
double d = dis.readDouble();
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":DOUBLE[" + d + "]");
|
||||
}
|
||||
} else {
|
||||
dis.skip(8);
|
||||
}
|
||||
return true;
|
||||
case CONSTANT_Class:
|
||||
// CONSTANT_Class_info { u1 tag; u2 name_index; }
|
||||
type[index] = b;
|
||||
cpdata[index] = (int) dis.readShort();
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":CLASS[name_index=" + cpdata[index] + "]");
|
||||
}
|
||||
break;
|
||||
case CONSTANT_String:
|
||||
// CONSTANT_String_info { u1 tag; u2 string_index; }
|
||||
if (DEBUG) {
|
||||
cpdata[index] = (int) dis.readShort();
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":STRING[string_index=" + cpdata[index] + "]");
|
||||
}
|
||||
} else {
|
||||
dis.skip(2);
|
||||
}
|
||||
break;
|
||||
case CONSTANT_Fieldref:
|
||||
// CONSTANT_Fieldref_info { u1 tag; u2 class_index; u2 name_and_type_index; }
|
||||
if (DEBUG) {
|
||||
cpdata[index] = new int[] { dis.readShort(), dis.readShort() };
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":FIELDREF[class_index=" + ((int[]) cpdata[index])[0] + ",name_and_type_index="
|
||||
+ ((int[]) cpdata[index])[1] + "]");
|
||||
}
|
||||
} else {
|
||||
dis.skip(4);
|
||||
}
|
||||
break;
|
||||
case CONSTANT_Methodref:
|
||||
// CONSTANT_Methodref_info { u1 tag; u2 class_index; u2 name_and_type_index; }
|
||||
type[index] = b;
|
||||
//if (DEBUG) {
|
||||
cpdata[index] = new int[] { dis.readShort(), dis.readShort() };
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":METHODREF[class_index=" + ((int[]) cpdata[index])[0] + ",name_and_type_index="
|
||||
+ ((int[]) cpdata[index])[1] + "]");
|
||||
}
|
||||
// } else {
|
||||
// dis.skip(4);
|
||||
// }
|
||||
break;
|
||||
case CONSTANT_InterfaceMethodref:
|
||||
// CONSTANT_InterfaceMethodref_info {
|
||||
// u1 tag;
|
||||
// u2 class_index;
|
||||
// u2 name_and_type_index;
|
||||
// }
|
||||
if (DEBUG) {
|
||||
cpdata[index] = new int[] { dis.readShort(), dis.readShort() };
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":INTERFACEMETHODREF[class_index=" + ((int[]) cpdata[index])[0]
|
||||
+ ",name_and_type_index=" + ((int[]) cpdata[index])[1] + "]");
|
||||
}
|
||||
} else {
|
||||
dis.skip(4);
|
||||
}
|
||||
break;
|
||||
case CONSTANT_NameAndType:
|
||||
// The CONSTANT_NameAndType_info structure is used to represent a field or method, without indicating which class or interface type it belongs to:
|
||||
// CONSTANT_NameAndType_info { u1 tag; u2 name_index; u2 descriptor_index; }
|
||||
// type[index] = b;
|
||||
cpdata[index] = (int) dis.readShort();// new int[] { dis.readShort(), dis.readShort() };
|
||||
dis.skip(2); // skip the descriptor for now
|
||||
if (DEBUG) {
|
||||
System.out.println(index + ":NAMEANDTYPE[name_index=" + ((int[]) cpdata[index])[0] + ",descriptor_index="
|
||||
+ ((int[]) cpdata[index])[1] + "]");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Entry: " + index + " " + Byte.toString(b));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
/**
|
||||
* Common constants used throughout Spring Loaded.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public interface Constants extends Opcodes {
|
||||
|
||||
public static final Integer DEFAULT_INT = Integer.valueOf(0);
|
||||
public static final Byte DEFAULT_BYTE = Byte.valueOf((byte) 0);
|
||||
public static final Character DEFAULT_CHAR = Character.valueOf((char) 0);
|
||||
public static final Short DEFAULT_SHORT = Short.valueOf((short) 0);
|
||||
public static final Long DEFAULT_LONG = Long.valueOf(0);
|
||||
public static final Float DEFAULT_FLOAT = Float.valueOf(0);
|
||||
public static final Double DEFAULT_DOUBLE = Double.valueOf(0);
|
||||
public static final Boolean DEFAULT_BOOLEAN = Boolean.FALSE;
|
||||
|
||||
static String magicDescriptorForGeneratedCtors = "org.springsource.loaded.C";
|
||||
|
||||
// TODO change r$ to _sl or sl throughout?
|
||||
static String PREFIX = "r$";
|
||||
|
||||
static String tRegistryType = "org/springsource/loaded/TypeRegistry";
|
||||
static String lRegistryType = "L" + tRegistryType + ";";
|
||||
|
||||
static String tDynamicallyDispatchable = "org/springsource/loaded/__DynamicallyDispatchable";
|
||||
static String lDynamicallyDispatchable = "L" + tDynamicallyDispatchable + ";";
|
||||
|
||||
static String tReloadableType = "org/springsource/loaded/ReloadableType";
|
||||
static String lReloadableType = "L" + tReloadableType + ";";
|
||||
|
||||
static String tInstanceStateManager = "org/springsource/loaded/ISMgr";
|
||||
static String lInstanceStateManager = "L" + tInstanceStateManager + ";";
|
||||
|
||||
static String tStaticStateManager = "org/springsource/loaded/SSMgr";
|
||||
static String lStaticStateManager = "L" + tStaticStateManager + ";";
|
||||
|
||||
static String fReloadableTypeFieldName = PREFIX + "type";
|
||||
|
||||
// Static field holding map and accessors
|
||||
static String fStaticFieldsName = PREFIX + "sfields";
|
||||
static String mStaticFieldSetterName = PREFIX + "sets";
|
||||
static String mStaticFieldSetterDescriptor = "(Ljava/lang/Object;Ljava/lang/String;)V";
|
||||
static String mStaticFieldGetterName = PREFIX + "gets";
|
||||
|
||||
// Instance field holding map and accessors
|
||||
static String fInstanceFieldsName = PREFIX + "fields";
|
||||
static String mInstanceFieldSetterName = PREFIX + "set";
|
||||
static String mInstanceFieldSetterDescriptor = "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/String;)V";
|
||||
static String mInstanceFieldGetterName = PREFIX + "get";
|
||||
static String mInstanceFieldGetterDescriptor = "(Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;";
|
||||
|
||||
static String mStaticFieldInterceptionRequired = "staticFieldInterceptionRequired";
|
||||
static String mInstanceFieldInterceptionRequired = "instanceFieldInterceptionRequired";
|
||||
// method called to see if the target of what is about to be called has changed
|
||||
static String mChangedForInvocationName = "anyChanges";
|
||||
static String mChangedForInvokeStaticName = "istcheck";
|
||||
static String mChangedForInvokeInterfaceName = "iincheck";
|
||||
static String mChangedForInvokeVirtualName = "ivicheck";
|
||||
static String mChangedForInvokeSpecialName = "ispcheck";
|
||||
static String descriptorChangedForInvokeSpecialName = "(ILjava/lang/String;)Lorg/springsource/loaded/__DynamicallyDispatchable;";
|
||||
static String mChangedForConstructorName = "ccheck";
|
||||
|
||||
static int WAS_INVOKESTATIC = 0x0001;
|
||||
static int WAS_INVOKEVIRTUAL = 0x0002;
|
||||
|
||||
// Dynamic dispatch method
|
||||
static String mDynamicDispatchName = "__execute";
|
||||
static String mDynamicDispatchDescriptor = "([Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;";
|
||||
static String mInitializerName = "___init___";
|
||||
static String mStaticInitializerName = "___clinit___";
|
||||
|
||||
static int ACC_PUBLIC_ABSTRACT = Opcodes.ACC_PUBLIC | Opcodes.ACC_ABSTRACT;
|
||||
static int ACC_PRIVATE_STATIC = Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC;
|
||||
static int ACC_PUBLIC_STATIC = Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC;
|
||||
static int ACC_PUBLIC_STATIC_FINAL = Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL;
|
||||
static int ACC_PUBLIC_INTERFACE = Opcodes.ACC_PUBLIC | Opcodes.ACC_INTERFACE | Opcodes.ACC_ABSTRACT;
|
||||
static int ACC_PUBLIC_STATIC_SYNTHETIC = Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC;
|
||||
static int ACC_PUBLIC_SYNTHETIC = Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC;
|
||||
|
||||
static int ACC_PUBLIC_PROTECTED = Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED;
|
||||
static int ACC_PUBLIC_PRIVATE_PROTECTED = Opcodes.ACC_PUBLIC | Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED;
|
||||
static int ACC_PRIVATE_PROTECTED = Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED;
|
||||
|
||||
static int ACC_PRIVATE_STATIC_FINAL = ACC_FINAL | ACC_STATIC | ACC_PRIVATE;
|
||||
|
||||
static String[] NO_STRINGS = new String[0];
|
||||
static Method[] NO_METHODS = new Method[0];
|
||||
static Field[] NO_FIELDS = new Field[0];
|
||||
|
||||
//Name pattern used to recognise names of Executor classes.
|
||||
static Pattern executorClassNamePattern = Pattern.compile("\\$\\$E[0-9,a-z,A-Z]+$");
|
||||
|
||||
static final String jlObject = "java/lang/Object";
|
||||
|
||||
//
|
||||
public static int JLC_GETDECLAREDFIELDS = 0x0001;
|
||||
public static int JLC_GETDECLAREDFIELD = 0x0002;
|
||||
public static int JLC_GETFIELD = 0x0004;
|
||||
public static int JLC_GETDECLAREDMETHODS = 0x0008;
|
||||
public static int JLC_GETDECLAREDMETHOD = 0x0010;
|
||||
public static int JLC_GETMETHOD = 0x0020;
|
||||
public static int JLC_GETDECLAREDCONSTRUCTOR = 0x0040;
|
||||
public static int JLC_GETMODIFIERS = 0x0080;
|
||||
public static int JLC_GETMETHODS = 0x0100;
|
||||
public static int JLC_GETCONSTRUCTOR = 0x0200;
|
||||
|
||||
// For rewritten reflection in system classes, these are used:
|
||||
static final String jlcgdfs = "__sljlcgdfs";
|
||||
static final String jlcgdfsDescriptor = "(Ljava/lang/Class;)[Ljava/lang/reflect/Field;";
|
||||
static final String jlcgdf = "__sljlcgdf";
|
||||
static final String jlcgdfDescriptor = "(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/reflect/Field;";
|
||||
static final String jlcgf = "__sljlcgf";
|
||||
static final String jlcgfDescriptor = "(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/reflect/Field;";
|
||||
static final String jlcgdms = "__sljlcgdms";
|
||||
static final String jlcgdmsDescriptor = "(Ljava/lang/Class;)[Ljava/lang/reflect/Method;";
|
||||
static final String jlcgdm = "__sljlcgdm";
|
||||
static final String jlcgdmDescriptor = "(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;";
|
||||
static final String jlcgm = "__sljlcgm";
|
||||
static final String jlcgmDescriptor = "(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;";
|
||||
static final String jlcgdc = "__sljlcgdc";
|
||||
static final String jlcgdcDescriptor = "(Ljava/lang/Class;[Ljava/lang/Class;)Ljava/lang/reflect/Constructor;";
|
||||
static final String jlcgc = "__sljlcgc";
|
||||
static final String jlcgcDescriptor = "(Ljava/lang/Class;[Ljava/lang/Class;)Ljava/lang/reflect/Constructor;";
|
||||
static final String jlcgmods = "__sljlcgmods";
|
||||
static final String jlcgmodsDescriptor = "(Ljava/lang/Class;)I";
|
||||
static final String jlcgms = "__sljlcgms";
|
||||
static final String jlcgmsDescriptor = "(Ljava/lang/Class;)[Ljava/lang/reflect/Method;";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
import org.objectweb.asm.Label;
|
||||
import org.objectweb.asm.MethodAdapter;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
|
||||
/**
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
class ConstructorCopier extends MethodAdapter implements Constants {
|
||||
|
||||
private final static int preInvokeSpecial = 0;
|
||||
private final static int postInvokeSpecial = 1;
|
||||
|
||||
// It is important to know when an INVOKESPECIAL is hit, whether it is our actual one that delegates to the super or just
|
||||
// one being invoked due to some early object construction prior to the real INVOKESPECIAL running. By tracking
|
||||
// how many unitialized objects there are (count the NEWs) and how many INVOKESPECIALs have occurred, it is possible
|
||||
// to identify the right one.
|
||||
private int state = preInvokeSpecial;
|
||||
private int unitializedObjectsCount = 0;
|
||||
private TypeDescriptor typeDescriptor;
|
||||
private String suffix;
|
||||
private String classname;
|
||||
|
||||
public ConstructorCopier(MethodVisitor mv, TypeDescriptor typeDescriptor, String suffix, String classname) {
|
||||
super(mv);
|
||||
this.typeDescriptor = typeDescriptor;
|
||||
this.suffix = suffix;
|
||||
this.classname = classname;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {
|
||||
// Rename 'this' to 'thiz' in executor otherwise Eclipse debugger will fail (static method with 'this')
|
||||
if (index == 0 && name.equals("this")) {
|
||||
super.visitLocalVariable("thiz", desc, signature, start, end, index);
|
||||
} else {
|
||||
super.visitLocalVariable(name, desc, signature, start, end, index);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitFieldInsn(final int opcode, final String owner, final String name, final String desc) {
|
||||
super.visitFieldInsn(opcode, owner, name, desc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitTypeInsn(final int opcode, final String type) {
|
||||
if (opcode == NEW) {
|
||||
unitializedObjectsCount++;
|
||||
}
|
||||
super.visitTypeInsn(opcode, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc) {
|
||||
// If this is an invokespecial, first determine if it is the one of interest (the one calling our super constructor)
|
||||
if (opcode == INVOKESPECIAL && name.charAt(0) == '<') {
|
||||
if (unitializedObjectsCount != 0) {
|
||||
unitializedObjectsCount--;
|
||||
} else {
|
||||
// This looks like our INVOKESPECIAL
|
||||
if (state == preInvokeSpecial) {
|
||||
// special case for calling jlObject, do nothing!
|
||||
if (owner.equals("java/lang/Object")) {
|
||||
mv.visitInsn(POP);
|
||||
} else {
|
||||
// Need to replace this INVOKESPECIAL call.
|
||||
String supertypename = typeDescriptor.getSupertypeName();
|
||||
ReloadableType superRtype = typeDescriptor.getReloadableType().getTypeRegistry()
|
||||
.getReloadableSuperType(supertypename);
|
||||
if (superRtype == null) {
|
||||
// supertype was not reloadable. This either means it really isn't (doesn't match what we consider reloadable)
|
||||
// or it just hasn't been loaded yet.
|
||||
// In a real scenario supertypes will get loaded first always and this can't happen (the latter case) - it happens in tests
|
||||
// because they don't actively load all their bits and pieces in a hierarchical way. Given that on a reloadable boundary
|
||||
// the magic ctors are setup to call a default ctor, we can assume that above the boundary the object has been initialized.
|
||||
// this means we don't need to call a super __init__ or __execute...
|
||||
|
||||
if (typeDescriptor.getReloadableType().getTypeRegistry().isReloadableTypeName(supertypename)) {
|
||||
superRtype = typeDescriptor.getReloadableType().getTypeRegistry()
|
||||
.getReloadableSuperType(supertypename);
|
||||
throw new IllegalStateException("The supertype " + supertypename.replace('/', '.')
|
||||
+ " has not been loaded as a reloadabletype");
|
||||
}
|
||||
Utils.insertPopsForAllParameters(mv, desc);
|
||||
mv.visitInsn(POP); // pop 'this'
|
||||
} else {
|
||||
// Check the original form of the supertype for a constructor to call
|
||||
MethodMember existingCtor = (superRtype == null ? null : superRtype.getTypeDescriptor().getConstructor(
|
||||
desc));
|
||||
if (existingCtor == null) {
|
||||
// It did not exist in the original supertype version, need to use dynamic dispatch method
|
||||
// collapse the arguments on the stack
|
||||
Utils.collapseStackToArray(mv, desc);
|
||||
// now the stack is the instance then the params
|
||||
mv.visitInsn(SWAP);
|
||||
mv.visitInsn(DUP_X1);
|
||||
// no stack is instance then params then instance
|
||||
mv.visitLdcInsn("<init>" + desc);
|
||||
mv.visitMethodInsn(INVOKESPECIAL, typeDescriptor.getSupertypeName(), mDynamicDispatchName,
|
||||
mDynamicDispatchDescriptor);
|
||||
mv.visitInsn(POP);
|
||||
} else {
|
||||
// it did exist in the original, so there will be parallel constructor
|
||||
mv.visitMethodInsn(INVOKESPECIAL, typeDescriptor.getSupertypeName(), mInitializerName, desc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state = postInvokeSpecial;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Is it a private method call?
|
||||
// TODO r$ check here because we use invokespecial to avoid virtual dispatch on field changes...
|
||||
if (opcode == INVOKESPECIAL && name.charAt(0) != '<' && owner.equals(classname) && !name.startsWith("r$")) {
|
||||
// leaving the invokespecial alone will cause a verify error
|
||||
String descriptor = Utils.insertExtraParameter(owner, desc);
|
||||
super.visitMethodInsn(INVOKESTATIC, Utils.getExecutorName(classname, suffix), name, descriptor);
|
||||
} else {
|
||||
boolean done = false;
|
||||
// TODO dup of code in method copier - can we refactor?
|
||||
if (opcode == INVOKESTATIC) {
|
||||
MethodMember mm = typeDescriptor.getByDescriptor(name, desc);
|
||||
if (mm != null && mm.isPrivate()) {
|
||||
super.visitMethodInsn(INVOKESTATIC, Utils.getExecutorName(classname, suffix), name, desc);
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
if (!done) {
|
||||
super.visitMethodInsn(opcode, owner, name, desc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.objectweb.asm.Type;
|
||||
import org.objectweb.asm.tree.MethodNode;
|
||||
|
||||
/**
|
||||
* Captures the information about the reloaded parts of a type that vary each time a new version is loaded.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class CurrentLiveVersion {
|
||||
|
||||
private static Logger log = Logger.getLogger(CurrentLiveVersion.class.getName());
|
||||
|
||||
// Which reloadable type this represents the live version of
|
||||
final ReloadableType reloadableType;
|
||||
|
||||
// Type descriptor for this live version
|
||||
final TypeDescriptor typeDescriptor;
|
||||
|
||||
// 'stamp' (i.e. suffix) for this version
|
||||
final String versionstamp;
|
||||
|
||||
public final IncrementalTypeDescriptor incrementalTypeDescriptor;
|
||||
|
||||
String dispatcherName;
|
||||
byte[] dispatcher;
|
||||
Class<?> dispatcherClass;
|
||||
Object dispatcherInstance;
|
||||
|
||||
String executorName;
|
||||
byte[] executor;
|
||||
Class<?> executorClass;
|
||||
|
||||
TypeDelta typeDelta;
|
||||
|
||||
private Method staticInitializer;
|
||||
private boolean haveLookedForStaticInitializer;
|
||||
|
||||
public boolean staticInitializedNeedsRerunningOnDefine = false;
|
||||
|
||||
public CurrentLiveVersion(ReloadableType reloadableType, String versionstamp, byte[] newbytedata) {
|
||||
if (GlobalConfiguration.logging && log.isLoggable(Level.FINER)) {
|
||||
log.entering("CurrentLiveVersion", "<init>", " new version of " + reloadableType.getName() + " loaded, version stamp '"
|
||||
+ versionstamp + "'");
|
||||
}
|
||||
this.reloadableType = reloadableType;
|
||||
this.typeDescriptor = reloadableType.getTypeRegistry().getExtractor().extract(newbytedata, true);
|
||||
this.versionstamp = versionstamp;
|
||||
|
||||
if (GlobalConfiguration.assertsOn) {
|
||||
if (!this.typeDescriptor.getName().equals(reloadableType.typedescriptor.getName())) {
|
||||
throw new IllegalStateException("New version has wrong name. Expected " + reloadableType.typedescriptor.getName()
|
||||
+ " but was " + typeDescriptor.getName());
|
||||
}
|
||||
}
|
||||
|
||||
newbytedata = GlobalConfiguration.callsideRewritingOn ? MethodInvokerRewriter.rewrite(reloadableType.typeRegistry,
|
||||
newbytedata) : newbytedata;
|
||||
|
||||
this.incrementalTypeDescriptor = new IncrementalTypeDescriptor(reloadableType.typedescriptor);
|
||||
this.incrementalTypeDescriptor.setLatestTypeDescriptor(this.typeDescriptor);
|
||||
|
||||
// Executors for interfaces simply hold annotations
|
||||
this.executor = reloadableType.getTypeRegistry().executorBuilder.createFor(reloadableType, versionstamp, typeDescriptor,
|
||||
newbytedata);
|
||||
|
||||
if (GlobalConfiguration.classesToDump != null
|
||||
&& GlobalConfiguration.classesToDump.contains(reloadableType.getSlashedName())) {
|
||||
Utils.dump(Utils.getExecutorName(reloadableType.getName(), versionstamp).replace('.', '/'), this.executor);
|
||||
}
|
||||
if (!typeDescriptor.isInterface()) {
|
||||
this.dispatcherName = Utils.getDispatcherName(reloadableType.getName(), versionstamp);
|
||||
this.executorName = Utils.getExecutorName(reloadableType.getName(), versionstamp);
|
||||
this.dispatcher = DispatcherBuilder.createFor(reloadableType, incrementalTypeDescriptor, versionstamp);
|
||||
}
|
||||
reloadableType.typeRegistry.checkChildClassLoader(reloadableType);
|
||||
define();
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines this version. Called up front but can also be called later if the ChildClassLoader in a type registry is discarded
|
||||
* and recreated.
|
||||
*/
|
||||
public void define() {
|
||||
staticInitializer = null;
|
||||
haveLookedForStaticInitializer = false;
|
||||
if (!typeDescriptor.isInterface()) {
|
||||
try {
|
||||
dispatcherClass = reloadableType.typeRegistry.defineClass(dispatcherName, dispatcher, false);
|
||||
} catch (RuntimeException t) {
|
||||
// TODO check for something strange. something to do with the file detection misbehaving, see the same file attempted to be reloaded twice...
|
||||
if (t.getMessage().indexOf("duplicate class definition") == -1) {
|
||||
throw t;
|
||||
} else {
|
||||
t.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
executorClass = reloadableType.typeRegistry.defineClass(executorName, executor, false);
|
||||
} catch (RuntimeException t) {
|
||||
// TODO check for something strange. something to do with the file detection misbehaving, see the same file attempted to be reloaded twice...
|
||||
if (t.getMessage().indexOf("duplicate class definition") == -1) {
|
||||
throw t;
|
||||
} else {
|
||||
t.printStackTrace();
|
||||
}
|
||||
}
|
||||
if (!typeDescriptor.isInterface()) {
|
||||
try {
|
||||
dispatcherInstance = dispatcherClass.newInstance();
|
||||
} catch (InstantiationException e) {
|
||||
throw new RuntimeException("Unable to build dispatcher class instance", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Unable to build dispatcher class instance", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public MethodMember getReloadableMethod(String name, String descriptor) {
|
||||
// Look through the methods on the latest loaded version and find the method we want
|
||||
MethodMember[] methods = incrementalTypeDescriptor.getLatestTypeDescriptor().getMethods();
|
||||
for (MethodMember rmethod : methods) {
|
||||
if (rmethod.getName().equals(name)) {
|
||||
if (descriptor.equals(rmethod.getDescriptor())) {
|
||||
return rmethod;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO should be caching the result in the MethodMember objects for speed
|
||||
public Method getExecutorMethod(MethodMember methodMember) {
|
||||
String executorDescriptor;
|
||||
String name;
|
||||
|
||||
//What to search for:
|
||||
if (methodMember.isConstructor()) {
|
||||
name = Constants.mInitializerName;
|
||||
} else {
|
||||
name = methodMember.getName();
|
||||
}
|
||||
executorDescriptor = getExecutorDescriptor(methodMember);
|
||||
|
||||
//Search for it:
|
||||
if (executorClass != null) {
|
||||
Method[] executorMethods = executorClass.getDeclaredMethods();
|
||||
for (Method executor : executorMethods) {
|
||||
if (executor.getName().equals(name) && Type.getMethodDescriptor(executor).equals(executorDescriptor)) {
|
||||
return executor;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getExecutorDescriptor(MethodMember methodMember) {
|
||||
Type[] params = Type.getArgumentTypes(methodMember.getDescriptor());
|
||||
Type[] newParametersArray = params;
|
||||
if (!methodMember.isStatic()) {
|
||||
newParametersArray = new Type[params.length + 1];
|
||||
System.arraycopy(params, 0, newParametersArray, 1, params.length);
|
||||
newParametersArray[0] = Type.getType(reloadableType.getClazz());
|
||||
}
|
||||
String executorDescriptor = Type.getMethodDescriptor(Type.getReturnType(methodMember.getDescriptor()), newParametersArray);
|
||||
return executorDescriptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CurrentLiveVersion [reloadableType=" + reloadableType + ", typeDescriptor=" + typeDescriptor + ", versionstamp="
|
||||
+ versionstamp + ", dispatcherName=" + dispatcherName + ", executorName=" + executorName + "]";
|
||||
}
|
||||
|
||||
public Class<?> getExecutorClass() {
|
||||
return executorClass;
|
||||
}
|
||||
|
||||
public String getVersionStamp() {
|
||||
return versionstamp;
|
||||
}
|
||||
|
||||
public Field getExecutorField(String name) throws SecurityException, NoSuchFieldException {
|
||||
return executorClass.getDeclaredField(name);
|
||||
}
|
||||
|
||||
public TypeDelta getTypeDelta() {
|
||||
return typeDelta;
|
||||
}
|
||||
|
||||
public void setTypeDelta(TypeDelta td) {
|
||||
typeDelta = td;
|
||||
}
|
||||
|
||||
public boolean hasClinit() {
|
||||
return typeDescriptor.hasClinit();
|
||||
}
|
||||
|
||||
public boolean hasConstructorChanged(String descriptor) {
|
||||
MethodMember mm = typeDescriptor.getConstructor(descriptor);
|
||||
return hasConstructorChanged(mm);
|
||||
}
|
||||
|
||||
public boolean hasConstructorChanged(MethodMember mm) {
|
||||
if (mm == null) {
|
||||
return true;
|
||||
}
|
||||
// need to look at the delta
|
||||
if (typeDelta.haveMethodsChangedOrBeenAddedOrRemoved()) {
|
||||
if (typeDelta.haveMethodsChanged()) {
|
||||
MethodDelta md = typeDelta.changedMethods.get(mm.name + mm.descriptor);
|
||||
if (md != null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (typeDelta.haveMethodsBeenAdded()) {
|
||||
MethodNode mn = typeDelta.brandNewMethods.get(mm.name + mm.descriptor);
|
||||
if (mn != null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (typeDelta.haveMethodsBeenDeleted()) {
|
||||
MethodNode mn = typeDelta.lostMethods.get(mm.name + mm.descriptor);
|
||||
if (mn != null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO can we speed this up?
|
||||
public boolean hasConstructorChanged(int ctorId) {
|
||||
// need to find the constructor that id is for
|
||||
MethodMember mm = typeDescriptor.getConstructor(ctorId);
|
||||
return hasConstructorChanged(mm);
|
||||
}
|
||||
|
||||
public void clearClassloaderLinks() {
|
||||
this.executorClass = null;
|
||||
this.dispatcherClass = null;
|
||||
}
|
||||
|
||||
public void reloadMostRecentDispatcherAndExecutor() {
|
||||
define();
|
||||
}
|
||||
|
||||
public Object getDispatcherInstance() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
public void runStaticInitializer() {
|
||||
if (!haveLookedForStaticInitializer) {
|
||||
try {
|
||||
staticInitializer = this.getExecutorClass().getDeclaredMethod(Constants.mStaticInitializerName);
|
||||
} catch (NoSuchMethodException e) {
|
||||
// some types don't have a static initializer, that is OK
|
||||
}
|
||||
haveLookedForStaticInitializer = true;
|
||||
}
|
||||
if (staticInitializer != null) {
|
||||
try {
|
||||
staticInitializer.invoke(null);
|
||||
} catch (Exception e) {
|
||||
log.severe("Unexpected exception whilst trying to call the static initializer on " + this.reloadableType.getName());
|
||||
e.printStackTrace(); // TODO remove when happy
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,329 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.objectweb.asm.AnnotationVisitor;
|
||||
import org.objectweb.asm.Attribute;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.ClassVisitor;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.FieldVisitor;
|
||||
import org.objectweb.asm.Label;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.springsource.loaded.Utils.ReturnType;
|
||||
|
||||
|
||||
/**
|
||||
* Builder that creates the dispatcher. The dispatcher is the implementation of the interface extracted for a type which then
|
||||
* delegates to the executor. A new dispatcher (and executor) is built for each class reload.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class DispatcherBuilder {
|
||||
|
||||
/**
|
||||
* Factory method that builds the dispatcher for a specified reloadabletype.
|
||||
*
|
||||
* @param rtype the reloadable type
|
||||
* @param newVersionTypeDescriptor the descriptor of the new version (the executor will be generated according to this)
|
||||
* @param versionstamp the suffix that should be appended to the generated dispatcher
|
||||
* @return the bytecode for the new dispatcher
|
||||
*/
|
||||
public static byte[] createFor(ReloadableType rtype, IncrementalTypeDescriptor newVersionTypeDescriptor, String versionstamp) {
|
||||
ClassReader fileReader = new ClassReader(rtype.interfaceBytes);
|
||||
DispatcherBuilderVisitor dispatcherVisitor = new DispatcherBuilderVisitor(rtype, newVersionTypeDescriptor, versionstamp);
|
||||
fileReader.accept(dispatcherVisitor, 0);
|
||||
return dispatcherVisitor.getBytes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whilst visiting the interface, the implementation is created.
|
||||
*/
|
||||
static class DispatcherBuilderVisitor implements ClassVisitor, Opcodes, Constants {
|
||||
|
||||
private ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
|
||||
|
||||
private String classname;
|
||||
private String executorClassName;
|
||||
private String suffix;
|
||||
private ReloadableType rtype;
|
||||
private IncrementalTypeDescriptor typeDescriptor;
|
||||
|
||||
public DispatcherBuilderVisitor(ReloadableType rtype, IncrementalTypeDescriptor typeDescriptor, String suffix) {
|
||||
this.classname = rtype.getSlashedName();
|
||||
this.typeDescriptor = typeDescriptor;
|
||||
this.suffix = suffix;
|
||||
this.rtype = rtype;
|
||||
this.executorClassName = Utils.getExecutorName(classname, suffix);
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
return cw.toByteArray();
|
||||
}
|
||||
|
||||
public void visit(int version, int flags, String name, String signature, String superclassName, String[] interfaceNames) {
|
||||
String dispatcherName = Utils.getDispatcherName(classname, suffix);
|
||||
cw.visit(version, Opcodes.ACC_PUBLIC, dispatcherName, null, "java/lang/Object",
|
||||
new String[] { Utils.getInterfaceName(classname), "org/springsource/loaded/__DynamicallyDispatchable" });
|
||||
generateDefaultConstructor();
|
||||
}
|
||||
|
||||
private void generateDefaultConstructor() {
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
|
||||
mv.visitCode();
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
|
||||
mv.visitInsn(RETURN);
|
||||
mv.visitMaxs(1, 1);
|
||||
mv.visitEnd();
|
||||
}
|
||||
|
||||
private void generateClinitDispatcher() {
|
||||
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, mStaticInitializerName, "()V", null, null);
|
||||
mv.visitCode();
|
||||
mv.visitMethodInsn(INVOKESTATIC, executorClassName, mStaticInitializerName, "()V");
|
||||
mv.visitInsn(RETURN);
|
||||
mv.visitMaxs(1, 1);
|
||||
mv.visitEnd();
|
||||
}
|
||||
|
||||
public AnnotationVisitor visitAnnotation(String arg0, boolean arg1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void visitAttribute(Attribute arg0) {
|
||||
}
|
||||
|
||||
public void visitEnd() {
|
||||
}
|
||||
|
||||
public FieldVisitor visitField(int arg0, String arg1, String arg2, String arg3, Object arg4) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void visitInnerClass(String arg0, String arg1, String arg2, int arg3) {
|
||||
}
|
||||
|
||||
public MethodVisitor visitMethod(int flags, String name, String descriptor, String signature, String[] exceptions) {
|
||||
if (name.equals(mDynamicDispatchName)) {
|
||||
generateDynamicDispatchMethod(name, descriptor, signature, exceptions);
|
||||
} else if (!name.equals("<init>")) {
|
||||
generateRegularMethod(name, descriptor, signature, exceptions);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the body of the dynamic dispatcher method. This method is responsible for calling all the methods that are added
|
||||
* to a type after the first time it is defined.
|
||||
*/
|
||||
private void generateDynamicDispatchMethod(String name, String descriptor, String signature, String[] exceptions) {
|
||||
final int indexDispatcherInstance = 0;
|
||||
final int indexArgs = 1;
|
||||
final int indexTarget = 2;
|
||||
final int indexNameAndDescriptor = 3;
|
||||
|
||||
// Should be generating the code for each additional method in
|
||||
// the executor (new version) that wasn't in the original.
|
||||
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, name, descriptor, signature, exceptions);
|
||||
mv.visitCode();
|
||||
|
||||
// Entries required here for all methods that exist in the new version but didn't exist in the original version
|
||||
// There should be no entries for catchers
|
||||
|
||||
int maxStack = 0;
|
||||
// Basically generate a long if..else sequence for each method
|
||||
List<MethodMember> methods = new ArrayList<MethodMember>(typeDescriptor.getNewOrChangedMethods());
|
||||
|
||||
// these are added because we may be calling through the dynamic dispatcher if calling from an invokeinterface - the invokeinterface
|
||||
// will call __execute on the interface, which is then implemented by the real class - but it may be that the
|
||||
// actual type implementing the interface already implements that method - if the dispatcher doesn't recognize
|
||||
// it then we may go bang
|
||||
|
||||
// System.out.println("Generating __execute in type " + classname);
|
||||
for (MethodMember m : typeDescriptor.getOriginal().getMethods()) {
|
||||
methods.add(m);
|
||||
}
|
||||
|
||||
for (MethodMember method : methods) {
|
||||
if (MethodMember.isCatcher(method)) { // for reason above, may also need to consider catchers here - what if an interface is changed to add a toString() method, for example
|
||||
continue;
|
||||
// would the implementation for a catcher call the super catcher?
|
||||
}
|
||||
// System.out.println("Generating handler for " + method.name);
|
||||
String nameWithDescriptor = new StringBuilder(method.name).append(method.descriptor).toString();
|
||||
|
||||
// 2. Load the input name+descriptor and compare it with this method:
|
||||
mv.visitVarInsn(ALOAD, 3);
|
||||
mv.visitLdcInsn(nameWithDescriptor);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z");
|
||||
Label label = new Label();
|
||||
mv.visitJumpInsn(IFEQ, label); // means if false
|
||||
|
||||
// 3. Generate the code that will call the method on the executor:
|
||||
if (!method.isStatic()) {
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 2);
|
||||
mv.visitTypeInsn(CHECKCAST, classname);
|
||||
}
|
||||
String callDescriptor = method.isStatic() ? method.descriptor : Utils.insertExtraParameter(classname,
|
||||
method.descriptor);
|
||||
|
||||
int pcount = Utils.getParameterCount(method.descriptor);
|
||||
if (pcount > maxStack) {
|
||||
pcount = maxStack;
|
||||
}
|
||||
|
||||
// 4. Unpack parameter array to fit the descriptor for that method
|
||||
Utils.generateInstructionsToUnpackArrayAccordingToDescriptor(mv, method.descriptor, 1);
|
||||
|
||||
ReturnType returnType = Utils.getReturnTypeDescriptor(method.descriptor);
|
||||
mv.visitMethodInsn(Opcodes.INVOKESTATIC, executorClassName, method.name, callDescriptor);
|
||||
if (returnType.isVoid()) {
|
||||
mv.visitInsn(ACONST_NULL);
|
||||
} else if (returnType.isPrimitive()) {
|
||||
Utils.insertBoxInsns(mv, returnType.descriptor);
|
||||
}
|
||||
mv.visitInsn(Opcodes.ARETURN);
|
||||
mv.visitLabel(label);
|
||||
}
|
||||
for (MethodMember ctor : typeDescriptor.getLatestTypeDescriptor().getConstructors()) {
|
||||
String nameWithDescriptor = new StringBuilder(ctor.name).append(ctor.descriptor).toString();
|
||||
|
||||
// 2. Load the input name+descriptor and compare it with this method:
|
||||
// if (nameAndDescriptor.equals(xxx)) {
|
||||
mv.visitVarInsn(ALOAD, indexNameAndDescriptor);
|
||||
mv.visitLdcInsn(nameWithDescriptor);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z");
|
||||
Label label = new Label();
|
||||
mv.visitJumpInsn(IFEQ, label); // means if false
|
||||
|
||||
// 3. Generate the code that will call the method on the executor:
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 2);
|
||||
mv.visitTypeInsn(CHECKCAST, classname);
|
||||
String callDescriptor = Utils.insertExtraParameter(classname, ctor.descriptor);
|
||||
|
||||
int pcount = Utils.getParameterCount(ctor.descriptor);
|
||||
if (pcount > maxStack) {
|
||||
pcount = maxStack;
|
||||
}
|
||||
|
||||
// 4. Unpack parameter array to fit the descriptor for that method
|
||||
Utils.generateInstructionsToUnpackArrayAccordingToDescriptor(mv, ctor.descriptor, 1);
|
||||
|
||||
// ReturnType returnType = Utils.getReturnTypeDescriptor(method.descriptor);
|
||||
mv.visitMethodInsn(Opcodes.INVOKESTATIC, executorClassName, "___init___", callDescriptor);
|
||||
// if (returnType.isVoid()) {
|
||||
mv.visitInsn(ACONST_NULL);
|
||||
// } else if (returnType.isPrimitive()) {
|
||||
// Utils.insertBoxInsns(mv, returnType.descriptor);
|
||||
// }
|
||||
mv.visitInsn(Opcodes.ARETURN);
|
||||
mv.visitLabel(label);
|
||||
}
|
||||
|
||||
// 5. Throw exception as dynamic dispatcher has been called for something it shouldn't have
|
||||
|
||||
// At this point we failed to find it as a method we can dispatch to our executor, so we want
|
||||
// to pass it 'up' to our supertype. We need to get the dispatcher for our superclass
|
||||
// and then call the __execute() on it, assuming that it will be able to handle this request.
|
||||
|
||||
// alternative 1: use the dispatcher for the superclass
|
||||
|
||||
// Determine the supertype
|
||||
String slashedSupertypeName = rtype.getTypeDescriptor().getSupertypeName();
|
||||
|
||||
// getDispatcher will give us the dispatcher for the supertype
|
||||
mv.visitFieldInsn(Opcodes.GETSTATIC, slashedSupertypeName, fReloadableTypeFieldName, lReloadableType);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, tReloadableType, "getDispatcher",
|
||||
"()Lorg/springsource/loaded/__DynamicallyDispatchable;");
|
||||
|
||||
// alternative 2: find the right dispatcher - i.e. who in the super hierarchy provides that nameAndDescriptor
|
||||
|
||||
// now invoke the dynamic dispatch call on that dispatcher
|
||||
mv.visitVarInsn(ALOAD, indexArgs);
|
||||
mv.visitVarInsn(ALOAD, indexTarget);
|
||||
mv.visitVarInsn(ALOAD, indexNameAndDescriptor);
|
||||
mv.visitMethodInsn(INVOKEINTERFACE, tDynamicallyDispatchable, mDynamicDispatchName, mDynamicDispatchDescriptor);
|
||||
mv.visitInsn(ARETURN);
|
||||
|
||||
// mv.visitTypeInsn(NEW, "java/lang/IllegalStateException");
|
||||
// mv.visitInsn(DUP);
|
||||
// mv.visitVarInsn(ALOAD, 3);
|
||||
// mv.visitMethodInsn(INVOKESPECIAL, "java/lang/IllegalStateException", "<init>", "(Ljava/lang/String;)V");
|
||||
// mv.visitInsn(ATHROW);
|
||||
mv.visitMaxs(maxStack, 6);
|
||||
mv.visitEnd();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to generate the implementation of a normal method on the interface - a normal method is one that did exist when
|
||||
* the type was first defined. Might be a catcher.
|
||||
*/
|
||||
private void generateRegularMethod(String name, String descriptor, String signature, String[] exceptions) {
|
||||
// The original descriptor is how it was defined on the original type and how it is defined in the executor class.
|
||||
// The original descriptor is this descriptor with the first parameter trimmed off.
|
||||
boolean isClinit = name.equals("___clinit___");
|
||||
String originalDescriptor = isClinit ? descriptor : Utils.stripFirstParameter(descriptor);
|
||||
MethodMember method = null;
|
||||
|
||||
// Detect if the name has been modified for clash avoidance reasons
|
||||
if (name.equals("___init___")) {
|
||||
// it is a ctor
|
||||
method = rtype.getConstructor(originalDescriptor);
|
||||
} else {
|
||||
if (isClinit) {
|
||||
generateClinitDispatcher();
|
||||
return;
|
||||
} else {
|
||||
// TODO need a better solution that these __
|
||||
if (name.startsWith("__") && !name.equals("__$swapInit")) { // __$swapInit is the groovy reset method
|
||||
// clash avoidance name
|
||||
method = rtype.getMethod(name.substring(2), originalDescriptor);
|
||||
} else {
|
||||
method = rtype.getMethod(name, originalDescriptor);
|
||||
}
|
||||
}
|
||||
}
|
||||
boolean isStatic = method.isStatic();
|
||||
|
||||
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, name, descriptor, signature, exceptions);
|
||||
mv.visitCode();
|
||||
// The input descriptor will include the extra initial parameter (the instance, or null for static methods)
|
||||
ReturnType returnTypeDescriptor = Utils.getReturnTypeDescriptor(descriptor);
|
||||
// For a static method the first parameter can be ignored
|
||||
int params = Utils.getParameterCount(descriptor);
|
||||
String callDescriptor = isStatic ? originalDescriptor : descriptor;
|
||||
Utils.createLoadsBasedOnDescriptor(mv, callDescriptor, isStatic ? 2 : 1);
|
||||
mv.visitMethodInsn(INVOKESTATIC, executorClassName, name, callDescriptor);
|
||||
Utils.addCorrectReturnInstruction(mv, returnTypeDescriptor, false);
|
||||
mv.visitMaxs(params, params + 1);
|
||||
mv.visitEnd();
|
||||
}
|
||||
|
||||
public void visitOuterClass(String arg0, String arg1, String arg2) {
|
||||
}
|
||||
|
||||
public void visitSource(String arg0, String arg1) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
import org.objectweb.asm.AnnotationVisitor;
|
||||
import org.objectweb.asm.Attribute;
|
||||
import org.objectweb.asm.ClassVisitor;
|
||||
import org.objectweb.asm.FieldVisitor;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
|
||||
/**
|
||||
* Empty implementation that can be subclassed to pick up default implementations of most methods.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.7.3
|
||||
*/
|
||||
public class EmptyClassVisitor implements ClassVisitor, Constants {
|
||||
|
||||
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
|
||||
}
|
||||
|
||||
public void visitSource(String source, String debug) {
|
||||
}
|
||||
|
||||
public void visitOuterClass(String owner, String name, String desc) {
|
||||
}
|
||||
|
||||
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void visitAttribute(Attribute attr) {
|
||||
}
|
||||
|
||||
public void visitInnerClass(String name, String outerName, String innerName, int access) {
|
||||
}
|
||||
|
||||
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void visitEnd() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
/**
|
||||
* Represents an exact type pattern.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class ExactTypePattern extends TypePattern {
|
||||
|
||||
private String pattern;
|
||||
|
||||
/**
|
||||
* @param pattern type pattern of the form com.foo.Bar
|
||||
*/
|
||||
public ExactTypePattern(String pattern) {
|
||||
this.pattern = pattern;
|
||||
}
|
||||
|
||||
protected boolean internalMatches(String input) {
|
||||
boolean b = input.equals(pattern);
|
||||
return b;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "text:" + pattern;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
import org.objectweb.asm.AnnotationVisitor;
|
||||
import org.objectweb.asm.Attribute;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.ClassVisitor;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.FieldVisitor;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
/**
|
||||
* The executor embodies the new implementation of the type after it has been reloaded.
|
||||
* <p>
|
||||
* The executor is the class full of static methods that looks very like the original class.
|
||||
* <p>
|
||||
* <b>Methods</b>. For each method in the original type we have a method in the executor, it has the same SourceFile attribute and
|
||||
* the same local variable and line number details for debugging to work. Note the first variable will have been renamed from 'this'
|
||||
* to 'thiz' to prevent the eclipse debugger crashing. All annotations from the new version will be copied to the methods on an
|
||||
* executor.
|
||||
* <p>
|
||||
* <b>Fields</b>. Fields are copied into the executor but only so that there is a place to hang the annotations off (so that they
|
||||
* can be accessed through reflection).
|
||||
* <p>
|
||||
* <b>Constructors</b>. Constructors are added to the executor as ___init___ methods, with the invokespecials within them
|
||||
* transformed, either removed if they are calls to Object.<init> or mutated into ___init___ calls on the supertype instance.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class ExecutorBuilder {
|
||||
|
||||
private TypeRegistry typeRegistry;
|
||||
|
||||
ExecutorBuilder(TypeRegistry typeRegistry) {
|
||||
this.typeRegistry = typeRegistry;
|
||||
}
|
||||
|
||||
public byte[] createFor(ReloadableType reloadableType, String versionstamp, TypeDescriptor typeDescriptor, byte[] newVersionData) {
|
||||
if (typeDescriptor == null) {
|
||||
// must be reloadable or we would not be here - so can pass 'true'
|
||||
typeDescriptor = typeRegistry.getExtractor().extract(newVersionData, true);
|
||||
}
|
||||
ClassReader fileReader = new ClassReader(newVersionData);
|
||||
ExecutorBuilderVisitor executorVisitor = new ExecutorBuilderVisitor(reloadableType.getSlashedName(), versionstamp,
|
||||
typeDescriptor);
|
||||
fileReader.accept(executorVisitor, 0);
|
||||
return executorVisitor.getBytes();
|
||||
}
|
||||
|
||||
/**
|
||||
* ClassVisitor that constructs the executor by visiting the original class. The basic goal is to visit the original class and
|
||||
* 'copy' the methods into the executor, making adjustments as we go.
|
||||
*/
|
||||
static class ExecutorBuilderVisitor implements ClassVisitor, Constants {
|
||||
|
||||
private ClassWriter cw = new ClassWriter(0);
|
||||
|
||||
private String classname;
|
||||
private String suffix;
|
||||
protected TypeDescriptor typeDescriptor;
|
||||
|
||||
public ExecutorBuilderVisitor(String classname, String suffix, TypeDescriptor typeDescriptor) {
|
||||
this.classname = classname;
|
||||
this.suffix = suffix;
|
||||
this.typeDescriptor = typeDescriptor;
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
return cw.toByteArray();
|
||||
}
|
||||
|
||||
public void visit(int version, int flags, String name, String signature, String superclassName, String[] interfaceNames) {
|
||||
cw.visit(version, Opcodes.ACC_PUBLIC, Utils.getExecutorName(classname, suffix), null, "java/lang/Object", null);
|
||||
}
|
||||
|
||||
// For type level annotation copying
|
||||
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
|
||||
AnnotationVisitor av = cw.visitAnnotation(desc, visible);
|
||||
return new CopyingAnnotationVisitor(av);
|
||||
}
|
||||
|
||||
// Fields are copied solely to provide a place to hang annotations
|
||||
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
|
||||
return cw.visitField(access, name, desc, signature, value);
|
||||
}
|
||||
|
||||
// For each method, copy it into the new class making appropriate adjustments
|
||||
public MethodVisitor visitMethod(int flags, String name, String descriptor, String signature, String[] exceptions) {
|
||||
if (!Utils.isInitializer(name)) {
|
||||
// method
|
||||
if (!Modifier.isStatic(flags)) {
|
||||
// For non static methods add the extra initial parameter which is 'this'
|
||||
descriptor = Utils.insertExtraParameter(classname, descriptor);
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC_STATIC, name, descriptor, signature, exceptions);
|
||||
return new MethodCopier(mv, typeDescriptor.isInterface(), descriptor, typeDescriptor, classname, suffix);
|
||||
} else {
|
||||
// If this static method would 'clash' with an instance method that has the extra parameter added then
|
||||
// we have a couple of options to make them different:
|
||||
// 1. tweak the name
|
||||
// 2. tweak the parameters
|
||||
MethodMember method = typeDescriptor.getByDescriptor(name, descriptor);
|
||||
if (MethodMember.isClash(method)) {
|
||||
name = "__" + name;
|
||||
}
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC_STATIC, name, descriptor, signature, exceptions);
|
||||
return new MethodCopier(mv, typeDescriptor.isInterface(), descriptor, typeDescriptor, classname, suffix);
|
||||
}
|
||||
} else {
|
||||
// constructor
|
||||
if (name.charAt(1) != 'c') {
|
||||
// regular constructor
|
||||
// want to create the ___init___ handler for this constructor
|
||||
descriptor = Utils.insertExtraParameter(classname, descriptor);
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC_STATIC, mInitializerName, descriptor, signature, exceptions);
|
||||
|
||||
ConstructorCopier cc = new ConstructorCopier(mv, typeDescriptor, suffix, classname);
|
||||
return cc;
|
||||
} else {
|
||||
// static initializer
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC_STATIC, mStaticInitializerName, descriptor, signature, exceptions);
|
||||
return new MethodCopier(mv, typeDescriptor.isInterface(), descriptor, typeDescriptor, classname, suffix);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void visitSource(String sourcefile, String debug) {
|
||||
cw.visitSource(sourcefile, debug);
|
||||
}
|
||||
|
||||
private static class CopyingAnnotationVisitor implements AnnotationVisitor {
|
||||
|
||||
private AnnotationVisitor av;
|
||||
|
||||
public CopyingAnnotationVisitor(AnnotationVisitor av) {
|
||||
this.av = av;
|
||||
}
|
||||
|
||||
public void visit(String name, Object value) {
|
||||
av.visit(name, value);
|
||||
}
|
||||
|
||||
public AnnotationVisitor visitAnnotation(String name, String desc) {
|
||||
AnnotationVisitor localav = av.visitAnnotation(name, desc);
|
||||
return new CopyingAnnotationVisitor(localav);
|
||||
}
|
||||
|
||||
public AnnotationVisitor visitArray(String name) {
|
||||
AnnotationVisitor localav = av.visitArray(name);
|
||||
return new CopyingAnnotationVisitor(localav);
|
||||
}
|
||||
|
||||
public void visitEnd() {
|
||||
|
||||
av.visitEnd();
|
||||
}
|
||||
|
||||
public void visitEnum(String name, String desc, String value) {
|
||||
av.visitEnum(name, desc, value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void visitOuterClass(String arg0, String arg1, String arg2) {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
public void visitAttribute(Attribute attr) {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
public void visitEnd() {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
public void visitInnerClass(String arg0, String arg1, String arg2, int arg3) {
|
||||
// nothing to do
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
/**
|
||||
* Encapsulates what has changed about a field on a reload.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class FieldDelta {
|
||||
public int changed;
|
||||
|
||||
private final static int CHANGED_TYPE = 0x0001;
|
||||
private final static int CHANGED_ACCESS = 0x0002;
|
||||
private final static int CHANGED_ANNOTATIONS = 0x0004;
|
||||
|
||||
private final static int CHANGED_MASK = CHANGED_TYPE | CHANGED_ACCESS | CHANGED_ANNOTATIONS;
|
||||
|
||||
public final String name;
|
||||
// o = original, n = new
|
||||
String oDesc, nDesc;
|
||||
String annotationChanges;
|
||||
int oAccess, nAccess;
|
||||
|
||||
public FieldDelta(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void setTypeChanged(String oldDesc, String newDesc) {
|
||||
this.oDesc = oldDesc;
|
||||
this.nDesc = newDesc;
|
||||
this.changed |= CHANGED_TYPE;
|
||||
}
|
||||
|
||||
public void setAnnotationsChanged(String annotationChanges) {
|
||||
this.annotationChanges = annotationChanges;
|
||||
this.changed |= CHANGED_ANNOTATIONS;
|
||||
}
|
||||
|
||||
public boolean hasAnyChanges() {
|
||||
return (changed & CHANGED_MASK) != 0;
|
||||
}
|
||||
|
||||
public void setAccessChanged(int oldAccess, int newAccess) {
|
||||
this.oAccess = oldAccess;
|
||||
this.nAccess = newAccess;
|
||||
this.changed |= CHANGED_ACCESS;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuilder s = new StringBuilder();
|
||||
s.append("FieldDelta[field:").append(name);
|
||||
if ((changed & CHANGED_TYPE) != 0) {
|
||||
s.append(" type:").append(oDesc).append(">").append(nDesc);
|
||||
}
|
||||
if ((changed & CHANGED_ACCESS) != 0) {
|
||||
s.append(" access:").append(oAccess).append(">").append(nAccess);
|
||||
}
|
||||
if ((changed & CHANGED_ANNOTATIONS) != 0) {
|
||||
s.append(" annotations:").append(annotationChanges);
|
||||
}
|
||||
s.append("]");
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
/**
|
||||
* Describes a field, created during TypeDescriptor construction.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class FieldMember extends AbstractMember {
|
||||
|
||||
final static FieldMember[] NONE = new FieldMember[0];
|
||||
String typename;
|
||||
|
||||
protected FieldMember(String typename, int modifiers, String name, String descriptor, String signature) {
|
||||
super(modifiers, name, descriptor, signature);
|
||||
this.typename = typename;
|
||||
}
|
||||
|
||||
public String getDeclaringTypeName() {
|
||||
return typename;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("0x").append(Integer.toHexString(modifiers));
|
||||
sb.append(" ").append(descriptor).append(" ").append(name);
|
||||
if (signature != null) {
|
||||
sb.append(" [").append(signature).append("]");
|
||||
}
|
||||
return sb.toString().trim();
|
||||
}
|
||||
|
||||
public boolean equals(Object other) {
|
||||
if (!(other instanceof FieldMember)) {
|
||||
return false;
|
||||
}
|
||||
FieldMember o = (FieldMember) other;
|
||||
if (!name.equals(o.name)) {
|
||||
return false;
|
||||
}
|
||||
if (modifiers != o.modifiers) {
|
||||
return false;
|
||||
}
|
||||
if (!descriptor.equals(o.descriptor)) {
|
||||
return false;
|
||||
}
|
||||
if (signature == null && o.signature != null) {
|
||||
return false;
|
||||
}
|
||||
if (signature != null && o.signature == null) {
|
||||
return false;
|
||||
}
|
||||
if (signature != null) {
|
||||
if (!signature.equals(o.signature)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
int result = modifiers;
|
||||
result = result * 37 + name.hashCode();
|
||||
result = result * 37 + descriptor.hashCode();
|
||||
if (signature != null) {
|
||||
result = result * 37 + signature.hashCode();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,464 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Able to read or write a particular field in a type. Knows nothing about the instance upon which the read/write may be getting
|
||||
* done.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class FieldReaderWriter {
|
||||
|
||||
private static Logger log = Logger.getLogger(FieldReaderWriter.class.getName());
|
||||
|
||||
/**
|
||||
* The type descriptor for the type that defines the field we want to access
|
||||
*/
|
||||
protected TypeDescriptor typeDescriptor;
|
||||
|
||||
protected FieldMember theField;
|
||||
|
||||
public FieldReaderWriter(FieldMember theField, TypeDescriptor typeDescriptor) {
|
||||
this.theField = theField;
|
||||
this.typeDescriptor = typeDescriptor;
|
||||
assert theField.typename == typeDescriptor.typename;
|
||||
}
|
||||
|
||||
protected FieldReaderWriter() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of an instance field on the specified instance to the specified value. If a state manager is passed in things
|
||||
* can be done in a more optimal way, otherwise the state manager has to be discovered from the instance.
|
||||
*
|
||||
* @param instance the object instance upon which to set the field
|
||||
* @param newValue the new value for that field
|
||||
* @param the optional state manager for this instance, which will be looked up (expensive) if not passed in
|
||||
*/
|
||||
public void setValue(Object instance, Object newValue, ISMgr stateManager) throws IllegalAccessException {
|
||||
if (typeDescriptor.isReloadable()) {
|
||||
if (stateManager == null) {
|
||||
// Look it up using reflection
|
||||
stateManager = findInstanceStateManager(instance);
|
||||
}
|
||||
String declaringTypeName = typeDescriptor.getName();
|
||||
Map<String, Object> typeLevelValues = stateManager.getMap().get(declaringTypeName);
|
||||
if (typeLevelValues == null) {
|
||||
// first time we've accessed this type for an instance field
|
||||
typeLevelValues = new HashMap<String, Object>();
|
||||
stateManager.getMap().put(declaringTypeName, typeLevelValues);
|
||||
}
|
||||
typeLevelValues.put(theField.getName(), newValue);
|
||||
} else { // the type is not reloadable, must use reflection to access the value
|
||||
// TODO generate get/set in the topmost reloader for these kinds of field and use them?
|
||||
if (typeDescriptor.isInterface()) {
|
||||
// field resolution has left us with an interface field, those can't be set like this
|
||||
throw new IncompatibleClassChangeError("Expected non-static field " + instance.getClass().getName() + "."
|
||||
+ theField.getName());
|
||||
} else {
|
||||
findAndSetFieldValueInHierarchy(instance, newValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setStaticFieldValue(Class<?> clazz, Object newValue, SSMgr stateManager) throws IllegalAccessException {
|
||||
if (clazz == null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
// First decision - is the field part of a reloadable type or not? The typeDescriptor here is the actual owner
|
||||
// of the field, at this point we *know* this class has this field.
|
||||
if (typeDescriptor.isReloadable()) {
|
||||
if (stateManager == null) {
|
||||
// need to go and find it, there *will* be one but it will be slow to retrieve (reflection)
|
||||
stateManager = findStaticStateManager(clazz);
|
||||
}
|
||||
String declaringTypeName = typeDescriptor.getName();
|
||||
Map<String, Object> typeLevelValues = stateManager.getMap().get(declaringTypeName);
|
||||
if (typeLevelValues == null) {
|
||||
typeLevelValues = new HashMap<String, Object>();
|
||||
stateManager.getMap().put(declaringTypeName, typeLevelValues);
|
||||
}
|
||||
typeLevelValues.put(theField.getName(), newValue);
|
||||
} else { // the type is not reloadable, must use reflection to access the value
|
||||
try {
|
||||
Field f = locateFieldByReflection(clazz, typeDescriptor.getDottedName(), typeDescriptor.isInterface(),
|
||||
theField.getName());
|
||||
f.setAccessible(true);
|
||||
f.set(null, newValue);
|
||||
// cant cache result - we dont control the sets so won't know it is happening anyway
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Unexpectedly unable to reflectively set the field " + theField.getName()
|
||||
+ " on the type " + clazz.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the value of the field for which is reader-writer exists. To improve performance a fieldAccessor can be supplied but
|
||||
* if it is missing the code will go and discover it.
|
||||
*
|
||||
* @param instance the instance for which the field should be fetched
|
||||
* @param stateManager an optional state manager containing the map of values (will be discovered if not supplied)
|
||||
*/
|
||||
public Object getValue(Object instance, ISMgr stateManager) throws IllegalAccessException, IllegalArgumentException {
|
||||
Object result = null;
|
||||
String fieldname = theField.getName();
|
||||
if (typeDescriptor.isReloadable()) {
|
||||
if (stateManager == null) {
|
||||
// find it using reflection
|
||||
stateManager = findInstanceStateManager(instance);
|
||||
}
|
||||
String declaringTypeName = typeDescriptor.getName();
|
||||
Map<String, Object> typeLevelValues = stateManager.getMap().get(declaringTypeName);
|
||||
boolean knownField = false;
|
||||
if (typeLevelValues != null) {
|
||||
knownField = typeLevelValues.containsKey(fieldname);
|
||||
}
|
||||
if (knownField) {
|
||||
result = typeLevelValues.get(fieldname);
|
||||
}
|
||||
|
||||
// If a field has been deleted it may 'reveal' a field in a supertype. The revealed field may be in a type
|
||||
// not yet dealt with. In this case typeLevelValues may be null (type not seen before) or the typelevelValues
|
||||
// may not have heard of our field name. In these cases we need to go and find the field and 'relocate' it
|
||||
// into our map, where it will be processed from now on.
|
||||
if (typeLevelValues == null || !knownField) {
|
||||
|
||||
FieldMember fieldOnOriginalType = typeDescriptor.getReloadableType().getTypeRegistry()
|
||||
.getReloadableType(declaringTypeName).getTypeDescriptor().getField(fieldname);
|
||||
|
||||
if (fieldOnOriginalType != null) {
|
||||
// Copy the field into the map - that is where it will live from now on
|
||||
ReloadableType rt = typeDescriptor.getReloadableType();
|
||||
try {
|
||||
Field f = rt.getClazz().getDeclaredField(fieldname);
|
||||
f.setAccessible(true);
|
||||
result = f.get(instance);
|
||||
if (typeLevelValues == null) {
|
||||
typeLevelValues = new HashMap<String, Object>();
|
||||
stateManager.getMap().put(declaringTypeName, typeLevelValues);
|
||||
}
|
||||
typeLevelValues.put(fieldname, result);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Unexpectedly unable to access field " + fieldname + " on class "
|
||||
+ rt.getClazz(), e);
|
||||
}
|
||||
} else {
|
||||
// The field was not on the original type. As not seen before, can default it
|
||||
result = Utils.toResultCheckIfNull(null, theField.getDescriptor());
|
||||
if (typeLevelValues == null) {
|
||||
typeLevelValues = new HashMap<String, Object>();
|
||||
stateManager.getMap().put(declaringTypeName, typeLevelValues);
|
||||
}
|
||||
typeLevelValues.put(fieldname, result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
if (result != null) {
|
||||
result = Utils.checkCompatibility(typeDescriptor.getTypeRegistry(), result, theField.getDescriptor());
|
||||
if (result == null) {
|
||||
// Was not compatible, forget it
|
||||
typeLevelValues.remove(fieldname);
|
||||
}
|
||||
}
|
||||
result = Utils.toResultCheckIfNull(result, theField.getDescriptor());
|
||||
} else {
|
||||
// the type is not reloadable, must use reflection to access the value.
|
||||
// TODO measure how often we hit the reflection path, should never happen unless reflection is already on the frame
|
||||
|
||||
if (typeDescriptor.isInterface()) { // cant be an instance field if it is found to be on an interface
|
||||
throw new IncompatibleClassChangeError("Expected non-static field " + instance.getClass().getName() + "."
|
||||
+ fieldname);
|
||||
} else {
|
||||
result = findAndGetFieldValueInHierarchy(instance);
|
||||
}
|
||||
}
|
||||
|
||||
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINER)) {
|
||||
log.finer("<getValue() value of " + theField + " is " + result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public Object getStaticFieldValue(Class<?> clazz, SSMgr stateManager) throws IllegalAccessException, IllegalArgumentException {
|
||||
Object result = null;
|
||||
if (clazz == null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
// First decision - is the field part of a reloadable type or not? The typeDescriptor here is the actual owner
|
||||
// of the field, at this point we *know* this class has this field.
|
||||
if (typeDescriptor.isReloadable()) {
|
||||
if (stateManager == null) {
|
||||
// need to go and find it, there *will* be one but it will be slow to retrieve (reflection)
|
||||
stateManager = findStaticStateManager(clazz);
|
||||
if (stateManager == null) {
|
||||
return Utils.toResultCheckIfNull(null, theField.descriptor);
|
||||
}
|
||||
}
|
||||
String declaringTypeName = typeDescriptor.getName();
|
||||
Map<String, Object> typeLevelValues = stateManager.getMap().get(declaringTypeName);
|
||||
String fieldname = theField.getName();
|
||||
boolean knownField = false;
|
||||
if (typeLevelValues != null) {
|
||||
knownField = typeLevelValues.containsKey(fieldname);
|
||||
}
|
||||
if (knownField) {
|
||||
result = typeLevelValues.get(fieldname);
|
||||
}
|
||||
// If a field has been deleted it may 'reveal' a field in a supertype. The revealed field may be in a type
|
||||
// not yet dealt with. In this case typeLevelValues may be null (type not seen before) or the typelevelValues
|
||||
// may not have heard of our field name. In these cases we need to go and find the field and 'relocate' it
|
||||
// into our map, where it will be processed from now on.
|
||||
|
||||
// These revealed fields are not necessarily in the original form of the type so cannot always be accessed via reflection
|
||||
if (typeLevelValues == null || !knownField) {
|
||||
|
||||
FieldMember fieldOnOriginalType = typeDescriptor.getReloadableType().getTypeRegistry()
|
||||
.getReloadableType(declaringTypeName).getTypeDescriptor().getField(fieldname);
|
||||
|
||||
if (fieldOnOriginalType != null) { // && fieldOnOriginalType.isStatic()
|
||||
// can use reflection
|
||||
ReloadableType rt = typeDescriptor.getReloadableType();
|
||||
try {
|
||||
Field f = rt.getClazz().getDeclaredField(theField.getName());
|
||||
if (!Modifier.isStatic(f.getModifiers())) {
|
||||
// need to default it anyway, cant see that original value
|
||||
// TODO this is a dup of the code below, refactor
|
||||
result = Utils.toResultCheckIfNull(null, theField.getDescriptor());
|
||||
if (typeLevelValues == null) {
|
||||
typeLevelValues = new HashMap<String, Object>();
|
||||
stateManager.getMap().put(declaringTypeName, typeLevelValues);
|
||||
}
|
||||
typeLevelValues.put(fieldname, result);
|
||||
return result;
|
||||
}
|
||||
f.setAccessible(true);
|
||||
// TODO can fail on this next line if the field we've found is non-static
|
||||
result = f.get(null);
|
||||
if (typeLevelValues == null) {
|
||||
typeLevelValues = new HashMap<String, Object>();
|
||||
stateManager.getMap().put(declaringTypeName, typeLevelValues);
|
||||
}
|
||||
typeLevelValues.put(theField.getName(), result);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Unexpectedly unable to read field " + theField.getName() + " on type "
|
||||
+ rt.getClazz(), e);
|
||||
}
|
||||
} else {
|
||||
// The field was not on the original type. As not seen before, can default it
|
||||
result = Utils.toResultCheckIfNull(null, theField.getDescriptor());
|
||||
if (typeLevelValues == null) {
|
||||
typeLevelValues = new HashMap<String, Object>();
|
||||
stateManager.getMap().put(declaringTypeName, typeLevelValues);
|
||||
}
|
||||
typeLevelValues.put(fieldname, result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
// A problem that can occur is if a fields type is changed on a reload. If the field
|
||||
// was previously written to we can then retrieve it and attempt to pass it back to the caller
|
||||
// and they'll get something unexpected.
|
||||
if (result != null) {
|
||||
result = Utils.checkCompatibility(typeDescriptor.getTypeRegistry(), result, theField.getDescriptor());
|
||||
if (result == null) {
|
||||
typeLevelValues.remove(theField.getName());
|
||||
}
|
||||
}
|
||||
result = Utils.toResultCheckIfNull(result, theField.getDescriptor());
|
||||
} else { // the type is not reloadable, must use reflection to access the value
|
||||
// TODO measure how often this code gets hit - ensure it does not in the common (non reflective) case
|
||||
try {
|
||||
Field f = locateFieldByReflection(clazz, typeDescriptor.getDottedName(), typeDescriptor.isInterface(),
|
||||
theField.getName());
|
||||
f.setAccessible(true);
|
||||
result = f.get(null);
|
||||
// cant cache result - we dont control the sets so won't know it is happening anyway
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Unexpectedly unable to set static field " + theField.getName() + " on type "
|
||||
+ typeDescriptor.getDottedName());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINER)) {
|
||||
log.finer("<getValue() value of " + theField + " is " + result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discover the named field in the hierarchy using the standard rules of resolution.
|
||||
*
|
||||
* @param clazz the class upon which to start looking
|
||||
* @param typeWanted where the field is!
|
||||
* @param name the name of the field to find
|
||||
* @return the jlrField object representing that field, or null if not found
|
||||
*/
|
||||
private Field locateFieldByReflection(Class<?> clazz, String typeWanted, boolean isInterface, String name) {
|
||||
if (clazz.getName().equals(typeWanted)) {
|
||||
Field[] fs = clazz.getDeclaredFields();
|
||||
if (fs != null) {
|
||||
for (Field f : fs) {
|
||||
if (f.getName().equals(name)) {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check interfaces
|
||||
if (!isInterface) { // not worth looking!
|
||||
Class<?>[] interfaces = clazz.getInterfaces();
|
||||
if (interfaces != null) {
|
||||
for (Class<?> intface : interfaces) {
|
||||
Field f = locateFieldByReflection(intface, typeWanted, isInterface, name);
|
||||
if (f != null) {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check superclass
|
||||
Class<?> superclass = clazz.getSuperclass();
|
||||
if (superclass == null) {
|
||||
return null;
|
||||
} else {
|
||||
return locateFieldByReflection(superclass, typeWanted, isInterface, name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Discover the instance state manager for the specific object instance. Will fail by exception rather than returning null.
|
||||
*
|
||||
* @param instance the object instance on which to look
|
||||
* @return the discovered state manager
|
||||
*/
|
||||
private ISMgr findInstanceStateManager(Object instance) {
|
||||
Class<?> clazz = typeDescriptor.getReloadableType().getClazz();
|
||||
try {
|
||||
Field fieldAccessorField = clazz.getField(Constants.fInstanceFieldsName);
|
||||
if (fieldAccessorField == null) {
|
||||
throw new IllegalStateException("Cant find field accessor for type " + clazz.getName());
|
||||
}
|
||||
ISMgr stateManager = (ISMgr) fieldAccessorField.get(instance);
|
||||
if (stateManager == null) {
|
||||
// Looks to not have been initialized yet, this can happen if a non standard ctor was used.
|
||||
// We could push this step into the generated ctors...
|
||||
ISMgr instanceStateManager = new ISMgr(instance, typeDescriptor.getReloadableType());
|
||||
fieldAccessorField.set(instance,instanceStateManager);
|
||||
stateManager = (ISMgr) fieldAccessorField.get(instance);
|
||||
// For some reason it didn't stick!
|
||||
if (stateManager == null) {
|
||||
throw new IllegalStateException("The class '" + clazz.getName()
|
||||
+ "' has a null instance state manager object, instance is " + instance);
|
||||
}
|
||||
}
|
||||
return stateManager;
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Unexpectedly unable to find instance state manager on class " + clazz.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Discover the static state manager on the specified class and return it. Will fail by exception rather than returning null.
|
||||
*
|
||||
* @param clazz the class on which to look
|
||||
* @return the discovered state manager
|
||||
*/
|
||||
private SSMgr findStaticStateManager(Class<?> clazz) {
|
||||
try {
|
||||
Field stateManagerField = clazz.getField(Constants.fStaticFieldsName);
|
||||
if (stateManagerField == null) {
|
||||
throw new IllegalStateException("Cant find field accessor for type " + typeDescriptor.getReloadableType().getName());
|
||||
}
|
||||
SSMgr stateManager = (SSMgr) stateManagerField.get(null);
|
||||
// Field should always have been initialized - it is done at the start of the top most reloadable type <clinit>
|
||||
if (stateManager == null) {
|
||||
throw new IllegalStateException("Instance of this class has no state manager: " + clazz.getName());
|
||||
}
|
||||
return stateManager;
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Unexpectedly unable to find static state manager on class " + clazz.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isStatic() {
|
||||
return theField.isStatic();
|
||||
}
|
||||
|
||||
/**
|
||||
* Walk up the instance hierarchy looking for the field, and when it is found access it and return the result. Will exit via
|
||||
* exception if it cannot find the field or something goes wrong when accessing it.
|
||||
*
|
||||
* @param instance the object instance upon which the field is being accessed
|
||||
* @return the value of the field
|
||||
*/
|
||||
private Object findAndGetFieldValueInHierarchy(Object instance) {
|
||||
Class<?> clazz = instance.getClass();
|
||||
String fieldname = theField.getName();
|
||||
String searchName = typeDescriptor.getName().replace('/', '.');
|
||||
while (clazz != null && !clazz.getName().equals(searchName)) {
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
if (clazz == null) {
|
||||
throw new IllegalStateException("Failed to find " + searchName + " in hierarchy of " + instance.getClass());
|
||||
}
|
||||
try {
|
||||
Field f = clazz.getDeclaredField(fieldname);
|
||||
f.setAccessible(true);
|
||||
return f.get(instance);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Unexpectedly could not access field named " + fieldname + " on class "
|
||||
+ clazz.getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Walk up the instance hierarchy looking for the field, and when it is found set it. Will exit via exception if it cannot find
|
||||
* the field or something goes wrong when accessing it.
|
||||
*
|
||||
* @param instance the object instance upon which the field is being set
|
||||
* @param newValue the new value for the field
|
||||
*/
|
||||
private void findAndSetFieldValueInHierarchy(Object instance, Object newValue) {
|
||||
Class<?> clazz = instance.getClass();
|
||||
String fieldname = theField.getName();
|
||||
String searchName = typeDescriptor.getName().replace('/', '.');
|
||||
while (clazz != null && !clazz.getName().equals(searchName)) {
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
if (clazz == null) {
|
||||
throw new IllegalStateException("Failed to find " + searchName + " in hierarchy of " + instance.getClass());
|
||||
}
|
||||
try {
|
||||
Field f = clazz.getDeclaredField(fieldname);
|
||||
f.setAccessible(true);
|
||||
f.set(instance, newValue);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Unexpectedly could not access field named " + fieldname + " on class "
|
||||
+ clazz.getName());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Call back interface for the FileSystemWatcher.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public interface FileChangeListener {
|
||||
|
||||
void fileChanged(File file);
|
||||
|
||||
void register(ReloadableType rtype, File file);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.springsource.loaded.agent.SpringPlugin;
|
||||
|
||||
|
||||
/**
|
||||
* Captures configurable elements - these are set (to values other than the defaults) in TypeRegistry when the system property
|
||||
* springloaded.configuration is processed. It is possible to tweak them during testcases to simplify what is being tested - the
|
||||
* test should reset them to their original values on completion.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class GlobalConfiguration {
|
||||
|
||||
private static Logger log = Logger.getLogger(GlobalConfiguration.class.getName());
|
||||
|
||||
/**
|
||||
* Are references to fields being modified - covering both the GETS/SETS and the reflective references.
|
||||
*/
|
||||
public final static boolean fieldRewriting = true;
|
||||
|
||||
public static boolean catchersOn = true;
|
||||
|
||||
/**
|
||||
* If active, SpringLoaded will be trying to watch for types changing on the file system once they have been made reloadable.
|
||||
*/
|
||||
public static boolean fileSystemMonitoring = false;
|
||||
|
||||
/**
|
||||
* Global control for loadtime logging
|
||||
*/
|
||||
public static boolean logging = false;
|
||||
|
||||
/**
|
||||
* verbose mode can trigger extra messages. Enable with 'verbose=true'
|
||||
*/
|
||||
public static boolean verboseMode = false;
|
||||
|
||||
/**
|
||||
* Global control for runtime logging
|
||||
*/
|
||||
public static boolean isRuntimeLogging = false;
|
||||
|
||||
public static boolean callsideRewritingOn = true;
|
||||
|
||||
/**
|
||||
* Allows a cache to be cleaned up as the agent starts (effectively starting with a new cache, if 'caching' is true)
|
||||
*/
|
||||
public static boolean cleanCache = false;
|
||||
|
||||
/**
|
||||
* Determine whether on disk caching will be used.
|
||||
*/
|
||||
public static boolean isCaching = false;
|
||||
|
||||
/**
|
||||
* A well known profile (e.g. grails) can tweak a lot of the default options in a particular way.
|
||||
*/
|
||||
public static String profile = null;
|
||||
|
||||
/**
|
||||
* The base directory in which to create any cache (.slcache folder). If null then user.home will be used.
|
||||
*/
|
||||
public static String cacheDir = null;
|
||||
|
||||
public final static boolean logNonInterceptedReflectiveCalls = false;
|
||||
|
||||
/**
|
||||
* Global control for checking assertions
|
||||
*/
|
||||
public final static boolean assertsOn = false;
|
||||
public final static boolean isProfiling = false;
|
||||
|
||||
public static boolean directlyDefineTypes = true;
|
||||
|
||||
public final static boolean interceptReflection = true;
|
||||
|
||||
public static boolean reloadMessages = false;// can be forced on for testing
|
||||
|
||||
/**
|
||||
* When a reload is attempted, if this is true it will be checked to confirm it is allowed and does not violate the supported
|
||||
* reloadable changes that can be made to a type.
|
||||
*/
|
||||
public static boolean verifyReloads = true;
|
||||
|
||||
/**
|
||||
* When classes are dumped by Utils.dump() this specifies where. A null value will cause us to dump into the default temp
|
||||
* folder.
|
||||
*/
|
||||
public static String dumpFolder = null;
|
||||
|
||||
/**
|
||||
* Global configuration properties set based on the value of system property 'springloaded'. If null then not yet initialized
|
||||
* (and a call to initializeFromSystemProperty()) is needed. If settings are truely once per VM, they are set directly in
|
||||
* GlobalConfiguration whereas if they may be overridden on a per classloader level, they are set in this properties object and
|
||||
* may be overridden by the springloaded.properties files accessible through each classloader.
|
||||
*/
|
||||
public static Properties globalConfigurationProperties;
|
||||
|
||||
/**
|
||||
* List of slashed classnames for types we should 'dump' during processing (for debugging purposes).
|
||||
*/
|
||||
public static List<String> classesToDump;
|
||||
|
||||
public static int maxClassDefinitions = 100;
|
||||
|
||||
/**
|
||||
* List of dotted classnames representing classnames of plugins that should be loaded.
|
||||
*/
|
||||
public static List<String> pluginClassnameList;
|
||||
|
||||
public final static boolean debugplugins;
|
||||
|
||||
/**
|
||||
* Look for a springloaded system property and initialize the 'default system wide' configuration based upon it.
|
||||
*/
|
||||
static {
|
||||
// classesToDump = new ArrayList<String>();
|
||||
// classesToDump.add("Demo");
|
||||
globalConfigurationProperties = new Properties();
|
||||
// Load global configuration
|
||||
boolean debugPlugins = false;
|
||||
try {
|
||||
boolean specifiedCaching = false;
|
||||
String value = System.getProperty("springloaded");
|
||||
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINEST)) {
|
||||
log.finest("GlobalConfiguration: being configured from '" + value + "'");
|
||||
}
|
||||
// value is a ';' separated list of configuration options which either may be name=value settings or directives (just a name)
|
||||
if (value != null) {
|
||||
StringTokenizer st = new StringTokenizer(value, ";");
|
||||
while (st.hasMoreTokens()) {
|
||||
String kv = st.nextToken();
|
||||
int equals = kv.indexOf('=');
|
||||
if (equals != -1) {
|
||||
// key=value
|
||||
String key = kv.substring(0, equals);
|
||||
// Supported settings:
|
||||
|
||||
// dump=XX,YYY,ZZZ
|
||||
// - this option lists classes for which we should dump the bytecode, names are dotted
|
||||
if (key.equals("dump")) {
|
||||
String classList = kv.substring(equals + 1);
|
||||
StringTokenizer clSt = new StringTokenizer(classList, ",");
|
||||
classesToDump = new ArrayList<String>();
|
||||
while (clSt.hasMoreTokens()) {
|
||||
classesToDump.add(clSt.nextToken().replace('.', '/'));
|
||||
}
|
||||
if (isRuntimeLogging && log.isLoggable(Level.INFO)) {
|
||||
log.info("configuration: dumping: " + classesToDump);
|
||||
}
|
||||
// } else if (key.equals("interceptReflection")) { // global setting
|
||||
// interceptReflection = kv.substring(equals + 1).equalsIgnoreCase("true");
|
||||
// if (isRuntimeLogging && log.isLoggable(Level.INFO)) {
|
||||
// log.info("configuration: interceptReflection = " + interceptReflection);
|
||||
// }
|
||||
} else if (key.equals("cleanCache")) {
|
||||
cleanCache = kv.substring(equals + 1).equalsIgnoreCase("true");
|
||||
} else if (key.equals("caching")) {
|
||||
specifiedCaching = true;
|
||||
isCaching = kv.substring(equals + 1).equalsIgnoreCase("true");
|
||||
} else if (key.equals("debugplugins")) {
|
||||
debugPlugins = true;
|
||||
} else if (key.equals("profile")) {
|
||||
profile = kv.substring(equals + 1);
|
||||
} else if (key.equals("cacheDir")) {
|
||||
cacheDir = kv.substring(equals + 1);
|
||||
} else if (key.equals("callsideRewritingOn")) { // global setting
|
||||
callsideRewritingOn = kv.substring(equals + 1).equalsIgnoreCase("true");
|
||||
if (isRuntimeLogging && log.isLoggable(Level.INFO)) {
|
||||
log.info("configuration: callsideRewritingOn = " + callsideRewritingOn);
|
||||
}
|
||||
// } else if (key.equals("logNonInterceptedReflectiveCalls")) { // global setting
|
||||
// logNonInterceptedReflectiveCalls = kv.substring(equals + 1).equalsIgnoreCase("true");
|
||||
// if (isRuntimeLogging && log.isLoggable(Level.INFO)) {
|
||||
// log.info("configuration: logNonInterceptedReflectiveCalls = " + logNonInterceptedReflectiveCalls);
|
||||
// }
|
||||
} else if (key.equals("verifyReloads")) { // global setting
|
||||
verifyReloads = kv.substring(equals + 1).equalsIgnoreCase("true");
|
||||
if (isRuntimeLogging && log.isLoggable(Level.INFO)) {
|
||||
log.info("configuration: verifyReloads = " + verifyReloads);
|
||||
}
|
||||
} else if (key.equals("dumpFolder")) { // global setting
|
||||
dumpFolder = kv.substring(equals + 1);
|
||||
if (isRuntimeLogging && log.isLoggable(Level.INFO)) {
|
||||
log.info("configuration: dumpFolder = " + dumpFolder);
|
||||
}
|
||||
} else if (key.equals("maxClassDefinitions")) {
|
||||
try {
|
||||
maxClassDefinitions = Integer.parseInt(kv.substring(equals + 1));
|
||||
if (isRuntimeLogging && log.isLoggable(Level.INFO)) {
|
||||
log.info("configuration: maxClassDefinitions = " + maxClassDefinitions);
|
||||
}
|
||||
} catch (NumberFormatException nfe) {
|
||||
System.err.println("ERROR: unable to parse " + kv.substring(equals + 1) + " as a integer");
|
||||
}
|
||||
} else if (key.equals("logging")) {
|
||||
GlobalConfiguration.isRuntimeLogging = kv.substring(equals + 1).equalsIgnoreCase("true");
|
||||
GlobalConfiguration.logging = kv.substring(equals + 1).equalsIgnoreCase("true");
|
||||
System.out.println("Spring-Loaded logging = (" + GlobalConfiguration.isRuntimeLogging + ","
|
||||
+ GlobalConfiguration.logging + ")");
|
||||
} else if (key.equals("verbose")) {
|
||||
GlobalConfiguration.verboseMode = kv.substring(equals + 1).equalsIgnoreCase("true");
|
||||
GlobalConfiguration.reloadMessages = verboseMode;
|
||||
} else if (key.equals("rebasePaths")) {
|
||||
// value is a series of "a=b,c=d,e=f" indicating from and to
|
||||
globalConfigurationProperties.put("rebasePaths", kv.substring(equals + 1));
|
||||
} else if (key.equals("inclusions")) {
|
||||
globalConfigurationProperties.put("inclusions", kv.substring(equals + 1));
|
||||
} else if (key.equals("exclusions")) {
|
||||
globalConfigurationProperties.put("exclusions", kv.substring(equals + 1));
|
||||
} else if (key.equals("plugins")) {
|
||||
// plugins=com.myplugin.Plugin,com.somethingelse.SomeOtherPlugin
|
||||
String pluginList = kv.substring(equals + 1);
|
||||
StringTokenizer pluginListTokenizer = new StringTokenizer(pluginList, ",");
|
||||
pluginClassnameList = new ArrayList<String>();
|
||||
while (pluginListTokenizer.hasMoreTokens()) {
|
||||
pluginClassnameList.add(pluginListTokenizer.nextToken());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// directive
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Profile support. A profile is a shortcut for configuring a bunch of options
|
||||
if (profile != null) {
|
||||
if (profile.equals("grails")) {
|
||||
// Configure options based on a grails profile
|
||||
// turn on caching if we have a cacheDir set or can put one in the .grails folder under user.home
|
||||
if (cacheDir == null) {
|
||||
try {
|
||||
String userhome = System.getProperty("user.home");
|
||||
if (userhome != null) {
|
||||
cacheDir = new StringBuilder(userhome).append(File.separator).append(".grails").toString();
|
||||
new File(cacheDir).mkdir();
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
System.err.println("looks like user.home is not set, or cannot write to it: cannot create cache.");
|
||||
t.printStackTrace(System.err);
|
||||
}
|
||||
}
|
||||
if (!specifiedCaching) {
|
||||
if (cacheDir != null) {
|
||||
isCaching = true;
|
||||
}
|
||||
}
|
||||
if (pluginClassnameList == null) {
|
||||
pluginClassnameList = new ArrayList<String>();
|
||||
}
|
||||
pluginClassnameList.add("org.springsource.loaded.SystemPropertyConfiguredIsReloadableTypePlugin");
|
||||
// turn off the 3.0 reloading, for now (just because it hasn't been tested)
|
||||
SpringPlugin.support305 = false;
|
||||
}
|
||||
} else {
|
||||
if (isCaching) {
|
||||
try {
|
||||
String userhome = System.getProperty("user.home");
|
||||
if (userhome != null) {
|
||||
cacheDir = userhome;
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
System.err.println("looks like user.home is not set: cannot create cache.");
|
||||
t.printStackTrace(System.err);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isCaching) {
|
||||
// Ensure cache folder exists
|
||||
try {
|
||||
File cacheDirFile = new File(cacheDir);
|
||||
if (!cacheDirFile.exists()) {
|
||||
boolean created = cacheDirFile.mkdirs();
|
||||
if (!created) {
|
||||
System.err.println("Caching deactivated: failed to create cache directory: " + cacheDir);
|
||||
isCaching = false;
|
||||
}
|
||||
} else {
|
||||
if (!cacheDirFile.isDirectory()) {
|
||||
System.err.println("Caching deactivated: unable to use specified cache area, it is not a directory: "
|
||||
+ cacheDirFile);
|
||||
isCaching = false;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("Unexpected problem creating specified cachedir: " + cacheDir);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
System.err.println("Unexpected problem reading global configuration setting:" + t.toString());
|
||||
t.printStackTrace();
|
||||
}
|
||||
debugplugins = debugPlugins;
|
||||
}
|
||||
}
|
||||
208
springloaded/src/main/java/org/springsource/loaded/ISMgr.java
Normal file
208
springloaded/src/main/java/org/springsource/loaded/ISMgr.java
Normal file
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Every reloadable hierarchy gets an Instance State Manager (ISMgr). The instance state manager is used to find the value of a
|
||||
* field for a particular object instance. The manager is added to the top most type in a reloadable hierarchy and is accessible to
|
||||
* all the subtypes. It maintains a map from types to secondary maps. The secondary maps record name,value pairs for each field. The
|
||||
* maps are only used if something has happened to mean we cannot continue to store the values in the original fields.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class ISMgr {
|
||||
|
||||
private static Logger log = Logger.getLogger(ISMgr.class.getName());
|
||||
|
||||
Map<String, Map<String, Object>> values = new HashMap<String, Map<String, Object>>();
|
||||
|
||||
// TODO rtype in here means no need to have it on the getValue calls
|
||||
public ISMgr(Object instance, ReloadableType rtype) {
|
||||
// System.out.println("Instance passed to ISMgr " + instance + " rtype=" + rtype);
|
||||
if (rtype.getTypeDescriptor().isGroovyType()) {
|
||||
rtype.trackLiveInstance(instance);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of a instance field - this will use 'any means necessary' to get to it.
|
||||
*
|
||||
* @param rtype the reloadabletype
|
||||
* @param instance the object instance on which the field is being accessed (whose type may not be that which declares the
|
||||
* field)
|
||||
* @param name the name of the field
|
||||
*
|
||||
* @return the value of the field
|
||||
*/
|
||||
public Object getValue(ReloadableType rtype, Object instance, String name) throws IllegalAccessException {
|
||||
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINER)) {
|
||||
log.finer(">getValue(rtype=" + rtype + ",instance=" + instance + ",name=" + name + ")");
|
||||
}
|
||||
Object result = null;
|
||||
|
||||
// Quick look from here to top most reloadable type:
|
||||
FieldMember field = rtype.findInstanceField(name);
|
||||
|
||||
if (field == null) {
|
||||
// If the field is now null, there are two possible reasons:
|
||||
// 1. The field does not exist in the hierarchy at all
|
||||
// 2. The field is on a type just above our topmost reloadable type
|
||||
FieldReaderWriter frw = rtype.locateField(name);
|
||||
if (frw == null) {
|
||||
// Used to be caused because we were not reloading constructors - so when a new version of the type was
|
||||
// loaded, maybe a field was removed, but the constructor may still be referring to it. Should no longer
|
||||
// happen but what about static initializers that aren't run straightaway?
|
||||
log.info("Unexpectedly unable to locate instance field " + name + " starting from type " + rtype.dottedtypename
|
||||
+ ": clinit running late?");
|
||||
return null;
|
||||
}
|
||||
result = frw.getValue(instance, this);
|
||||
} else {
|
||||
if (field.isStatic()) {
|
||||
throw new IncompatibleClassChangeError("Expected non-static field " + rtype.dottedtypename + "." + field.getName());
|
||||
}
|
||||
String declaringTypeName = field.getDeclaringTypeName();
|
||||
Map<String, Object> typeLevelValues = values.get(declaringTypeName);
|
||||
boolean knownField = false;
|
||||
if (typeLevelValues != null) {
|
||||
knownField = typeLevelValues.containsKey(name);
|
||||
}
|
||||
if (knownField) {
|
||||
result = typeLevelValues.get(name);
|
||||
}
|
||||
|
||||
// If a field has been deleted it may 'reveal' a field in a supertype. The revealed field may be in a type
|
||||
// not yet dealt with. In this case typeLevelValues may be null (type not seen before) or the typelevelValues
|
||||
// may not have heard of our field name. In these cases we need to go and find the field and 'relocate' it
|
||||
// into our map, where it will be processed from now on.
|
||||
|
||||
// These revealed fields are not necessarily in the original form of the type so cannot be accessed via reflection
|
||||
if (typeLevelValues == null || !knownField) {
|
||||
// Determine whether we need to use reflection or not:
|
||||
// 'field' tells us if we know about it now, it doesn't tell us if we've always known about it
|
||||
|
||||
// TODO lookup performance
|
||||
FieldMember fieldOnOriginalType = rtype.getTypeRegistry().getReloadableType(field.getDeclaringTypeName())
|
||||
.getTypeDescriptor().getField(name);
|
||||
|
||||
if (fieldOnOriginalType != null) {
|
||||
// The field was on the original type, use reflection
|
||||
ReloadableType rt = rtype.getTypeRegistry().getReloadableType(field.getDeclaringTypeName());
|
||||
try {
|
||||
Field f = rt.getClazz().getDeclaredField(name);
|
||||
f.setAccessible(true);
|
||||
result = f.get(instance);
|
||||
if (typeLevelValues == null) {
|
||||
typeLevelValues = new HashMap<String, Object>();
|
||||
values.put(declaringTypeName, typeLevelValues);
|
||||
}
|
||||
typeLevelValues.put(name, result);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Unexpectedly unable to access field " + name + " on type "
|
||||
+ rt.getClazz().getName(), e);
|
||||
}
|
||||
} else {
|
||||
// The field was not on the original type. As not seen before, can default it
|
||||
result = Utils.toResultCheckIfNull(null, field.getDescriptor());
|
||||
if (typeLevelValues == null) {
|
||||
typeLevelValues = new HashMap<String, Object>();
|
||||
values.put(declaringTypeName, typeLevelValues);
|
||||
}
|
||||
typeLevelValues.put(name, result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
if (result != null) {
|
||||
result = Utils.checkCompatibility(rtype.getTypeRegistry(), result, field.getDescriptor());
|
||||
if (result == null) {
|
||||
typeLevelValues.remove(field.getName());
|
||||
}
|
||||
}
|
||||
result = Utils.toResultCheckIfNull(result, field.getDescriptor());
|
||||
}
|
||||
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINER)) {
|
||||
log.finer("<getValue() value of " + name + " is " + result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of a field.
|
||||
*
|
||||
* @param rtype the reloadabletype
|
||||
* @param instance the instance upon which to set the field
|
||||
* @param name the name of the field
|
||||
*/
|
||||
public void setValue(ReloadableType rtype, Object instance, Object value, String name) throws IllegalAccessException {
|
||||
// System.err.println(">setValue(rtype=" + rtype + ",instance=" + instance + ",value=" + value + ",name=" + name + ")");
|
||||
|
||||
// Look up through our reloadable hierarchy to find it
|
||||
FieldMember fieldmember = rtype.findInstanceField(name);
|
||||
|
||||
if (fieldmember == null) {
|
||||
// If the field is null, there are two possible reasons:
|
||||
// 1. The field does not exist in the hierarchy at all
|
||||
// 2. The field is on a type just above our topmost reloadable type
|
||||
FieldReaderWriter frw = rtype.locateField(name);
|
||||
if (frw == null) {
|
||||
// bad code redeployed?
|
||||
log.info("Unexpectedly unable to locate instance field " + name + " starting from type " + rtype.dottedtypename
|
||||
+ ": clinit running late?");
|
||||
return;
|
||||
}
|
||||
frw.setValue(instance, value, this);
|
||||
} else {
|
||||
if (fieldmember.isStatic()) {
|
||||
throw new IncompatibleClassChangeError("Expected non-static field " + rtype.dottedtypename + "."
|
||||
+ fieldmember.getName());
|
||||
}
|
||||
Map<String, Object> typeValues = values.get(fieldmember.getDeclaringTypeName());
|
||||
if (typeValues == null) {
|
||||
typeValues = new HashMap<String, Object>();
|
||||
values.put(fieldmember.getDeclaringTypeName(), typeValues);
|
||||
}
|
||||
typeValues.put(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
private String valuesToString() {
|
||||
StringBuilder s = new StringBuilder();
|
||||
s.append("InstanceState:" + System.identityHashCode(this)).append("\n");
|
||||
for (Map.Entry<String, Map<String, Object>> entry : values.entrySet()) {
|
||||
s.append("Type " + entry.getKey()).append("\n");
|
||||
for (Map.Entry<String, Object> entry2 : entry.getValue().entrySet()) {
|
||||
s.append(" " + entry2.getKey() + "=" + entry2.getValue()).append("\n");
|
||||
}
|
||||
}
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return valuesToString();
|
||||
}
|
||||
|
||||
Map<String, Map<String, Object>> getMap() {
|
||||
return values;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* This class computes and then encapsulates what has changed between the original form of a type and a newly loaded version.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class IncrementalTypeDescriptor implements Constants {
|
||||
|
||||
private TypeDescriptor initialTypeDescriptor;
|
||||
private TypeDescriptor latestTypeDescriptor;
|
||||
|
||||
private int bits;
|
||||
|
||||
private final static int BIT_COMPUTED_DIFF = 0x0001;
|
||||
|
||||
private Map<String, MethodMember> latestMethods; // Map from nameAndDescriptor to the MethodMember
|
||||
private List<MethodMember> newOrChangedMethods;
|
||||
private List<MethodMember> newOrChangedConstructors; // TODO required?
|
||||
private List<MethodMember> deletedMethods;
|
||||
|
||||
public IncrementalTypeDescriptor(TypeDescriptor initialTypeDescriptor) {
|
||||
reinitialize();
|
||||
this.initialTypeDescriptor = initialTypeDescriptor;
|
||||
}
|
||||
|
||||
public TypeDescriptor getLatestTypeDescriptor() {
|
||||
return latestTypeDescriptor;
|
||||
}
|
||||
|
||||
public void setLatestTypeDescriptor(TypeDescriptor typeDescriptor) {
|
||||
reinitialize();
|
||||
this.latestTypeDescriptor = typeDescriptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* When first setup or a new latest descriptor passed to us, forget what we know.
|
||||
*/
|
||||
private void reinitialize() {
|
||||
// trigger recomputation
|
||||
bits = 0x0000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of 'new or changed' methods. New or changed is characterised as: *
|
||||
* <ul>
|
||||
* <li>methods that never used to exist but do now
|
||||
* <li>methods where name and descriptor are the same but something else has changed (see MethodMember.equals())
|
||||
* <li>method was represented as a catcher in the original, but is now 'real'
|
||||
* </ul>
|
||||
* It does not include catchers.
|
||||
*/
|
||||
public List<MethodMember> getNewOrChangedMethods() {
|
||||
compute();
|
||||
return newOrChangedMethods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of 'new or changed' constructors. New or changed is characterized as:
|
||||
* <ul>
|
||||
* <li>constructors that did not exist in the original class as loaded, but do now
|
||||
* <li>constructors that did exist in the original class but have changed in some way (visibility)
|
||||
* </ul>
|
||||
*/
|
||||
public List<MethodMember> getNewOrChangedConstructors() {
|
||||
compute();
|
||||
return newOrChangedConstructors;
|
||||
}
|
||||
|
||||
public List<MethodMember> getDeletedMethods() {
|
||||
compute();
|
||||
return deletedMethods;
|
||||
}
|
||||
|
||||
private void compute() {
|
||||
if ((bits & BIT_COMPUTED_DIFF) != 0) {
|
||||
return;
|
||||
}
|
||||
latestMethods = new HashMap<String, MethodMember>();
|
||||
newOrChangedMethods = new ArrayList<MethodMember>();
|
||||
deletedMethods = new ArrayList<MethodMember>();
|
||||
// Process the methods in the latest copy, compared to the original
|
||||
for (MethodMember latest : latestTypeDescriptor.getMethods()) {
|
||||
|
||||
// Did this method exist in the original? Ask by name and descriptor
|
||||
MethodMember original = initialTypeDescriptor.getByDescriptor(latest.getName(), latest.getDescriptor());
|
||||
|
||||
// If it did not exist, tag it
|
||||
if (original == null) {
|
||||
latest.bits |= MethodMember.IS_NEW;
|
||||
newOrChangedMethods.add(latest);
|
||||
} else {
|
||||
if (!original.equals(latest)) { // check more than just name/descriptor
|
||||
newOrChangedMethods.add(latest);
|
||||
}
|
||||
// If originally it was a catcher and now it is no longer a catcher (an impl has been provided), record it
|
||||
if (MethodMember.isCatcher(original) && !MethodMember.isCatcher(latest)) {
|
||||
latest.bits |= MethodMember.IS_NEW;
|
||||
newOrChangedMethods.add(latest);
|
||||
}
|
||||
// If it now is a catcher where it didn't used to be, it has been deleted
|
||||
if (MethodMember.isCatcher(latest) && !MethodMember.isCatcher(original)) {
|
||||
latest.bits |= MethodMember.WAS_DELETED;
|
||||
}
|
||||
latest.original = original;
|
||||
// Keep track of important changes:
|
||||
if (original.modifiers != latest.modifiers) {
|
||||
// Determine if a change was made from static to non-static or vice versa
|
||||
boolean wasStatic = original.isStatic();
|
||||
boolean isStatic = latest.isStatic();
|
||||
if (wasStatic != isStatic) {
|
||||
if (wasStatic) {
|
||||
// has been made non-static
|
||||
latest.bits |= MethodMember.MADE_NON_STATIC;
|
||||
} else {
|
||||
// has been made static
|
||||
latest.bits |= MethodMember.MADE_STATIC;
|
||||
}
|
||||
}
|
||||
// Determine if a change was made with regards visibility
|
||||
int oldVisibility = original.modifiers & ACC_PUBLIC_PRIVATE_PROTECTED;
|
||||
int newVisibility = latest.modifiers & ACC_PUBLIC_PRIVATE_PROTECTED;
|
||||
if (oldVisibility != newVisibility) {
|
||||
latest.bits |= MethodMember.VISIBILITY_CHANGE;
|
||||
}
|
||||
}
|
||||
// TODO do we care about exceptions changing? It doesn't make it a new method.
|
||||
// TODO if we do, upgrade this check to remember the precise changes?
|
||||
// int oExceptionsLength = original.exceptions == null ? 0 : original.exceptions.length;
|
||||
// int nExceptionsLength = latest.exceptions == null ? 0 : latest.exceptions.length;
|
||||
// if (oExceptionsLength != nExceptionsLength) {
|
||||
// latest.bits |= MethodMember.EXCEPTIONS_CHANGE;
|
||||
// } else {
|
||||
// for (int i = 0; i < oExceptionsLength; i++) {
|
||||
// if (!original.exceptions[i].equals(latest.exceptions[i])) {
|
||||
// latest.bits |= MethodMember.EXCEPTIONS_CHANGE;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
String nadKey = new StringBuilder(latest.getName()).append(latest.getDescriptor()).toString();
|
||||
latestMethods.put(nadKey, latest);
|
||||
}
|
||||
for (MethodMember initialMethod : initialTypeDescriptor.getMethods()) {
|
||||
if (MethodMember.isCatcher(initialMethod)) {
|
||||
continue;
|
||||
}
|
||||
if (!latestTypeDescriptor.defines(initialMethod)) {
|
||||
deletedMethods.add(initialMethod);
|
||||
}
|
||||
}
|
||||
bits |= BIT_COMPUTED_DIFF;
|
||||
}
|
||||
|
||||
public boolean mustUseExecutorForThisMethod(int methodId) {
|
||||
// Rule1: if it is a new method, we must use the executor
|
||||
|
||||
compute();
|
||||
// If it is a catcher method that now has an implementation, we must use the executor
|
||||
MethodMember method = initialTypeDescriptor.getMethod(methodId);
|
||||
if (MethodMember.isCatcher(method)) {
|
||||
// Has it now been provided?? If it has not we can just return immediately
|
||||
boolean found = false;
|
||||
for (MethodMember method2 : newOrChangedMethods) {
|
||||
if (method2.shouldReplace(method)) { // modifiers? what of static/nonstatic et al
|
||||
//We should not consider modifiers or exceptions in this test!
|
||||
// otherwise we will end up not finding a method that really should replace / override
|
||||
// the catcher.
|
||||
found = true;
|
||||
if (MethodMember.isCatcher(method2)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
// not provided! New type descriptor doesn't include catchers
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean hasBeenDeleted(int methodId) {
|
||||
compute();
|
||||
MethodMember method = initialTypeDescriptor.getMethod(methodId);
|
||||
|
||||
boolean a = false;
|
||||
for (MethodMember m : deletedMethods) {
|
||||
if (m.equals(method)) {
|
||||
a = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// alternative mechanism
|
||||
// boolean b = true;
|
||||
// for (MethodMember m : this.latestTypeDescriptor.getMethods()) {
|
||||
// if (m.equals(method)) {
|
||||
// b = wasDeleted(m);
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
public MethodMember getFromLatestByDescriptor(String nameAndDescriptor) {
|
||||
compute();
|
||||
return latestMethods.get(nameAndDescriptor);
|
||||
}
|
||||
|
||||
// For checking the bitflags:
|
||||
|
||||
/**
|
||||
* @return true if the method is brand new after a reload (i.e. was never defined in the original type)
|
||||
*/
|
||||
public static boolean isBrandNewMethod(MethodMember mm) {
|
||||
return (mm.bits & MethodMember.IS_NEW) != 0;
|
||||
}
|
||||
|
||||
public static boolean hasChanged(MethodMember mm) {
|
||||
return (mm.bits & 0x7fffffff) != 0;
|
||||
}
|
||||
|
||||
public static boolean isCatcher(MethodMember method) {
|
||||
return (method.bits & MethodMember.BIT_CATCHER) != 0;
|
||||
}
|
||||
|
||||
public static boolean isNowNonStatic(MethodMember method) {
|
||||
return (method.bits & MethodMember.MADE_NON_STATIC) != 0;
|
||||
}
|
||||
|
||||
public static boolean isNowStatic(MethodMember method) {
|
||||
return (method.bits & MethodMember.MADE_STATIC) != 0;
|
||||
}
|
||||
|
||||
public static boolean hasVisibilityChanged(MethodMember method) {
|
||||
return (method.bits & MethodMember.VISIBILITY_CHANGE) != 0;
|
||||
}
|
||||
|
||||
public static boolean wasDeleted(MethodMember method) {
|
||||
return (method.bits & MethodMember.WAS_DELETED) != 0;
|
||||
}
|
||||
|
||||
public TypeDescriptor getOriginal() {
|
||||
return this.initialTypeDescriptor;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return toString(false);
|
||||
}
|
||||
|
||||
public String toString(boolean compute) {
|
||||
StringBuilder s = new StringBuilder();
|
||||
s.append("Original:\n").append(this.initialTypeDescriptor).append("\nCurrent:\n").append(this.latestTypeDescriptor);
|
||||
s.append('\n');
|
||||
if (compute) {
|
||||
compute();
|
||||
s.append("Deleted methods: ").append(deletedMethods).append("\n");
|
||||
s.append("New or changed methods: ").append(newOrChangedMethods).append("\n");
|
||||
}
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
import org.objectweb.asm.AnnotationVisitor;
|
||||
import org.objectweb.asm.Attribute;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.ClassVisitor;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.FieldVisitor;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
|
||||
/**
|
||||
* Extract an interface for a type. The interface embodies the shape of the type as originally loaded. The key difference with
|
||||
* methods in the interface is that they contain an extra (leading) parameter that is the type of the original loaded class.<br>
|
||||
* For example:<br>
|
||||
*
|
||||
* <tt> <pre>
|
||||
* class Foo {
|
||||
* public String foo(int i) {}
|
||||
* }
|
||||
* </pre></tt>
|
||||
*
|
||||
* will cause creation of an interface method:
|
||||
*
|
||||
* <tt> <pre>
|
||||
* String foo(Foo instance, int i) {}
|
||||
* </pre></tt>
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class InterfaceExtractor {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private TypeRegistry registry;
|
||||
|
||||
public InterfaceExtractor(TypeRegistry registry) {
|
||||
this.registry = registry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the fixed interface for a class and a type descriptor with more details on the methods
|
||||
*/
|
||||
public static byte[] extract(byte[] classbytes, TypeRegistry registry, TypeDescriptor typeDescriptor) {
|
||||
return new InterfaceExtractor(registry).extract(classbytes, typeDescriptor);
|
||||
}
|
||||
|
||||
public byte[] extract(byte[] classbytes, TypeDescriptor typeDescriptor) {
|
||||
ClassReader fileReader = new ClassReader(classbytes);
|
||||
ExtractorVisitor extractorVisitor = new ExtractorVisitor(typeDescriptor);
|
||||
fileReader.accept(extractorVisitor, 0);
|
||||
return extractorVisitor.getBytes();
|
||||
}
|
||||
|
||||
class ExtractorVisitor implements ClassVisitor, Constants {
|
||||
|
||||
private TypeDescriptor typeDescriptor;
|
||||
private ClassWriter interfaceWriter = new ClassWriter(0);
|
||||
private String slashedtypename;
|
||||
|
||||
public ExtractorVisitor(TypeDescriptor typeDescriptor) {
|
||||
this.typeDescriptor = typeDescriptor;
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
return interfaceWriter.toByteArray();
|
||||
}
|
||||
|
||||
public void visit(int version, int flags, String name, String signature, String superclassName, String[] interfaceNames) {
|
||||
// Create interface "public interface [typename]__I {"
|
||||
interfaceWriter.visit(version, ACC_PUBLIC_INTERFACE, Utils.getInterfaceName(name), null, "java/lang/Object", null);
|
||||
this.slashedtypename = name;
|
||||
}
|
||||
|
||||
public MethodVisitor visitMethod(int flags, String name, String descriptor, String signature, String[] exceptions) {
|
||||
// TODO should we special case statics (and not have them require an extra leading param)?
|
||||
if (isClinitOrInit(name)) {
|
||||
if (name.charAt(1) != 'c') { // avoid <clinit>
|
||||
// It is a constructor
|
||||
String newDescriptor = createDescriptorWithPrefixedParameter(descriptor);
|
||||
// Need a modified name
|
||||
name = "___init___";
|
||||
interfaceWriter.visitMethod(ACC_PUBLIC_ABSTRACT, name, newDescriptor, signature, exceptions);
|
||||
}
|
||||
} else {
|
||||
String newDescriptor = createDescriptorWithPrefixedParameter(descriptor);
|
||||
// generic signature is erased
|
||||
MethodMember method = typeDescriptor.getByDescriptor(name, descriptor);
|
||||
if (MethodMember.isClash(method)) {
|
||||
name = "__" + name;
|
||||
}
|
||||
interfaceWriter.visitMethod(ACC_PUBLIC_ABSTRACT, name, newDescriptor, null, exceptions);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void visitInnerClass(String name, String outerName, String innerName, int access) {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
public void visitOuterClass(String owner, String name, String desc) {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
public void visitSource(String source, String debug) {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
public void visitAttribute(Attribute attr) {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
public void visitEnd() {
|
||||
// Must add a method on the interface for the dynamic invocation method
|
||||
String descriptor = mDynamicDispatchDescriptor;
|
||||
interfaceWriter.visitMethod(ACC_PUBLIC_ABSTRACT, mDynamicDispatchName, descriptor, null, null);
|
||||
interfaceWriter.visitMethod(ACC_PUBLIC_ABSTRACT, mStaticInitializerName, "()V", null, null);
|
||||
// Go through catchers on the type descriptor and add the methods to the interface
|
||||
for (MethodMember method : typeDescriptor.getMethods()) {
|
||||
if (!MethodMember.isCatcher(method)) {
|
||||
continue;
|
||||
}
|
||||
descriptor = createDescriptorWithPrefixedParameter(method.getDescriptor());
|
||||
interfaceWriter.visitMethod(ACC_PUBLIC_ABSTRACT, method.getName(), descriptor, null, method.getExceptions());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify the descriptor to include a leading parameter of the type of the class being visited. For example: if visiting
|
||||
* type "com.Bar" and hit method "(Ljava/lang/String;)V" then this method will return "(Lcom/Bar;Ljava/lang/String;)V"
|
||||
*
|
||||
* @return new descriptor with extra leading parameter
|
||||
*/
|
||||
private String createDescriptorWithPrefixedParameter(String descriptor) {
|
||||
StringBuilder newDescriptor = new StringBuilder();
|
||||
newDescriptor.append("(L").append(slashedtypename).append(";");
|
||||
newDescriptor.append(descriptor, 1, descriptor.length());
|
||||
return newDescriptor.toString();
|
||||
}
|
||||
|
||||
private boolean isClinitOrInit(String name) {
|
||||
return name.charAt(0) == '<';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
import java.security.ProtectionDomain;
|
||||
|
||||
import org.springsource.loaded.agent.ReloadDecision;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Plugins implementing this interface are allowed to participate in determining whether a type should be made reloadable.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.7.1
|
||||
*/
|
||||
public interface IsReloadableTypePlugin extends Plugin {
|
||||
|
||||
/**
|
||||
* @param typename slashed type name (e.g. java/lang/String)
|
||||
* @param protectionDomain
|
||||
* @param bytes the classfile data
|
||||
*/
|
||||
ReloadDecision shouldBeMadeReloadable(String typename, ProtectionDomain protectionDomain, byte[] bytes);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.security.ProtectionDomain;
|
||||
|
||||
/**
|
||||
* Plugins that implement this interface are allowed to modify types as they are loaded - this can be necessary sometimes to ensure,
|
||||
* for example, that a particular field is accessible later when a reload event occurs or that some factory method returns a wrapper
|
||||
* rather than the original object it intended to. For information on how to register plugins with the agent, see {@link Plugin}
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public interface LoadtimeInstrumentationPlugin extends Plugin {
|
||||
|
||||
// TODO should probably be dotted names rather than slashed
|
||||
/**
|
||||
* Called by the agent to determine if this plugin is interested in changing the specified type at load time. This is used when
|
||||
* the plugin wishes to do some kind of transformation itself before the type is loaded - for example modify it to record
|
||||
* something that will later be used on a reload event.
|
||||
*
|
||||
* @param slashedTypeName the type name, slashed form (e.g. java/lang/String)
|
||||
* @param classLoader the classloader loading the type
|
||||
* @param protectionDomain
|
||||
* @param bytes the classfile contents for the type
|
||||
* @return true if this plugin wants to change the bytes for the named type
|
||||
*/
|
||||
boolean accept(String slashedTypeName, ClassLoader classLoader, ProtectionDomain protectionDomain, byte[] bytes);
|
||||
|
||||
/**
|
||||
* Once accept has returned true for a type, the modify method will be called to make the actual change to the classfile bytes.
|
||||
*
|
||||
* @param slashedTypeName the type name, slashed form (e.g. java/lang/String)
|
||||
* @param classLoader the classloader loading the type
|
||||
* @param bytes the classfile contents for the type
|
||||
* @return the new (modified) bytes for the class
|
||||
*/
|
||||
byte[] modify(String slashedClassName, ClassLoader classLoader, byte[] bytes);
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
import org.objectweb.asm.Label;
|
||||
import org.objectweb.asm.MethodAdapter;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
import org.springsource.loaded.Utils.ReturnType;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
class MethodCopier extends MethodAdapter implements Constants {
|
||||
|
||||
private boolean isInterface;
|
||||
private String descriptor;
|
||||
private TypeDescriptor typeDescriptor;
|
||||
private String classname;
|
||||
private String suffix;
|
||||
private boolean hasFieldsRequiringAccessors;
|
||||
|
||||
public MethodCopier(MethodVisitor mv, boolean isInterface, String descriptor, TypeDescriptor typeDescriptor, String classname,
|
||||
String suffix) {
|
||||
super(mv);
|
||||
this.isInterface = isInterface;
|
||||
this.descriptor = descriptor;
|
||||
this.typeDescriptor = typeDescriptor;
|
||||
this.classname = classname;
|
||||
this.suffix = suffix;
|
||||
this.hasFieldsRequiringAccessors = this.typeDescriptor.getFieldsRequiringAccessors().length != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {
|
||||
// Rename 'this' to 'thiz' in executor otherwise Eclipse debugger will fail (static method with 'this')
|
||||
if (index == 0 && name.equals("this")) {
|
||||
super.visitLocalVariable("thiz", desc, signature, start, end, index);
|
||||
} else {
|
||||
super.visitLocalVariable(name, desc, signature, start, end, index);
|
||||
}
|
||||
}
|
||||
|
||||
private FieldMember findFieldIfRequiresAccessorUsage(String owner, String name) {
|
||||
FieldMember[] fms = this.typeDescriptor.getFieldsRequiringAccessors();
|
||||
for (FieldMember fm : fms) {
|
||||
if (fm.getName().equals(name) && (owner.equals(classname) || isOneOfOurSupertypes(owner))) { // && fm.getDeclaringTypeName().equals(owner)) {
|
||||
// possibly a match - testcase scenario:
|
||||
// 'owner=prot/SubThree' - this is what the FIELD instruction is working on
|
||||
// 'dfm=prot/Three' - this is the type that declared the field
|
||||
// 'classname=prot/SubThree' - this is the type we are currently operating on
|
||||
|
||||
// in our other case though (with JDK Proxies)
|
||||
// owner=java/lang/reflect/Proxy
|
||||
// dfm=java/lang/reflect/Proxy
|
||||
// classname=$Proxy6
|
||||
// (this is the funky InvocationHandler field called 'h' in Proxy)
|
||||
return fm;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the supplied type is a supertype of the current type we are modifying. This is used to determine if the owner we
|
||||
* have discovered for a field is one of our supertypes (and so, if it is protected, whether it is something that needs
|
||||
* redirecting through an accessor).
|
||||
*
|
||||
* @param type the type which may be one of this types supertypes
|
||||
* @return true if it is a supertype
|
||||
*/
|
||||
private boolean isOneOfOurSupertypes(String type) {
|
||||
String stypeName = typeDescriptor.getSupertypeName();
|
||||
while (stypeName != null) {
|
||||
// TODO [bug] should stop at the first one that has a field in it? and check the field is protected, yada yada yada
|
||||
if (stypeName.equals(type)) {
|
||||
return true;
|
||||
}
|
||||
stypeName = typeDescriptor.getTypeRegistry().getDescriptorFor(stypeName).getSupertypeName();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitFieldInsn(final int opcode, final String owner, final String name, final String desc) {
|
||||
if (hasFieldsRequiringAccessors) {
|
||||
// Check if this field reference needs redirecting to an accessor
|
||||
FieldMember fm = findFieldIfRequiresAccessorUsage(owner, name);
|
||||
if (fm != null) {
|
||||
switch (opcode) {
|
||||
case GETFIELD:
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, classname, Utils.getProtectedFieldGetterName(name), "()" + desc);
|
||||
return;
|
||||
case PUTFIELD:
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, classname, Utils.getProtectedFieldSetterName(name), "(" + desc + ")V");
|
||||
return;
|
||||
case GETSTATIC:
|
||||
mv.visitMethodInsn(INVOKESTATIC, classname, Utils.getProtectedFieldGetterName(name), "()" + desc);
|
||||
return;
|
||||
case PUTSTATIC:
|
||||
mv.visitMethodInsn(INVOKESTATIC, classname, Utils.getProtectedFieldSetterName(name), "(" + desc + ")V");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
super.visitFieldInsn(opcode, owner, name, desc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc) {
|
||||
// Is it a private method call?
|
||||
// TODO r$ check here because we use invokespecial to avoid virtual dispatch on field changes...
|
||||
if (opcode == INVOKESPECIAL && name.charAt(0) != '<' && owner.equals(classname) && !name.startsWith("r$")) {
|
||||
// leaving the invokespecial alone will cause a verify error
|
||||
String descriptor = Utils.insertExtraParameter(owner, desc);
|
||||
super.visitMethodInsn(INVOKESTATIC, Utils.getExecutorName(classname, suffix), name, descriptor);
|
||||
} else {
|
||||
// Might be a private static method
|
||||
boolean done = false;
|
||||
if (opcode == INVOKESTATIC) {
|
||||
MethodMember mm = typeDescriptor.getByDescriptor(name, desc);
|
||||
if (mm != null && mm.isPrivate()) {
|
||||
super.visitMethodInsn(INVOKESTATIC, Utils.getExecutorName(classname, suffix), name, desc);
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
if (!done) {
|
||||
super.visitMethodInsn(opcode, owner, name, desc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitEnd() {
|
||||
if (isInterface) {
|
||||
// Create 'dummy methods' for an interface implementation
|
||||
createDummyMethodBody();
|
||||
super.visitEnd();
|
||||
}
|
||||
}
|
||||
|
||||
private void createDummyMethodBody() {
|
||||
ReturnType returnType = Utils.getReturnTypeDescriptor(descriptor);
|
||||
int descriptorSize = Utils.getSize(descriptor);
|
||||
if (returnType.isVoid()) {
|
||||
super.visitInsn(RETURN);
|
||||
super.visitMaxs(1, descriptorSize);
|
||||
} else if (returnType.isPrimitive()) {
|
||||
super.visitLdcInsn(0);
|
||||
switch (returnType.descriptor.charAt(0)) {
|
||||
case 'B':
|
||||
case 'C':
|
||||
case 'I':
|
||||
case 'S':
|
||||
case 'Z':
|
||||
super.visitInsn(IRETURN);
|
||||
super.visitMaxs(2, descriptorSize);
|
||||
break;
|
||||
case 'D':
|
||||
super.visitInsn(DRETURN);
|
||||
super.visitMaxs(3, descriptorSize);
|
||||
break;
|
||||
case 'F':
|
||||
super.visitInsn(FRETURN);
|
||||
super.visitMaxs(2, descriptorSize);
|
||||
break;
|
||||
case 'J':
|
||||
super.visitInsn(LRETURN);
|
||||
super.visitMaxs(3, descriptorSize);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException(returnType.descriptor);
|
||||
}
|
||||
} else {
|
||||
// reference type
|
||||
super.visitInsn(ACONST_NULL);
|
||||
super.visitInsn(ARETURN);
|
||||
super.visitMaxs(1, descriptorSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
import org.objectweb.asm.tree.AbstractInsnNode;
|
||||
|
||||
/**
|
||||
* Encapsulates what has changed about a method when it is reloaded, compared to the original form.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class MethodDelta {
|
||||
public int changed;
|
||||
|
||||
private final static int CHANGED_INSTRUCTIONS = 0x0001;
|
||||
private final static int CHANGED_ACCESS = 0x0002;
|
||||
private final static int CHANGED_ANNOTATIONS = 0x0004;
|
||||
private final static int CHANGED_INVOKESPECIAL = 0x0008;
|
||||
private final static int CHANGED_CODE = 0x0010;
|
||||
|
||||
private final static int CHANGED_MASK = CHANGED_INSTRUCTIONS | CHANGED_ACCESS | CHANGED_ANNOTATIONS | CHANGED_INVOKESPECIAL
|
||||
| CHANGED_CODE;
|
||||
|
||||
// o = original, n = new
|
||||
public final String name;
|
||||
public final String desc;
|
||||
String annotationChanges;
|
||||
int oAccess, nAccess;
|
||||
String oInvokespecialDescriptor, nInvokespecialDescriptor;
|
||||
AbstractInsnNode[] oInstructions, nInstructions;
|
||||
|
||||
public MethodDelta(String name, String desc) {
|
||||
this.name = name;
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
public void setAnnotationsChanged(String annotationChanges) {
|
||||
this.annotationChanges = annotationChanges;
|
||||
this.changed |= CHANGED_ANNOTATIONS;
|
||||
}
|
||||
|
||||
public boolean hasAnyChanges() {
|
||||
return (changed & CHANGED_MASK) != 0;
|
||||
}
|
||||
|
||||
public boolean hasInvokeSpecialChanged() {
|
||||
return (changed & CHANGED_INVOKESPECIAL) != 0;
|
||||
}
|
||||
|
||||
public boolean hasCodeChanged() {
|
||||
return (changed & CHANGED_CODE) != 0;
|
||||
}
|
||||
|
||||
public void setAccessChanged(int oldAccess, int newAccess) {
|
||||
this.oAccess = oldAccess;
|
||||
this.nAccess = newAccess;
|
||||
this.changed |= CHANGED_ACCESS;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuilder s = new StringBuilder();
|
||||
s.append("MethodDelta[method:").append(name).append(desc);
|
||||
if ((changed & CHANGED_ACCESS) != 0) {
|
||||
s.append(" access:").append(oAccess).append(">").append(nAccess);
|
||||
}
|
||||
if ((changed & CHANGED_ANNOTATIONS) != 0) {
|
||||
s.append(" annotations:").append(annotationChanges);
|
||||
}
|
||||
s.append("]");
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
public void setInstructionsChanged(AbstractInsnNode[] oInstructions, AbstractInsnNode[] nInstructions) {
|
||||
this.changed |= CHANGED_INSTRUCTIONS;
|
||||
}
|
||||
|
||||
public void setInvokespecialChanged(String oInvokeSpecialDescriptor, String nInvokeSpecialDescriptor) {
|
||||
this.changed |= CHANGED_INVOKESPECIAL;
|
||||
this.oInvokespecialDescriptor = oInvokeSpecialDescriptor;
|
||||
this.nInvokespecialDescriptor = nInvokeSpecialDescriptor;
|
||||
}
|
||||
|
||||
public void setCodeChanged(AbstractInsnNode[] oInstructions, AbstractInsnNode[] nInstructions) {
|
||||
this.changed |= CHANGED_CODE;
|
||||
this.oInstructions = oInstructions;
|
||||
this.nInstructions = nInstructions;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,283 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
/**
|
||||
* Representation of a Method. Some of the bitflags and state are only set for 'incremental' methods - those found in a secondary
|
||||
* type descriptor representing a type reload.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class MethodMember extends AbstractMember {
|
||||
|
||||
final static MethodMember[] NONE = new MethodMember[0];
|
||||
|
||||
protected final String[] exceptions;
|
||||
|
||||
public int bits;
|
||||
|
||||
// computed up front:
|
||||
public final static int BIT_CATCHER = 0x001;
|
||||
public final static int BIT_CLASH = 0x0002;
|
||||
|
||||
// identifies a catcher method placed into an abstract class (where a method from a super interface hasn't been implemented)
|
||||
public final static int BIT_CATCHER_INTERFACE = 0x004;
|
||||
|
||||
// computed on incremental members to indicate what changed:
|
||||
public final static int MADE_STATIC = 0x0010;
|
||||
public final static int MADE_NON_STATIC = 0x0020;
|
||||
public final static int VISIBILITY_CHANGE = 0x0040;
|
||||
public final static int IS_NEW = 0x0080;
|
||||
public final static int WAS_DELETED = 0x0100;
|
||||
public final static int EXCEPTIONS_CHANGE = 0x0200;
|
||||
|
||||
// For MethodMembers in an incremental type descriptor, this tracks the method in the original type descriptor (if there was one)
|
||||
public MethodMember original;
|
||||
|
||||
public final String nameAndDescriptor;
|
||||
|
||||
public Method cachedMethod;
|
||||
|
||||
protected MethodMember(int modifiers, String name, String descriptor, String signature, String[] exceptions) {
|
||||
super(modifiers, name, descriptor, signature);
|
||||
this.exceptions = perhapsSortIfNecessary(exceptions);
|
||||
this.nameAndDescriptor = new StringBuilder(name).append(descriptor).toString();
|
||||
}
|
||||
|
||||
private String[] perhapsSortIfNecessary(String[] exceptions) {
|
||||
if (exceptions == null) {
|
||||
return Constants.NO_STRINGS;
|
||||
}
|
||||
// Arrays.sort(exceptions);
|
||||
return exceptions;
|
||||
}
|
||||
|
||||
public String[] getExceptions() {
|
||||
return exceptions;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("0x").append(Integer.toHexString(modifiers));
|
||||
sb.append(" ").append(name).append(descriptor);
|
||||
if (exceptions.length != 0) {
|
||||
sb.append(" throws ");
|
||||
for (String ex : exceptions) {
|
||||
sb.append(ex).append(" ");
|
||||
}
|
||||
}
|
||||
return sb.toString().trim();
|
||||
}
|
||||
|
||||
public String getParamDescriptor() {
|
||||
// more likely to be at the end, lets go back from there
|
||||
for (int pos = descriptor.length() - 1; pos > 0; pos--) {
|
||||
if (descriptor.charAt(pos) == ')') {
|
||||
return descriptor.substring(0, pos + 1);
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("Method has invalid descriptor: " + descriptor);
|
||||
}
|
||||
|
||||
public boolean hasReturnValue() {
|
||||
return descriptor.charAt(descriptor.length() - 1) != 'V';
|
||||
}
|
||||
|
||||
public boolean equals(Object other) {
|
||||
if (!(other instanceof MethodMember)) {
|
||||
return false;
|
||||
}
|
||||
MethodMember o = (MethodMember) other;
|
||||
if (!name.equals(o.name)) {
|
||||
return false;
|
||||
}
|
||||
if (modifiers != o.modifiers) {
|
||||
return false;
|
||||
}
|
||||
if (!descriptor.equals(o.descriptor)) {
|
||||
return false;
|
||||
}
|
||||
if (exceptions.length != o.exceptions.length) {
|
||||
return false;
|
||||
}
|
||||
if (signature == null && o.signature != null) {
|
||||
return false;
|
||||
}
|
||||
if (signature != null && o.signature == null) {
|
||||
return false;
|
||||
}
|
||||
if (signature != null) {
|
||||
if (!signature.equals(o.signature)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < exceptions.length; i++) {
|
||||
if (!exceptions[i].equals(o.exceptions[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
int result = modifiers;
|
||||
result = result * 37 + name.hashCode();
|
||||
result = result * 37 + descriptor.hashCode();
|
||||
if (signature != null) {
|
||||
result = result * 37 + signature.hashCode();
|
||||
}
|
||||
if (exceptions != null) {
|
||||
for (String ex : exceptions) {
|
||||
result = result * 37 + ex.hashCode();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public MethodMember catcherCopyOf() {
|
||||
int newModifiers = modifiers & ~Modifier.NATIVE;
|
||||
if (name.equals("clone") && (modifiers & Modifier.NATIVE) != 0) {
|
||||
newModifiers = Modifier.PUBLIC;
|
||||
} else if ((modifiers & Modifier.PROTECTED) != 0) {
|
||||
// promote to public
|
||||
// The reason for this is that the executor may try and call these things and as it is not in the hierarchy
|
||||
// it cannot. The necessary knock on effect is that subtypes get their methods promoted to public too...
|
||||
newModifiers = Modifier.PUBLIC;
|
||||
} else if ((modifiers & Constants.ACC_PUBLIC_PRIVATE_PROTECTED) == 0) {
|
||||
// promote to public from default
|
||||
// The reason for this is that the executor may try and call these things and as it is not in the hierarchy
|
||||
// it cannot. The necessary knock on effect is that subtypes get their methods promoted to public too...
|
||||
newModifiers = Modifier.PUBLIC;
|
||||
}
|
||||
MethodMember copy = new MethodMember(newModifiers, name, descriptor, signature, exceptions);
|
||||
copy.bits |= MethodMember.BIT_CATCHER;
|
||||
return copy;
|
||||
}
|
||||
|
||||
public MethodMember catcherCopyOfWithAbstractRemoved() {
|
||||
int newModifiers = modifiers & ~(Modifier.NATIVE | Modifier.ABSTRACT);
|
||||
if (name.equals("clone") && (modifiers & Modifier.NATIVE) != 0) {
|
||||
newModifiers = Modifier.PUBLIC;
|
||||
} else if ((modifiers & Modifier.PROTECTED) != 0) {
|
||||
// promote to public
|
||||
// The reason for this is that the executor may try and call these things and as it is not in the hierarchy
|
||||
// it cannot. The necessary knock on effect is that subtypes get their methods promoted to public too...
|
||||
newModifiers = Modifier.PUBLIC;
|
||||
} else if ((modifiers & Constants.ACC_PUBLIC_PRIVATE_PROTECTED) == 0) {
|
||||
// promote to public from default
|
||||
// The reason for this is that the executor may try and call these things and as it is not in the hierarchy
|
||||
// it cannot. The necessary knock on effect is that subtypes get their methods promoted to public too...
|
||||
newModifiers = Modifier.PUBLIC;
|
||||
}
|
||||
MethodMember copy = new MethodMember(newModifiers, name, descriptor, signature, exceptions);
|
||||
copy.bits |= MethodMember.BIT_CATCHER;
|
||||
copy.bits |= MethodMember.BIT_CATCHER_INTERFACE;
|
||||
return copy;
|
||||
}
|
||||
|
||||
public boolean equalsApartFromModifiers(MethodMember other) {
|
||||
if (!(other instanceof MethodMember)) {
|
||||
return false;
|
||||
}
|
||||
MethodMember o = other;
|
||||
if (!name.equals(o.name)) {
|
||||
return false;
|
||||
}
|
||||
if (!descriptor.equals(o.descriptor)) {
|
||||
return false;
|
||||
}
|
||||
// if (exceptions.length != o.exceptions.length) {
|
||||
// return false;
|
||||
// }
|
||||
// for (int i = 0; i < exceptions.length; i++) {
|
||||
// if (!exceptions[i].equals(o.exceptions[i])) {
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getNameAndDescriptor() {
|
||||
return nameAndDescriptor;
|
||||
}
|
||||
|
||||
public static boolean isClash(MethodMember method) {
|
||||
return (method.bits & MethodMember.BIT_CLASH) != 0;
|
||||
}
|
||||
|
||||
public static boolean isCatcher(MethodMember method) {
|
||||
return (method.bits & BIT_CATCHER) != 0;
|
||||
}
|
||||
|
||||
public static boolean isCatcherForInterfaceMethod(MethodMember method) {
|
||||
return (method.bits & BIT_CATCHER_INTERFACE) != 0;
|
||||
}
|
||||
|
||||
public static boolean isDeleted(MethodMember method) {
|
||||
return (method.bits & WAS_DELETED) != 0;
|
||||
}
|
||||
|
||||
public Object bitsToString() {
|
||||
StringBuilder s = new StringBuilder();
|
||||
if ((bits & BIT_CATCHER) != 0) {
|
||||
s.append("catcher ");
|
||||
}
|
||||
if ((bits & BIT_CLASH) != 0) {
|
||||
s.append("clash ");
|
||||
}
|
||||
if ((bits & MADE_STATIC) != 0) {
|
||||
s.append("made_static ");
|
||||
}
|
||||
if ((bits & MADE_NON_STATIC) != 0) {
|
||||
s.append("made_non_static ");
|
||||
}
|
||||
if ((bits & VISIBILITY_CHANGE) != 0) {
|
||||
s.append("vis_change ");
|
||||
}
|
||||
if ((bits & IS_NEW) != 0) {
|
||||
s.append("is_new ");
|
||||
}
|
||||
if ((bits & WAS_DELETED) != 0) {
|
||||
s.append("is_new ");
|
||||
}
|
||||
return "[" + s.toString().trim() + "]";
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether this method should replace the other method on reload. In accordance to how JVM works at class load time,
|
||||
* this will be the case if this and other have the same Class, name, parameter types and return type. I.e. formally, in JVM
|
||||
* bytecode (unlike source code) a method doesn't override a method with a different return type. When such a situation occurs
|
||||
* in source code, the compiler will introduce a bridge method in bytecode.
|
||||
*/
|
||||
public boolean shouldReplace(MethodMember other) {
|
||||
if (!name.equals(other.name)) {
|
||||
return false;
|
||||
}
|
||||
if (!descriptor.equals(other.descriptor)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isConstructor() {
|
||||
return name.equals("<init>");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
/**
|
||||
* Manages a mapping of names to numbers. The same number anywhere means the same name. This means that if some type a/b/C has been
|
||||
* loaded in two places (by different classloaders), it will have the same number in both. Only one of those a/b/C types will be
|
||||
* visible at the location in question, the tricky part could be working out which one (if classloaders are being naughty) but in
|
||||
* theory the first time we get confused (due to finding the name twice), we can work out which one is right and use that mapping
|
||||
* from then on.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.8.1
|
||||
*/
|
||||
public class NameRegistry {
|
||||
|
||||
private static int nextTypeId = 0;
|
||||
private static int size = 10;
|
||||
private static String[] allocatedIds = new String[size];
|
||||
|
||||
private NameRegistry() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Typically used by tests to ensure it looks like a fresh NameRegistry is being used.
|
||||
*/
|
||||
public static void reset() {
|
||||
nextTypeId = 0;
|
||||
size = 10;
|
||||
allocatedIds = new String[size];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the id for a particular type. This method will not allocate a new id if the type is unknown, it will return -1
|
||||
* instead.
|
||||
*
|
||||
* @param slashedClassName a type name like java/lang/String
|
||||
* @return the allocated ID for that type or -1 if unknown
|
||||
*/
|
||||
public static int getIdFor(String slashedClassName) {
|
||||
assert Asserts.assertNotDotted(slashedClassName);
|
||||
for (int i = 0; i < nextTypeId; i++) {
|
||||
if (allocatedIds[i].equals(slashedClassName)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the id for a particular type. This method will not allocate a new id if the type is unknown, it will return -1
|
||||
* instead.
|
||||
*
|
||||
* @param slashedClassName a type name like java/lang/String
|
||||
* @return the allocated ID for that type or -1 if unknown
|
||||
*/
|
||||
public static int getIdOrAllocateFor(String slashedClassName) {
|
||||
int id = getIdFor(slashedClassName);
|
||||
if (id == -1) {
|
||||
id = allocateId(slashedClassName);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
private synchronized static int allocateId(String slashedClassName) {
|
||||
// Check again, in case two threads passed the -1 check in the getIdOrAllocateFor method
|
||||
int id = getIdFor(slashedClassName);
|
||||
if (id == -1) {
|
||||
id = nextTypeId;
|
||||
if (nextTypeId >= allocatedIds.length) {
|
||||
size = size + 10;
|
||||
// need to make more room
|
||||
String[] newAllocatedIds = new String[size];
|
||||
System.arraycopy(allocatedIds, 0, newAllocatedIds, 0, allocatedIds.length);
|
||||
allocatedIds = newAllocatedIds;
|
||||
}
|
||||
allocatedIds[id] = slashedClassName;
|
||||
nextTypeId++; // increase at the end once the value has been set in the array
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
public static String getTypenameById(int typeId) {
|
||||
if (typeId > size) {
|
||||
return null;
|
||||
}
|
||||
return allocatedIds[typeId];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
/**
|
||||
* Top level interface for Spring-Loaded plugins. Plugins are allowed to participate in class loading and modify code as it is
|
||||
* loaded and can be notified as types are reloaded.
|
||||
* <p>
|
||||
* Implementations should be registered by creating a META-INF/services/org.springsource.reloading.agent.Plugins file that lists
|
||||
* (one per line) the plugin classes, for example: org.springsource.loaded.ReloadEventProcessorPluginImpl
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public interface Plugin {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
import org.springsource.loaded.agent.SpringLoadedPreProcessor;
|
||||
|
||||
/**
|
||||
* Manages plugin interactions between the user and the agent. Allows registration/removal/etc of plugins
|
||||
*
|
||||
* <p>
|
||||
* tag: API
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.7.2
|
||||
*/
|
||||
public class Plugins {
|
||||
public static void registerGlobalPlugin(Plugin instance) {
|
||||
SpringLoadedPreProcessor.registerGlobalPlugin(instance);
|
||||
}
|
||||
|
||||
public static void unregisterGlobalPlugin(Plugin instance) {
|
||||
SpringLoadedPreProcessor.unregisterGlobalPlugin(instance);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
/**
|
||||
* Represents a double dotted type pattern. For example: com.foo.bar..* - this has the same meaning as in AspectJ.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class PrefixTypePattern extends TypePattern {
|
||||
|
||||
private String pattern;
|
||||
|
||||
/**
|
||||
* @param prefix prefix of the form 'com.foo.bar..*'
|
||||
*/
|
||||
public PrefixTypePattern(String pattern) {
|
||||
this.pattern = pattern.substring(0, pattern.length() - 2); // chop off
|
||||
// the '.*'
|
||||
}
|
||||
|
||||
protected boolean internalMatches(String input) {
|
||||
boolean b = input.startsWith(pattern);
|
||||
return b;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "text:" + pattern + ".*";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
import org.objectweb.asm.ClassReader;
|
||||
|
||||
/**
|
||||
* Can be used to take a quick look in the bytecode for something. The various static get* methods are the things that the quick
|
||||
* visitor can discover.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.7.3
|
||||
*/
|
||||
public class QuickVisitor {
|
||||
|
||||
public static String[] getImplementedInterfaces(byte[] bytes) {
|
||||
ClassReader fileReader = new ClassReader(bytes);
|
||||
QuickVisitor1 qv = new QuickVisitor1();
|
||||
try {
|
||||
fileReader.accept(qv, ClassReader.SKIP_FRAMES);// TODO more flags to skip other things?
|
||||
} catch (EarlyExitException eee) {
|
||||
}
|
||||
return qv.interfaces;
|
||||
}
|
||||
|
||||
static class QuickVisitor1 extends EmptyClassVisitor {
|
||||
String[] interfaces;
|
||||
|
||||
@Override
|
||||
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
|
||||
this.interfaces = interfaces;
|
||||
// TODO is it truly easier to exit via exception than to visit the rest of it?
|
||||
throw new EarlyExitException();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
private static class EarlyExitException extends RuntimeException {
|
||||
|
||||
}
|
||||
|
||||
// public static int investigate(String slashedClassName, byte[] bytes) {
|
||||
// ClassReader fileReader = new ClassReader(bytes);
|
||||
// RewriteClassAdaptor classAdaptor = new RewriteClassAdaptor();
|
||||
// fileReader.accept(classAdaptor, ClassReader.SKIP_FRAMES);
|
||||
// return classAdaptor.hitCount;
|
||||
// }
|
||||
//
|
||||
// static class RewriteClassAdaptor extends ClassAdapter implements Constants {
|
||||
//
|
||||
// int hitCount = 0;
|
||||
// private ClassWriter cw;
|
||||
// int bits = 0x0000;
|
||||
// private String classname;
|
||||
//
|
||||
// private static boolean isInterceptable(String owner, String methodName) {
|
||||
// return MethodInvokerRewriter.RewriteClassAdaptor.intercepted.contains(owner + "." + methodName);
|
||||
// }
|
||||
//
|
||||
// public RewriteClassAdaptor() {
|
||||
// // TODO should it also compute frames?
|
||||
// super(new ClassWriter(ClassWriter.COMPUTE_MAXS));
|
||||
// cw = (ClassWriter) cv;
|
||||
// }
|
||||
//
|
||||
// public byte[] getBytes() {
|
||||
// byte[] bytes = cw.toByteArray();
|
||||
// return bytes;
|
||||
// }
|
||||
//
|
||||
// public int getBits() {
|
||||
// return bits;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
|
||||
// super.visit(version, access, name, signature, superName, interfaces);
|
||||
// this.classname = name;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public MethodVisitor visitMethod(int flags, String name, String descriptor, String signature, String[] exceptions) {
|
||||
// MethodVisitor mv = super.visitMethod(flags, name, descriptor, signature, exceptions);
|
||||
// return new RewritingMethodAdapter(mv);
|
||||
// }
|
||||
//
|
||||
// class RewritingMethodAdapter extends MethodAdapter implements Opcodes, Constants {
|
||||
//
|
||||
// public RewritingMethodAdapter(MethodVisitor mv) {
|
||||
// super(mv);
|
||||
// }
|
||||
//
|
||||
// private boolean interceptReflection(String owner, String name, String desc) {
|
||||
// if (isInterceptable(owner, name)) {
|
||||
// hitCount++;
|
||||
// System.out.println("SystemClassReflectionInvestigator: " + classname + " uses " + owner + "." + name + desc);
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// int unitializedObjectsCount = 0;
|
||||
//
|
||||
// @Override
|
||||
// public void visitTypeInsn(final int opcode, final String type) {
|
||||
// if (opcode == NEW) {
|
||||
// unitializedObjectsCount++;
|
||||
// }
|
||||
// super.visitTypeInsn(opcode, type);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc) {
|
||||
// if (!GlobalConfiguration.interceptReflection || rewriteReflectiveCall(opcode, owner, name, desc)) {
|
||||
// return;
|
||||
// }
|
||||
// if (opcode == INVOKESPECIAL) {
|
||||
// unitializedObjectsCount--;
|
||||
// }
|
||||
// super.visitMethodInsn(opcode, owner, name, desc);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Determine if a method call is a reflective call and an attempt should be made to rewrite it.
|
||||
// *
|
||||
// * @return true if the call was rewritten
|
||||
// */
|
||||
// private boolean rewriteReflectiveCall(int opcode, String owner, String name, String desc) {
|
||||
// if (owner.length() > 10 && owner.charAt(8) == 'g'
|
||||
// && (owner.startsWith("java/lang/reflect/") || owner.equals("java/lang/Class"))) {
|
||||
// boolean rewritten = interceptReflection(owner, name, desc);
|
||||
// if (rewritten) {
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// }
|
||||
// }
|
||||
}
|
||||
38
springloaded/src/main/java/org/springsource/loaded/RTH.java
Normal file
38
springloaded/src/main/java/org/springsource/loaded/RTH.java
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
// TODO many more to add here - would drastically reduce amount of generated bytecode but the cost would be debugging confusion
|
||||
// TODO can these methods be made synthetic (at load time) so they don't interfere with the debugger?
|
||||
// TODO can we check on whether debugging is going to happen and then choose whether to use these helper methods at startup?
|
||||
/**
|
||||
* Runtime Helper Class. Provides utility methods called by generated code to perform common functions. Using these does reduce the
|
||||
* amount of generated bytecode but it introduces extra paths into the code (calls) that a debugger might step into.
|
||||
*
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 1.0.4
|
||||
*/
|
||||
public class RTH {
|
||||
|
||||
/**
|
||||
* Collapse a String and int into an array
|
||||
*/
|
||||
public static Object[] collapse(String arg0, int arg1) {
|
||||
return new Object[] { arg0, Integer.valueOf(arg1) };
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
/**
|
||||
* A FieldReaderWriter implementation that simply uses reflection to set/get the fields.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class ReflectionFieldReaderWriter extends FieldReaderWriter {
|
||||
|
||||
private Field field;
|
||||
|
||||
public ReflectionFieldReaderWriter(Field findField) {
|
||||
super();
|
||||
this.field = findField;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getStaticFieldValue(Class<?> type, SSMgr fieldAccessor) throws IllegalAccessException, IllegalArgumentException {
|
||||
field.setAccessible(true);
|
||||
return field.get(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStaticFieldValue(Class<?> clazz, Object newValue, SSMgr fieldAccessor) throws IllegalAccessException {
|
||||
field.setAccessible(true);
|
||||
field.set(null, newValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(Object instance, Object newValue, ISMgr fieldAccessor) throws IllegalAccessException {
|
||||
field.setAccessible(true);
|
||||
field.set(instance, newValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue(Object instance, ISMgr fieldAccessor) throws IllegalAccessException, IllegalArgumentException {
|
||||
field.setAccessible(true);
|
||||
return field.get(instance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStatic() {
|
||||
return Modifier.isStatic(field.getModifiers());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
/**
|
||||
* ReloadEventProcessor Plugins are called when a type is reloading. For information on registering them, see {@link Plugin}
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public interface ReloadEventProcessorPlugin extends Plugin {
|
||||
|
||||
/**
|
||||
* Called when a type has been reloaded, allows the plugin to decide if the static initializer should be re-run for the reloaded
|
||||
* type. If the reloaded type has a different static initializer, the new one is the one that will run.
|
||||
*
|
||||
* @param typename the (dotted) type name, for example java.lang.String
|
||||
* @param clazz the Class object that has been reloaded
|
||||
* @param encodedTimestamp an encoded time stamp for this version, containing chars (A-Za-z0-9)
|
||||
* @return true if the static initializer should be re-run
|
||||
*/
|
||||
boolean shouldRerunStaticInitializer(String typename, Class<?> clazz, String encodedTimestamp);
|
||||
|
||||
// TODO expose detailed delta for changes in the type? (i.e. what new fields/methods/etc)
|
||||
// TODO expose instances when they are being tracked?
|
||||
/**
|
||||
* Called when a type has been reloaded. Note, the class is only truly defined to the VM once, and so the Class object (clazz
|
||||
* parameter) is always the same for the same type (ignoring multiple classloader situations). It is passed here so that plugins
|
||||
* processing events can clear any cached state related to it. The encodedTimestamp is an encoding of the ID that the agent has
|
||||
* assigned to this reloaded version of this type.
|
||||
*
|
||||
* @param typename the (dotted) type name, for example java.lang.String
|
||||
* @param clazz the Class object that has been reloaded
|
||||
* @param encodedTimestamp an encoded time stamp for this version, containing chars (A-Za-z0-9)
|
||||
*/
|
||||
void reloadEvent(String typename, Class<?> clazz, String encodedTimestamp);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class ReloadException extends RuntimeException {
|
||||
|
||||
public ReloadException(String message, Exception cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public ReloadException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
176
springloaded/src/main/java/org/springsource/loaded/SSMgr.java
Normal file
176
springloaded/src/main/java/org/springsource/loaded/SSMgr.java
Normal file
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Static State Manager. The top most class in every hierarchy of reloadable types gets a static state manager instance. The static
|
||||
* state manager is used to find the value of a field for a particular object instance. The FieldAccessor is added to the top most
|
||||
* type in a reloadable hierarchy and is accessible to all the subtypes. It maintains a map from type names to fields (name/value
|
||||
* pairs).
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class SSMgr {
|
||||
|
||||
private static Logger log = Logger.getLogger(SSMgr.class.getName());
|
||||
|
||||
Map<String, Map<String, Object>> values = new HashMap<String, Map<String, Object>>();
|
||||
|
||||
public Object getValue(ReloadableType rtype, String name) throws IllegalAccessException {
|
||||
// System.out.println("SSMgr.getValue(rtype=" + rtype + ",name=" + name + ")");
|
||||
Object result = null;
|
||||
// quick look to see if it is nearby (searches up supertype hierarchy, but only up to the topmost reloadabletype)
|
||||
FieldMember fieldmember = rtype.findStaticField(name);//InstanceField(name);
|
||||
|
||||
// Why can fieldmember be null?
|
||||
// 1. Field really does not exist - shouldn't really be possible if the code is 'valid'
|
||||
// 2. Field is inherited from a supertype (usually because a reload has occurred)
|
||||
if (fieldmember == null) {
|
||||
FieldReaderWriter flr = rtype.locateField(name);
|
||||
if (flr == null) {
|
||||
log.info("Unexpectedly unable to locate static field " + name + " starting from type " + rtype.dottedtypename
|
||||
+ ": clinit running late?");
|
||||
return null;
|
||||
}
|
||||
result = flr.getStaticFieldValue(rtype.getClazz(), this);
|
||||
} else {
|
||||
if (!fieldmember.isStatic()) {
|
||||
throw new IncompatibleClassChangeError("Expected static field " + rtype.dottedtypename + "."
|
||||
+ fieldmember.getName());
|
||||
}
|
||||
String declaringTypeName = fieldmember.getDeclaringTypeName();
|
||||
Map<String, Object> typeLevelValues = values.get(declaringTypeName);
|
||||
boolean knownField = false;
|
||||
if (typeLevelValues != null) {
|
||||
knownField = typeLevelValues.containsKey(name);
|
||||
}
|
||||
if (knownField) {
|
||||
result = typeLevelValues.get(name);
|
||||
}
|
||||
// If a field has been deleted it may 'reveal' a field in a supertype. The revealed field may be in a type
|
||||
// not yet dealt with. In this case typeLevelValues may be null (type not seen before) or the typelevelValues
|
||||
// may not have heard of our field name. In these cases we need to go and find the field and 'relocate' it
|
||||
// into our map, where it will be processed from now on.
|
||||
|
||||
// These revealed fields are not necessarily in the original form of the type so cannot always be accessed via reflection
|
||||
if (typeLevelValues == null || !knownField) {
|
||||
// TODO lookup performance?
|
||||
FieldMember fieldOnOriginalType = rtype.getTypeRegistry().getReloadableType(declaringTypeName).getTypeDescriptor()
|
||||
.getField(name);
|
||||
|
||||
if (fieldOnOriginalType != null) {
|
||||
// Copy that field into the map... where it is going to live from now on
|
||||
ReloadableType rt = rtype.getTypeRegistry().getReloadableType(fieldmember.getDeclaringTypeName());
|
||||
try {
|
||||
Field f = rt.getClazz().getDeclaredField(name);
|
||||
f.setAccessible(true);
|
||||
result = f.get(null);
|
||||
if (typeLevelValues == null) {
|
||||
typeLevelValues = new HashMap<String, Object>();
|
||||
values.put(declaringTypeName, typeLevelValues);
|
||||
}
|
||||
typeLevelValues.put(name, result);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Unexpectedly unable to access field " + name + " on type "
|
||||
+ rt.getClazz().getName(), e);
|
||||
}
|
||||
} else {
|
||||
// The field was not on the original type. As not seen before, can default it
|
||||
result = Utils.toResultCheckIfNull(null, fieldmember.getDescriptor());
|
||||
if (typeLevelValues == null) {
|
||||
typeLevelValues = new HashMap<String, Object>();
|
||||
values.put(declaringTypeName, typeLevelValues);
|
||||
}
|
||||
typeLevelValues.put(name, result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
if (result != null) {
|
||||
result = Utils.checkCompatibility(rtype.getTypeRegistry(), result, fieldmember.getDescriptor());
|
||||
if (result == null) {
|
||||
typeLevelValues.remove(fieldmember.getName());
|
||||
}
|
||||
}
|
||||
result = Utils.toResultCheckIfNull(result, fieldmember.getDescriptor());
|
||||
}
|
||||
// if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINER)) {
|
||||
// log.finer("<getValue() value of " + name + " is " + result);
|
||||
// }
|
||||
return result;
|
||||
}
|
||||
|
||||
// TODO ensure can't set field values on interfaces (constants)? (guess we should never encounter the code that tries it)
|
||||
public void setValue(ReloadableType rtype, Object newValue, String name) throws IllegalAccessException {
|
||||
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINEST)) {
|
||||
log.finest("Static field set: " + rtype.getName() + "." + name + " " + newValue);
|
||||
}
|
||||
// TODO can setValue for static fields ignore interfaces? since we know all those fields will be final - what about the clinit calls to setup the fields though?
|
||||
FieldMember fieldmember = rtype.findStaticField(name);//InstanceField(name);
|
||||
|
||||
if (fieldmember == null) {
|
||||
// If the field is null, there are two possible reasons:
|
||||
// 1. The field does not exist in the hierarchy at all
|
||||
// 2. The field is on a type just above our topmost reloadable type
|
||||
FieldReaderWriter frw = rtype.locateField(name);
|
||||
if (frw == null) {
|
||||
// bad code redeployed?
|
||||
log.info("Unexpectedly unable to locate static field " + name + " starting from type " + rtype.dottedtypename
|
||||
+ ": clinit running late?");
|
||||
return;
|
||||
}
|
||||
frw.setStaticFieldValue(rtype.getClazz(), newValue, this);
|
||||
} else {
|
||||
if (!fieldmember.isStatic()) {
|
||||
throw new IncompatibleClassChangeError("Expected static field " + rtype.dottedtypename + "."
|
||||
+ fieldmember.getName());
|
||||
}
|
||||
Map<String, Object> typeValues = values.get(fieldmember.getDeclaringTypeName());//rtype.getName());
|
||||
if (typeValues == null) {
|
||||
typeValues = new HashMap<String, Object>();
|
||||
values.put(fieldmember.getDeclaringTypeName(), typeValues);
|
||||
}
|
||||
typeValues.put(name, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
private String valuesToString() {
|
||||
StringBuilder s = new StringBuilder();
|
||||
s.append("FieldAccessor:" + System.identityHashCode(this)).append("\n");
|
||||
for (Map.Entry<String, Map<String, Object>> entry : values.entrySet()) {
|
||||
s.append("Type " + entry.getKey()).append("\n");
|
||||
for (Map.Entry<String, Object> entry2 : entry.getValue().entrySet()) {
|
||||
s.append(" " + entry2.getKey() + "=" + entry2.getValue()).append("\n");
|
||||
}
|
||||
}
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return valuesToString();
|
||||
}
|
||||
|
||||
Map<String, Map<String, Object>> getMap() {
|
||||
return values;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
/**
|
||||
* API for directly interacting with SpringLoaded.
|
||||
*
|
||||
* <p>
|
||||
* tag: API
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.8.0
|
||||
*/
|
||||
public class SpringLoaded {
|
||||
|
||||
/**
|
||||
* Force a reload of an existing type.
|
||||
*
|
||||
* @param clazz the class to be reloaded
|
||||
* @param newbytedata the data bytecode data to reload as the new version
|
||||
* @return int return code: 0 is success. 1 is unknown classloader, 2 is unknown type (possibly not yet loaded). 3 is reload
|
||||
* event failed. 4 is exception occurred.
|
||||
*/
|
||||
public static int loadNewVersionOfType(Class<?> clazz, byte[] newbytedata) {
|
||||
return loadNewVersionOfType(clazz.getClassLoader(), clazz.getName(), newbytedata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Force a reload of an existing type.
|
||||
*
|
||||
* @param classLoader the classloader that was used to load the original form of the type
|
||||
* @param dottedClassname the dotted name of the type being reloaded, e.g. com.foo.Bar
|
||||
* @param newbytedata the data bytecode data to reload as the new version
|
||||
* @return int return code: 0 is success. 1 is unknown classloader, 2 is unknown type (possibly not yet loaded). 3 is reload
|
||||
* event failed. 4 is exception occurred.
|
||||
*/
|
||||
public static int loadNewVersionOfType(ClassLoader classLoader, String dottedClassname, byte[] newbytedata) {
|
||||
try {
|
||||
// Obtain the type registry of interest
|
||||
TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(classLoader);
|
||||
if (typeRegistry == null) {
|
||||
return 1;
|
||||
}
|
||||
// Find the reloadable type
|
||||
ReloadableType reloadableType = typeRegistry.getReloadableType(dottedClassname.replace('.', '/'));
|
||||
if (reloadableType == null) {
|
||||
return 2;
|
||||
}
|
||||
// Create a unique version tag for this reload attempt
|
||||
String tag = Utils.encode(System.currentTimeMillis());
|
||||
boolean reloaded = reloadableType.loadNewVersion(tag, newbytedata);
|
||||
return reloaded ? 0 : 3;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
import org.objectweb.asm.ClassAdapter;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.MethodAdapter;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
/**
|
||||
* This is similar to SystemClassReflectionRewriter but this version just summarizes what it finds, rather than making any changes.
|
||||
* Using the results of this we can determine whether it needs proper rewriting by the SystemClassReflectionRewriter (which would be
|
||||
* done by adding this class to the list of those in the SLPP that should be processed like that).
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.7.3
|
||||
*/
|
||||
public class SystemClassReflectionInvestigator {
|
||||
|
||||
public static int investigate(String slashedClassName, byte[] bytes) {
|
||||
ClassReader fileReader = new ClassReader(bytes);
|
||||
RewriteClassAdaptor classAdaptor = new RewriteClassAdaptor();
|
||||
fileReader.accept(classAdaptor, ClassReader.SKIP_FRAMES);
|
||||
return classAdaptor.hitCount;
|
||||
}
|
||||
|
||||
static class RewriteClassAdaptor extends ClassAdapter implements Constants {
|
||||
|
||||
int hitCount = 0;
|
||||
private ClassWriter cw;
|
||||
int bits = 0x0000;
|
||||
private String classname;
|
||||
|
||||
private static boolean isInterceptable(String owner, String methodName) {
|
||||
return MethodInvokerRewriter.RewriteClassAdaptor.intercepted.contains(owner + "." + methodName);
|
||||
}
|
||||
|
||||
public RewriteClassAdaptor() {
|
||||
// TODO should it also compute frames?
|
||||
super(new ClassWriter(ClassWriter.COMPUTE_MAXS));
|
||||
cw = (ClassWriter) cv;
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
byte[] bytes = cw.toByteArray();
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public int getBits() {
|
||||
return bits;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
|
||||
super.visit(version, access, name, signature, superName, interfaces);
|
||||
this.classname = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodVisitor visitMethod(int flags, String name, String descriptor, String signature, String[] exceptions) {
|
||||
MethodVisitor mv = super.visitMethod(flags, name, descriptor, signature, exceptions);
|
||||
return new RewritingMethodAdapter(mv);
|
||||
}
|
||||
|
||||
class RewritingMethodAdapter extends MethodAdapter implements Opcodes, Constants {
|
||||
|
||||
public RewritingMethodAdapter(MethodVisitor mv) {
|
||||
super(mv);
|
||||
}
|
||||
|
||||
private boolean interceptReflection(String owner, String name, String desc) {
|
||||
if (isInterceptable(owner, name)) {
|
||||
hitCount++;
|
||||
System.out.println("SystemClassReflectionInvestigator: " + classname + " uses " + owner + "." + name + desc);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int unitializedObjectsCount = 0;
|
||||
|
||||
@Override
|
||||
public void visitTypeInsn(final int opcode, final String type) {
|
||||
if (opcode == NEW) {
|
||||
unitializedObjectsCount++;
|
||||
}
|
||||
super.visitTypeInsn(opcode, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc) {
|
||||
if (!GlobalConfiguration.interceptReflection || rewriteReflectiveCall(opcode, owner, name, desc)) {
|
||||
return;
|
||||
}
|
||||
if (opcode == INVOKESPECIAL) {
|
||||
unitializedObjectsCount--;
|
||||
}
|
||||
super.visitMethodInsn(opcode, owner, name, desc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a method call is a reflective call and an attempt should be made to rewrite it.
|
||||
*
|
||||
* @return true if the call was rewritten
|
||||
*/
|
||||
private boolean rewriteReflectiveCall(int opcode, String owner, String name, String desc) {
|
||||
if (owner.length() > 10 && owner.charAt(8) == 'g'
|
||||
&& (owner.startsWith("java/lang/reflect/") || owner.equals("java/lang/Class"))) {
|
||||
boolean rewritten = interceptReflection(owner, name, desc);
|
||||
if (rewritten) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,977 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.objectweb.asm.ClassAdapter;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.FieldVisitor;
|
||||
import org.objectweb.asm.Label;
|
||||
import org.objectweb.asm.MethodAdapter;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
/**
|
||||
* This is a special rewriter that should be used on system classes that are using reflection. These classes are loader above the
|
||||
* agent code and so cannot use the agent code directly (they can't see the classes). In these situations we will do some rewriting
|
||||
* that will only use other system types. How can that work? Well the affected types are modified to expose a static field (per
|
||||
* reflective API used), these static fields are set by springloaded during later startup and then are available for access from the
|
||||
* rewritten system class code.
|
||||
* <p>
|
||||
* There is a null check in the injected method for cases where everything runs even sooner than can be plugged by SpringLoaded.
|
||||
* <p>
|
||||
* The following are implemented so far:
|
||||
*
|
||||
* <p>
|
||||
* Due to ReflectionNavigator:
|
||||
* <ul>
|
||||
* <li>getDeclaredFields
|
||||
* <li>getDeclaredField
|
||||
* <li>getField
|
||||
* <li>getModifiers
|
||||
* <li>getDeclaredConstructor
|
||||
* <li>getDeclaredMethods
|
||||
* <li>getDeclaredMethod</li>
|
||||
* <p>
|
||||
* Due to ProxyGenerator
|
||||
* <ul>
|
||||
* <li>getMethods
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* This class modifiers the calls to the reflective APIs, adds the fields and helper methods. The wiring of the SpringLoaded
|
||||
* reflectiveinterceptor into types affected by this rewriter is currently done in SpringLoadedPreProcessor.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.7.3
|
||||
*/
|
||||
public class SystemClassReflectionRewriter {
|
||||
|
||||
private static Logger log = Logger.getLogger(SystemClassReflectionRewriter.class.getName());
|
||||
|
||||
public static RewriteResult rewrite(String slashedClassName, byte[] bytes) {
|
||||
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) {
|
||||
log.info("SystemClassReflectionRewriter running for " + slashedClassName);
|
||||
}
|
||||
ClassReader fileReader = new ClassReader(bytes);
|
||||
RewriteClassAdaptor classAdaptor = new RewriteClassAdaptor();
|
||||
// TODO always skip frames? or just for javassist things?
|
||||
fileReader.accept(classAdaptor, ClassReader.SKIP_FRAMES);
|
||||
return new RewriteResult(classAdaptor.getBytes(), classAdaptor.getBits());
|
||||
}
|
||||
|
||||
public static class RewriteResult implements Constants {
|
||||
|
||||
public final byte[] bytes;
|
||||
// These bits describe which kinds of reflective things were done in the
|
||||
// type - and so which fields (of the __sl variety) need filling in. For example,
|
||||
// if the JLC_GETDECLAREDFIELDS bit is set, the field __sljlcgdfs must be set
|
||||
public final int bits;
|
||||
|
||||
public RewriteResult(byte[] bytes, int bits) {
|
||||
this.bytes = bytes;
|
||||
this.bits = bits;
|
||||
}
|
||||
|
||||
public String summarize() {
|
||||
StringBuilder s = new StringBuilder();
|
||||
s.append((bits & JLC_GETDECLAREDCONSTRUCTOR) != 0 ? "getDeclaredConstructor()" : "");
|
||||
s.append((bits & JLC_GETCONSTRUCTOR) != 0 ? "getConstructor()" : "");
|
||||
s.append((bits & JLC_GETMODIFIERS) != 0 ? "getModifiers()" : "");
|
||||
s.append((bits & JLC_GETDECLAREDFIELDS) != 0 ? "getDeclaredFields() " : "");
|
||||
s.append((bits & JLC_GETDECLAREDFIELD) != 0 ? "getDeclaredField() " : "");
|
||||
s.append((bits & JLC_GETFIELD) != 0 ? "getField() " : "");
|
||||
s.append((bits & JLC_GETDECLAREDMETHODS) != 0 ? "getDeclaredMethods() " : "");
|
||||
s.append((bits & JLC_GETDECLAREDMETHOD) != 0 ? "getDeclaredMethod() " : "");
|
||||
s.append((bits & JLC_GETMETHOD) != 0 ? "getMethod() " : "");
|
||||
s.append((bits & JLC_GETMETHODS) != 0 ? "getMethods() " : "");
|
||||
return s.toString().trim();
|
||||
}
|
||||
}
|
||||
|
||||
static class RewriteClassAdaptor extends ClassAdapter implements Constants {
|
||||
|
||||
private ClassWriter cw;
|
||||
int bits = 0x0000;
|
||||
private String classname;
|
||||
|
||||
// enum SpecialRewrite { NotSpecial, java_io_ObjectStreamClass_2 };
|
||||
// private SpecialRewrite special = SpecialRewrite.NotSpecial;
|
||||
|
||||
// TODO [perf] lookup like this really the fastest way?
|
||||
private static boolean isInterceptable(String owner, String methodName) {
|
||||
String s = new StringBuilder(owner).append(".").append(methodName).toString();
|
||||
return MethodInvokerRewriter.RewriteClassAdaptor.intercepted.contains(s);
|
||||
}
|
||||
|
||||
public RewriteClassAdaptor() {
|
||||
// TODO should it also compute frames?
|
||||
super(new ClassWriter(ClassWriter.COMPUTE_MAXS));
|
||||
cw = (ClassWriter) cv;
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
byte[] bytes = cw.toByteArray();
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public int getBits() {
|
||||
return bits;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
|
||||
super.visit(version, access, name, signature, superName, interfaces);
|
||||
this.classname = name;
|
||||
// if (classname.equals("java/io/ObjectStreamClass$2")) {
|
||||
// special = SpecialRewrite.java_io_ObjectStreamClass_2;
|
||||
// }
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodVisitor visitMethod(int flags, String name, String descriptor, String signature, String[] exceptions) {
|
||||
MethodVisitor mv = super.visitMethod(flags, name, descriptor, signature, exceptions);
|
||||
return new RewritingMethodAdapter(mv);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitEnd() {
|
||||
addExtraMethodsAndFields();
|
||||
super.visitEnd();
|
||||
}
|
||||
|
||||
private void addExtraMethodsAndFields() {
|
||||
if ((bits & JLC_GETDECLAREDFIELDS) != 0) {
|
||||
SystemClassReflectionGenerator.generateJLCGDFS(cw, classname);
|
||||
}
|
||||
if ((bits & JLC_GETDECLAREDFIELD) != 0) {
|
||||
SystemClassReflectionGenerator.generateJLC(cw, classname, "getDeclaredField");
|
||||
}
|
||||
if ((bits & JLC_GETFIELD) != 0) {
|
||||
SystemClassReflectionGenerator.generateJLC(cw, classname, "getField");
|
||||
}
|
||||
if ((bits & JLC_GETDECLAREDMETHODS) != 0) {
|
||||
SystemClassReflectionGenerator.generateJLCGetXXXMethods(cw, classname, "getDeclaredMethods");
|
||||
}
|
||||
if ((bits & JLC_GETDECLAREDMETHOD) != 0) {
|
||||
SystemClassReflectionGenerator.generateJLCMethod(cw, classname, "getDeclaredMethod");
|
||||
}
|
||||
if ((bits & JLC_GETMETHOD) != 0) {
|
||||
SystemClassReflectionGenerator.generateJLCMethod(cw, classname, "getMethod");
|
||||
}
|
||||
if ((bits & JLC_GETMODIFIERS) != 0) {
|
||||
SystemClassReflectionGenerator.generateJLCGMODS(cw, classname);
|
||||
}
|
||||
if ((bits & JLC_GETDECLAREDCONSTRUCTOR) != 0) {
|
||||
SystemClassReflectionGenerator.generateJLCGDC(cw, classname);
|
||||
}
|
||||
if ((bits & JLC_GETMETHODS) != 0) {
|
||||
SystemClassReflectionGenerator.generateJLCGetXXXMethods(cw, classname, "getMethods");
|
||||
}
|
||||
if ((bits & JLC_GETCONSTRUCTOR) != 0) {
|
||||
SystemClassReflectionGenerator.generateJLCGC(cw, classname);
|
||||
}
|
||||
}
|
||||
|
||||
class RewritingMethodAdapter extends MethodAdapter implements Opcodes, Constants {
|
||||
|
||||
public RewritingMethodAdapter(MethodVisitor mv) {
|
||||
super(mv);
|
||||
}
|
||||
|
||||
/**
|
||||
* The big method for intercepting reflection. It is passed what the original code is trying to do (which method it is
|
||||
* calling) and decides:
|
||||
* <ul>
|
||||
* <li>whether to rewrite it
|
||||
* <li>what method should be called instead
|
||||
* </ul>
|
||||
*
|
||||
* @return true if the call was modified/intercepted
|
||||
*/
|
||||
private boolean interceptReflection(String owner, String name, String desc) {
|
||||
if (isInterceptable(owner, name)) {
|
||||
return callReflectiveInterceptor(owner, name, desc, mv);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int unitializedObjectsCount = 0;
|
||||
|
||||
@Override
|
||||
public void visitTypeInsn(final int opcode, final String type) {
|
||||
if (opcode == NEW) {
|
||||
unitializedObjectsCount++;
|
||||
}
|
||||
super.visitTypeInsn(opcode, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc) {
|
||||
if (!GlobalConfiguration.interceptReflection || rewriteReflectiveCall(opcode, owner, name, desc)) {
|
||||
return;
|
||||
}
|
||||
if (opcode == INVOKESPECIAL) {
|
||||
unitializedObjectsCount--;
|
||||
}
|
||||
// if (special!=SpecialRewrite.NotSpecial) {
|
||||
// // Special cases:
|
||||
// if (special==SpecialRewrite.java_io_ObjectStreamClass_2) {
|
||||
// // The class java.io.ObjectStreamClass is loaded too early for us to modify and yet it uses reflection.
|
||||
// // That means we need to modify calls to that class instead.
|
||||
//
|
||||
// // The anonymous inner type $2 makes a call to
|
||||
// // 66: invokestatic #10; //Method java/io/ObjectStreamClass.access$700:(Ljava/lang/Class;)Ljava/lang/Long;
|
||||
// // which is the accessor method for the private method 'Long getDeclaredSUID()' in ObjectStreamClass. Redirect this
|
||||
// // method to a helper that can retrieve the suid and be rewritten.
|
||||
// // TODO skip descriptor check, surely name is enough?
|
||||
//// if (owner.equals("java/io/ObjectStreamClass") && name.equals("access$700") && desc.equals("(Ljava/lang/Class;)Ljava/lang/Long;")) {
|
||||
//// // 1. retrieve the serialVersionUID field
|
||||
//// mv.visitLdcInsn("serialVersionUID");
|
||||
//// bits|=JLC_GETDECLAREDFIELD;
|
||||
//// mv.visitMethodInsn(INVOKESTATIC,classname, jlcgdf, jlcgdfDescriptor);
|
||||
////
|
||||
////
|
||||
//// }
|
||||
////// private static Long getDeclaredSUID(Class cl) {
|
||||
////// try {
|
||||
////// Field f = cl.getDeclaredField("serialVersionUID");
|
||||
////// int mask = Modifier.STATIC | Modifier.FINAL;
|
||||
////// if ((f.getModifiers() & mask) == mask) {
|
||||
////// f.setAccessible(true);
|
||||
////// return Long.valueOf(f.getLong(null));
|
||||
////// }
|
||||
////// } catch (Exception ex) {
|
||||
////// }
|
||||
////// return null;
|
||||
////// }
|
||||
// }
|
||||
// }
|
||||
super.visitMethodInsn(opcode, owner, name, desc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a method call is a reflective call and an attempt should be made to rewrite it.
|
||||
*
|
||||
* @return true if the call was rewritten
|
||||
*/
|
||||
private boolean rewriteReflectiveCall(int opcode, String owner, String name, String desc) {
|
||||
if (owner.length() > 10 && owner.charAt(8) == 'g'
|
||||
&& (owner.startsWith("java/lang/reflect/") || owner.equals("java/lang/Class"))) {
|
||||
boolean rewritten = interceptReflection(owner, name, desc);
|
||||
if (rewritten) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean callReflectiveInterceptor(String owner, String name, String desc, MethodVisitor mv) {
|
||||
if (owner.equals("java/lang/Class")) {
|
||||
if (name.equals("getDeclaredFields")) {
|
||||
// stack on arrival: <Class instance>
|
||||
bits |= JLC_GETDECLAREDFIELDS;
|
||||
mv.visitMethodInsn(INVOKESTATIC, classname, jlcgdfs, jlcgdfsDescriptor);
|
||||
return true;
|
||||
} else if (name.equals("getDeclaredField")) {
|
||||
// stack on arrival: <Class instance> <String fieldname>
|
||||
bits |= JLC_GETDECLAREDFIELD;
|
||||
mv.visitMethodInsn(INVOKESTATIC, classname, jlcgdf, jlcgdfDescriptor);
|
||||
return true;
|
||||
} else if (name.equals("getField")) {
|
||||
// stack on arrival: <Class instance> <String fieldname>
|
||||
bits |= JLC_GETFIELD;
|
||||
mv.visitMethodInsn(INVOKESTATIC, classname, jlcgf, jlcgfDescriptor);
|
||||
return true;
|
||||
} else if (name.equals("getDeclaredMethods")) {
|
||||
// stack on arrival: <Class instance>
|
||||
bits |= JLC_GETDECLAREDMETHODS;
|
||||
mv.visitMethodInsn(INVOKESTATIC, classname, jlcgdms, jlcgdmsDescriptor);
|
||||
return true;
|
||||
} else if (name.equals("getDeclaredMethod")) {
|
||||
// stack on arrival: <Class instance> <String methodname> <Class[] paramTypes>
|
||||
bits |= JLC_GETDECLAREDMETHOD;
|
||||
mv.visitMethodInsn(INVOKESTATIC, classname, jlcgdm, jlcgdmDescriptor);
|
||||
return true;
|
||||
} else if (name.equals("getMethod")) {
|
||||
// stack on arrival: <Class instance> <String methodname> <Class[] paramTypes>
|
||||
bits |= JLC_GETMETHOD;
|
||||
mv.visitMethodInsn(INVOKESTATIC, classname, jlcgm, jlcgmDescriptor);
|
||||
return true;
|
||||
} else if (name.equals("getDeclaredConstructor")) {
|
||||
// stack on arrival: <Class instance> <Class[] paramTypes>
|
||||
bits |= JLC_GETDECLAREDCONSTRUCTOR;
|
||||
mv.visitMethodInsn(INVOKESTATIC, classname, jlcgdc, jlcgdcDescriptor);
|
||||
return true;
|
||||
} else if (name.equals("getConstructor")) {
|
||||
// stack on arrival: <Class instance> <Class[] paramTypes>
|
||||
bits |= JLC_GETCONSTRUCTOR;
|
||||
mv.visitMethodInsn(INVOKESTATIC, classname, jlcgc, jlcgcDescriptor);
|
||||
return true;
|
||||
} else if (name.equals("getModifiers")) {
|
||||
// stack on arrival: <Class instance>
|
||||
bits |= JLC_GETMODIFIERS;
|
||||
mv.visitMethodInsn(INVOKESTATIC, classname, jlcgmods, jlcgmodsDescriptor);
|
||||
return true;
|
||||
} else if (name.equals("getMethods")) {
|
||||
// stack on arrival: <class instance>
|
||||
bits |= JLC_GETMETHODS;
|
||||
mv.visitMethodInsn(INVOKESTATIC, classname, jlcgms, jlcgmsDescriptor);
|
||||
return true;
|
||||
} else if (name.equals("newInstance")) {
|
||||
// TODO determine if this actually needs rewriting? Just catching in this if clause to avoid the message
|
||||
return false;
|
||||
}
|
||||
} else if (owner.equals("java/lang/reflect/Constructor")) {
|
||||
if (name.equals("newInstance")) {
|
||||
// catching to avoid message
|
||||
// seen: in Proxy Constructor.newInstance() is used on the newly created proxy class - we don't need to intercept that
|
||||
return false;
|
||||
}
|
||||
}
|
||||
System.err.println("!!! SystemClassReflectionRewriter: nyi for " + owner + "." + name);
|
||||
return false;
|
||||
//throw new IllegalStateException("nyi for " + owner + "." + name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This helper class will generate the fields/methods in the system classes that are being rewritten.
|
||||
*/
|
||||
class SystemClassReflectionGenerator implements Constants {
|
||||
|
||||
// public static Method __sljlcgdfs;
|
||||
// @SuppressWarnings("unused")
|
||||
// private static Field[] __sljlcgdfs(Class<?> clazz) {
|
||||
// if (__sljlcgdfs == null) {
|
||||
// return clazz.getDeclaredFields();
|
||||
// }
|
||||
// try {
|
||||
// return (Field[]) __sljlcgdfs.invoke(null, clazz);
|
||||
// } catch (Exception e) {
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
|
||||
// public static Method __sljlcgdms;
|
||||
// @SuppressWarnings("unused")
|
||||
// private static Method[] __sljlcgdms(Class<?> clazz) {
|
||||
// if (__sljlcgdms == null) {
|
||||
// return clazz.getDeclaredMethods();
|
||||
// }
|
||||
// try {
|
||||
// return (Method[]) __sljlcgdms.invoke(null, clazz);
|
||||
// } catch (Exception e) {
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
|
||||
// public static Method __sljlcgdf;
|
||||
// @SuppressWarnings("unused")
|
||||
// private static Field __sljlcgdf(Class<?> clazz, String fieldname) throws NoSuchFieldException {
|
||||
// if (__sljlcgdf == null) {
|
||||
// return clazz.getDeclaredField(fieldname);
|
||||
// }
|
||||
// try {
|
||||
// return (Field) __sljlcgdf.invoke(null, clazz, fieldname);
|
||||
// } catch (InvocationTargetException ite) {
|
||||
// if (ite.getCause() instanceof NoSuchFieldException) {
|
||||
// throw (NoSuchFieldException) ite.getCause();
|
||||
// }
|
||||
// } catch (Exception e) {
|
||||
// }
|
||||
// return null;
|
||||
// }
|
||||
|
||||
// public static Method __sljlcgdm;
|
||||
//
|
||||
// @SuppressWarnings("unused")
|
||||
// private static Method __sljlcgdm(Class<?> clazz, String methodname, Class... parameterTypes) throws NoSuchMethodException {
|
||||
// if (__sljlcgdm == null) {
|
||||
// return clazz.getDeclaredMethod(methodname, parameterTypes);
|
||||
// }
|
||||
// try {
|
||||
// // if (parameterTypes == null) {
|
||||
// return (Method) __sljlcgdm.invoke(null, clazz, methodname, parameterTypes);
|
||||
// // } else {
|
||||
// // Object[] params = new Object[2 + parameterTypes.length];
|
||||
// // System.arraycopy(parameterTypes, 0, params, 2, parameterTypes.length);
|
||||
// // params[0] = clazz;
|
||||
// // params[1] = methodname;
|
||||
// // return (Method) __sljlcgdm.invoke(null, clazz, methodname, parameterTypes);
|
||||
// // }
|
||||
// } catch (InvocationTargetException ite) {
|
||||
// ite.printStackTrace();
|
||||
// if (ite.getCause() instanceof NoSuchMethodException) {
|
||||
// throw (NoSuchMethodException) ite.getCause();
|
||||
// }
|
||||
// } catch (Exception e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// return null;
|
||||
// }
|
||||
|
||||
// public static Method __sljlcgdc;
|
||||
//
|
||||
// @SuppressWarnings("unused")
|
||||
// private static Constructor __sljlcgdc(Class<?> clazz, Class... parameterTypes) throws NoSuchMethodException {
|
||||
// if (__sljlcgdc == null) {
|
||||
// return clazz.getDeclaredConstructor(parameterTypes);
|
||||
// }
|
||||
// try {
|
||||
// return (Constructor) __sljlcgdc.invoke(null, clazz, parameterTypes);
|
||||
// } catch (InvocationTargetException ite) {
|
||||
// ite.printStackTrace();
|
||||
// if (ite.getCause() instanceof NoSuchMethodException) {
|
||||
// throw (NoSuchMethodException) ite.getCause();
|
||||
// }
|
||||
// } catch (Exception e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// return null;
|
||||
// }
|
||||
|
||||
// public static Method __sljlcgmods;
|
||||
//
|
||||
// @SuppressWarnings("unused")
|
||||
// private static int __sljlcgmods(Class<?> clazz) {
|
||||
// if (__sljlcgmods == null) {
|
||||
// return clazz.getModifiers();
|
||||
// }
|
||||
// try {
|
||||
// return (Integer) __sljlcgmods.invoke(null, clazz);
|
||||
// } catch (Exception e) {
|
||||
// e.printStackTrace();
|
||||
// return 0;
|
||||
// }
|
||||
// }
|
||||
|
||||
public static void generateJLCGMODS(ClassWriter cw, String classname) {
|
||||
FieldVisitor fv = cw.visitField(ACC_PUBLIC + ACC_STATIC, "__sljlcgmods", "Ljava/lang/reflect/Method;", null, null);
|
||||
fv.visitEnd();
|
||||
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC, "__sljlcgmods", "(Ljava/lang/Class;)I",
|
||||
"(Ljava/lang/Class<*>;)I", null);
|
||||
mv.visitCode();
|
||||
Label l0 = new Label();
|
||||
Label l1 = new Label();
|
||||
Label l2 = new Label();
|
||||
mv.visitTryCatchBlock(l0, l1, l2, "java/lang/Exception");
|
||||
Label l3 = new Label();
|
||||
mv.visitLabel(l3);
|
||||
mv.visitFieldInsn(GETSTATIC, classname, "__sljlcgmods", "Ljava/lang/reflect/Method;");
|
||||
mv.visitJumpInsn(IFNONNULL, l0);
|
||||
Label l4 = new Label();
|
||||
mv.visitLabel(l4);
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getModifiers", "()I");
|
||||
mv.visitInsn(IRETURN);
|
||||
mv.visitLabel(l0);
|
||||
mv.visitFieldInsn(GETSTATIC, classname, "__sljlcgmods", "Ljava/lang/reflect/Method;");
|
||||
mv.visitInsn(ACONST_NULL);
|
||||
mv.visitInsn(ICONST_1);
|
||||
mv.visitTypeInsn(ANEWARRAY, "java/lang/Object");
|
||||
mv.visitInsn(DUP);
|
||||
mv.visitInsn(ICONST_0);
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitInsn(AASTORE);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/Method", "invoke",
|
||||
"(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;");
|
||||
mv.visitTypeInsn(CHECKCAST, "java/lang/Integer");
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I");
|
||||
mv.visitLabel(l1);
|
||||
mv.visitInsn(IRETURN);
|
||||
mv.visitLabel(l2);
|
||||
mv.visitVarInsn(ASTORE, 1);
|
||||
Label l5 = new Label();
|
||||
mv.visitLabel(l5);
|
||||
mv.visitVarInsn(ALOAD, 1);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Exception", "printStackTrace", "()V");
|
||||
Label l6 = new Label();
|
||||
mv.visitLabel(l6);
|
||||
mv.visitInsn(ICONST_0);
|
||||
mv.visitInsn(IRETURN);
|
||||
Label l7 = new Label();
|
||||
mv.visitLabel(l7);
|
||||
// mv.visitLocalVariable("clazz", "Ljava/lang/Class;", "Ljava/lang/Class<*>;", l3, l7, 0);
|
||||
// mv.visitLocalVariable("e", "Ljava/lang/Exception;", null, l5, l7, 1);
|
||||
mv.visitMaxs(6, 2);
|
||||
mv.visitEnd();
|
||||
|
||||
}
|
||||
|
||||
public static void generateJLCGDC(ClassWriter cw, String classname) {
|
||||
FieldVisitor fv = cw.visitField(ACC_PUBLIC + ACC_STATIC, "__sljlcgdc", "Ljava/lang/reflect/Method;", null, null);
|
||||
fv.visitEnd();
|
||||
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC + ACC_VARARGS, "__sljlcgdc",
|
||||
"(Ljava/lang/Class;[Ljava/lang/Class;)Ljava/lang/reflect/Constructor;", null,
|
||||
new String[] { "java/lang/NoSuchMethodException" });
|
||||
mv.visitCode();
|
||||
Label l0 = new Label();
|
||||
Label l1 = new Label();
|
||||
Label l2 = new Label();
|
||||
mv.visitTryCatchBlock(l0, l1, l2, "java/lang/reflect/InvocationTargetException");
|
||||
Label l3 = new Label();
|
||||
mv.visitTryCatchBlock(l0, l1, l3, "java/lang/Exception");
|
||||
Label l4 = new Label();
|
||||
mv.visitLabel(l4);
|
||||
mv.visitFieldInsn(GETSTATIC, classname, "__sljlcgdc", "Ljava/lang/reflect/Method;");
|
||||
mv.visitJumpInsn(IFNONNULL, l0);
|
||||
Label l5 = new Label();
|
||||
mv.visitLabel(l5);
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitVarInsn(ALOAD, 1);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getDeclaredConstructor",
|
||||
"([Ljava/lang/Class;)Ljava/lang/reflect/Constructor;");
|
||||
mv.visitInsn(ARETURN);
|
||||
mv.visitLabel(l0);
|
||||
mv.visitFieldInsn(GETSTATIC, classname, "__sljlcgdc", "Ljava/lang/reflect/Method;");
|
||||
mv.visitInsn(ACONST_NULL);
|
||||
mv.visitInsn(ICONST_2);
|
||||
mv.visitTypeInsn(ANEWARRAY, "java/lang/Object");
|
||||
mv.visitInsn(DUP);
|
||||
mv.visitInsn(ICONST_0);
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitInsn(AASTORE);
|
||||
mv.visitInsn(DUP);
|
||||
mv.visitInsn(ICONST_1);
|
||||
mv.visitVarInsn(ALOAD, 1);
|
||||
mv.visitInsn(AASTORE);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/Method", "invoke",
|
||||
"(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;");
|
||||
mv.visitTypeInsn(CHECKCAST, "java/lang/reflect/Constructor");
|
||||
mv.visitLabel(l1);
|
||||
mv.visitInsn(ARETURN);
|
||||
mv.visitLabel(l2);
|
||||
mv.visitVarInsn(ASTORE, 2);
|
||||
Label l6 = new Label();
|
||||
mv.visitLabel(l6);
|
||||
mv.visitVarInsn(ALOAD, 2);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "printStackTrace", "()V");
|
||||
Label l7 = new Label();
|
||||
mv.visitLabel(l7);
|
||||
mv.visitVarInsn(ALOAD, 2);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "getCause", "()Ljava/lang/Throwable;");
|
||||
mv.visitTypeInsn(INSTANCEOF, "java/lang/NoSuchMethodException");
|
||||
Label l8 = new Label();
|
||||
mv.visitJumpInsn(IFEQ, l8);
|
||||
Label l9 = new Label();
|
||||
mv.visitLabel(l9);
|
||||
mv.visitVarInsn(ALOAD, 2);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "getCause", "()Ljava/lang/Throwable;");
|
||||
mv.visitTypeInsn(CHECKCAST, "java/lang/NoSuchMethodException");
|
||||
mv.visitInsn(ATHROW);
|
||||
mv.visitLabel(l3);
|
||||
mv.visitVarInsn(ASTORE, 2);
|
||||
Label l10 = new Label();
|
||||
mv.visitLabel(l10);
|
||||
// mv.visitVarInsn(ALOAD, 2);
|
||||
// mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Exception", "printStackTrace", "()V");
|
||||
mv.visitLabel(l8);
|
||||
mv.visitInsn(ACONST_NULL);
|
||||
mv.visitInsn(ARETURN);
|
||||
Label l11 = new Label();
|
||||
mv.visitLabel(l11);
|
||||
// mv.visitLocalVariable("clazz", "Ljava/lang/Class;", "Ljava/lang/Class<*>;", l4, l11, 0);
|
||||
// mv.visitLocalVariable("parameterTypes", "[Ljava/lang/Class;", null, l4, l11, 1);
|
||||
// mv.visitLocalVariable("ite", "Ljava/lang/reflect/InvocationTargetException;", null, l6, l3, 2);
|
||||
// mv.visitLocalVariable("e", "Ljava/lang/Exception;", null, l10, l8, 2);
|
||||
mv.visitMaxs(6, 3);
|
||||
mv.visitEnd();
|
||||
|
||||
}
|
||||
|
||||
public static void generateJLCGC(ClassWriter cw, String classname) {
|
||||
FieldVisitor fv = cw.visitField(ACC_PUBLIC + ACC_STATIC, "__sljlcgc", "Ljava/lang/reflect/Method;", null, null);
|
||||
fv.visitEnd();
|
||||
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC + ACC_VARARGS, "__sljlcgc",
|
||||
"(Ljava/lang/Class;[Ljava/lang/Class;)Ljava/lang/reflect/Constructor;", null,
|
||||
new String[] { "java/lang/NoSuchMethodException" });
|
||||
mv.visitCode();
|
||||
Label l0 = new Label();
|
||||
Label l1 = new Label();
|
||||
Label l2 = new Label();
|
||||
mv.visitTryCatchBlock(l0, l1, l2, "java/lang/reflect/InvocationTargetException");
|
||||
Label l3 = new Label();
|
||||
mv.visitTryCatchBlock(l0, l1, l3, "java/lang/Exception");
|
||||
Label l4 = new Label();
|
||||
mv.visitLabel(l4);
|
||||
mv.visitFieldInsn(GETSTATIC, classname, "__sljlcgc", "Ljava/lang/reflect/Method;");
|
||||
mv.visitJumpInsn(IFNONNULL, l0);
|
||||
Label l5 = new Label();
|
||||
mv.visitLabel(l5);
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitVarInsn(ALOAD, 1);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getConstructor",
|
||||
"([Ljava/lang/Class;)Ljava/lang/reflect/Constructor;");
|
||||
mv.visitInsn(ARETURN);
|
||||
mv.visitLabel(l0);
|
||||
mv.visitFieldInsn(GETSTATIC, classname, "__sljlcgc", "Ljava/lang/reflect/Method;");
|
||||
mv.visitInsn(ACONST_NULL);
|
||||
mv.visitInsn(ICONST_2);
|
||||
mv.visitTypeInsn(ANEWARRAY, "java/lang/Object");
|
||||
mv.visitInsn(DUP);
|
||||
mv.visitInsn(ICONST_0);
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitInsn(AASTORE);
|
||||
mv.visitInsn(DUP);
|
||||
mv.visitInsn(ICONST_1);
|
||||
mv.visitVarInsn(ALOAD, 1);
|
||||
mv.visitInsn(AASTORE);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/Method", "invoke",
|
||||
"(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;");
|
||||
mv.visitTypeInsn(CHECKCAST, "java/lang/reflect/Constructor");
|
||||
mv.visitLabel(l1);
|
||||
mv.visitInsn(ARETURN);
|
||||
mv.visitLabel(l2);
|
||||
mv.visitVarInsn(ASTORE, 2);
|
||||
Label l6 = new Label();
|
||||
mv.visitLabel(l6);
|
||||
mv.visitVarInsn(ALOAD, 2);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "printStackTrace", "()V");
|
||||
Label l7 = new Label();
|
||||
mv.visitLabel(l7);
|
||||
mv.visitVarInsn(ALOAD, 2);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "getCause", "()Ljava/lang/Throwable;");
|
||||
mv.visitTypeInsn(INSTANCEOF, "java/lang/NoSuchMethodException");
|
||||
Label l8 = new Label();
|
||||
mv.visitJumpInsn(IFEQ, l8);
|
||||
Label l9 = new Label();
|
||||
mv.visitLabel(l9);
|
||||
mv.visitVarInsn(ALOAD, 2);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "getCause", "()Ljava/lang/Throwable;");
|
||||
mv.visitTypeInsn(CHECKCAST, "java/lang/NoSuchMethodException");
|
||||
mv.visitInsn(ATHROW);
|
||||
mv.visitLabel(l3);
|
||||
mv.visitVarInsn(ASTORE, 2);
|
||||
Label l10 = new Label();
|
||||
mv.visitLabel(l10);
|
||||
// mv.visitVarInsn(ALOAD, 2);
|
||||
// mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Exception", "printStackTrace", "()V");
|
||||
mv.visitLabel(l8);
|
||||
mv.visitInsn(ACONST_NULL);
|
||||
mv.visitInsn(ARETURN);
|
||||
Label l11 = new Label();
|
||||
mv.visitLabel(l11);
|
||||
// mv.visitLocalVariable("clazz", "Ljava/lang/Class;", "Ljava/lang/Class<*>;", l4, l11, 0);
|
||||
// mv.visitLocalVariable("parameterTypes", "[Ljava/lang/Class;", null, l4, l11, 1);
|
||||
// mv.visitLocalVariable("ite", "Ljava/lang/reflect/InvocationTargetException;", null, l6, l3, 2);
|
||||
// mv.visitLocalVariable("e", "Ljava/lang/Exception;", null, l10, l8, 2);
|
||||
mv.visitMaxs(6, 3);
|
||||
mv.visitEnd();
|
||||
|
||||
}
|
||||
|
||||
public static void generateJLCMethod(ClassWriter cw, String classname, String membername, String methodname) {
|
||||
FieldVisitor fv = cw.visitField(ACC_PUBLIC + ACC_STATIC, membername, "Ljava/lang/reflect/Method;", null, null);
|
||||
fv.visitEnd();
|
||||
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC + ACC_VARARGS, membername,
|
||||
"(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", null,
|
||||
new String[] { "java/lang/NoSuchMethodException" });
|
||||
mv.visitCode();
|
||||
Label l0 = new Label();
|
||||
Label l1 = new Label();
|
||||
Label l2 = new Label();
|
||||
mv.visitTryCatchBlock(l0, l1, l2, "java/lang/reflect/InvocationTargetException");
|
||||
Label l3 = new Label();
|
||||
mv.visitTryCatchBlock(l0, l1, l3, "java/lang/Exception");
|
||||
Label l4 = new Label();
|
||||
mv.visitLabel(l4);
|
||||
mv.visitFieldInsn(GETSTATIC, classname, membername, "Ljava/lang/reflect/Method;");
|
||||
mv.visitJumpInsn(IFNONNULL, l0);
|
||||
Label l5 = new Label();
|
||||
mv.visitLabel(l5);
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitVarInsn(ALOAD, 1);
|
||||
mv.visitVarInsn(ALOAD, 2);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", methodname,
|
||||
"(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;");
|
||||
mv.visitInsn(ARETURN);
|
||||
mv.visitLabel(l0);
|
||||
mv.visitFieldInsn(GETSTATIC, classname, membername, "Ljava/lang/reflect/Method;");
|
||||
mv.visitInsn(ACONST_NULL);
|
||||
mv.visitInsn(ICONST_3);
|
||||
mv.visitTypeInsn(ANEWARRAY, "java/lang/Object");
|
||||
mv.visitInsn(DUP);
|
||||
mv.visitInsn(ICONST_0);
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitInsn(AASTORE);
|
||||
mv.visitInsn(DUP);
|
||||
mv.visitInsn(ICONST_1);
|
||||
mv.visitVarInsn(ALOAD, 1);
|
||||
mv.visitInsn(AASTORE);
|
||||
mv.visitInsn(DUP);
|
||||
mv.visitInsn(ICONST_2);
|
||||
mv.visitVarInsn(ALOAD, 2);
|
||||
mv.visitInsn(AASTORE);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/Method", "invoke",
|
||||
"(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;");
|
||||
mv.visitTypeInsn(CHECKCAST, "java/lang/reflect/Method");
|
||||
mv.visitLabel(l1);
|
||||
mv.visitInsn(ARETURN);
|
||||
mv.visitLabel(l2);
|
||||
mv.visitVarInsn(ASTORE, 3);
|
||||
Label l6 = new Label();
|
||||
mv.visitLabel(l6);
|
||||
// Don't print the exception if just unwrapping it
|
||||
// mv.visitVarInsn(ALOAD, 3);
|
||||
// mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "printStackTrace", "()V");
|
||||
Label l7 = new Label();
|
||||
mv.visitLabel(l7);
|
||||
mv.visitVarInsn(ALOAD, 3);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "getCause", "()Ljava/lang/Throwable;");
|
||||
mv.visitTypeInsn(INSTANCEOF, "java/lang/NoSuchMethodException");
|
||||
Label l8 = new Label();
|
||||
mv.visitJumpInsn(IFEQ, l8);
|
||||
Label l9 = new Label();
|
||||
mv.visitLabel(l9);
|
||||
mv.visitVarInsn(ALOAD, 3);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "getCause", "()Ljava/lang/Throwable;");
|
||||
mv.visitTypeInsn(CHECKCAST, "java/lang/NoSuchMethodException");
|
||||
mv.visitInsn(ATHROW);
|
||||
mv.visitLabel(l3);
|
||||
mv.visitVarInsn(ASTORE, 3);
|
||||
Label l10 = new Label();
|
||||
mv.visitLabel(l10);
|
||||
mv.visitVarInsn(ALOAD, 3);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Exception", "printStackTrace", "()V");
|
||||
mv.visitLabel(l8);
|
||||
mv.visitInsn(ACONST_NULL);
|
||||
mv.visitInsn(ARETURN);
|
||||
Label l11 = new Label();
|
||||
mv.visitLabel(l11);
|
||||
// mv.visitLocalVariable("clazz", "Ljava/lang/Class;", "Ljava/lang/Class<*>;", l4, l11, 0);
|
||||
// mv.visitLocalVariable("methodname", "Ljava/lang/String;", null, l4, l11, 1);
|
||||
// mv.visitLocalVariable("parameterTypes", "[Ljava/lang/Class;", null, l4, l11, 2);
|
||||
// mv.visitLocalVariable("ite", "Ljava/lang/reflect/InvocationTargetException;", null, l6, l3, 3);
|
||||
// mv.visitLocalVariable("e", "Ljava/lang/Exception;", null, l10, l8, 3);
|
||||
mv.visitMaxs(6, 4);
|
||||
mv.visitEnd();
|
||||
}
|
||||
|
||||
public static void generateJLCMethod(ClassWriter cw, String classname, String operation) {
|
||||
if (operation.equals("getDeclaredMethod")) {
|
||||
generateJLCMethod(cw, classname, "__sljlcgdm", "getDeclaredMethod");
|
||||
} else if (operation.equals("getMethod")) {
|
||||
generateJLCMethod(cw, classname, "__sljlcgm", "getMethod");
|
||||
} else {
|
||||
throw new IllegalStateException("nyi:" + operation);
|
||||
}
|
||||
}
|
||||
|
||||
public static void generateJLC(ClassWriter cw, String classname, String operation) {
|
||||
if (operation.equals("getDeclaredField")) {
|
||||
generateJLCGDF(cw, classname, "__sljlcgdf", "getDeclaredField");
|
||||
} else if (operation.equals("getField")) {
|
||||
generateJLCGDF(cw, classname, "__sljlcgf", "getField");
|
||||
} else {
|
||||
throw new IllegalStateException("nyi:" + operation);
|
||||
}
|
||||
}
|
||||
|
||||
public static void generateJLCGDF(ClassWriter cw, String classname, String fieldname, String methodname) {
|
||||
FieldVisitor fv = cw.visitField(ACC_PUBLIC_STATIC, fieldname, "Ljava/lang/reflect/Method;", null, null);
|
||||
fv.visitEnd();
|
||||
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC, fieldname,
|
||||
"(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/reflect/Field;", null,
|
||||
new String[] { "java/lang/NoSuchFieldException" });
|
||||
mv.visitCode();
|
||||
Label l0 = new Label();
|
||||
Label l1 = new Label();
|
||||
Label l2 = new Label();
|
||||
mv.visitTryCatchBlock(l0, l1, l2, "java/lang/reflect/InvocationTargetException");
|
||||
Label l3 = new Label();
|
||||
mv.visitTryCatchBlock(l0, l1, l3, "java/lang/Exception");
|
||||
Label l4 = new Label();
|
||||
mv.visitLabel(l4);
|
||||
mv.visitFieldInsn(GETSTATIC, classname, fieldname, "Ljava/lang/reflect/Method;");
|
||||
mv.visitJumpInsn(IFNONNULL, l0);
|
||||
Label l5 = new Label();
|
||||
mv.visitLabel(l5);
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitVarInsn(ALOAD, 1);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", methodname, "(Ljava/lang/String;)Ljava/lang/reflect/Field;");
|
||||
mv.visitInsn(ARETURN);
|
||||
mv.visitLabel(l0);
|
||||
mv.visitFieldInsn(GETSTATIC, classname, fieldname, "Ljava/lang/reflect/Method;");
|
||||
mv.visitInsn(ACONST_NULL);
|
||||
mv.visitInsn(ICONST_2);
|
||||
mv.visitTypeInsn(ANEWARRAY, "java/lang/Object");
|
||||
mv.visitInsn(DUP);
|
||||
mv.visitInsn(ICONST_0);
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitInsn(AASTORE);
|
||||
mv.visitInsn(DUP);
|
||||
mv.visitInsn(ICONST_1);
|
||||
mv.visitVarInsn(ALOAD, 1);
|
||||
mv.visitInsn(AASTORE);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/Method", "invoke",
|
||||
"(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;");
|
||||
mv.visitTypeInsn(CHECKCAST, "java/lang/reflect/Field");
|
||||
mv.visitLabel(l1);
|
||||
mv.visitInsn(ARETURN);
|
||||
mv.visitLabel(l2);
|
||||
mv.visitVarInsn(ASTORE, 2);
|
||||
Label l6 = new Label();
|
||||
mv.visitLabel(l6);
|
||||
mv.visitVarInsn(ALOAD, 2);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "getCause", "()Ljava/lang/Throwable;");
|
||||
mv.visitTypeInsn(INSTANCEOF, "java/lang/NoSuchFieldException");
|
||||
Label l7 = new Label();
|
||||
mv.visitJumpInsn(IFEQ, l7);
|
||||
Label l8 = new Label();
|
||||
mv.visitLabel(l8);
|
||||
mv.visitVarInsn(ALOAD, 2);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "getCause", "()Ljava/lang/Throwable;");
|
||||
mv.visitTypeInsn(CHECKCAST, "java/lang/NoSuchFieldException");
|
||||
mv.visitInsn(ATHROW);
|
||||
mv.visitLabel(l3);
|
||||
mv.visitVarInsn(ASTORE, 2);
|
||||
mv.visitLabel(l7);
|
||||
mv.visitInsn(ACONST_NULL);
|
||||
mv.visitInsn(ARETURN);
|
||||
Label l9 = new Label();
|
||||
mv.visitLabel(l9);
|
||||
// mv.visitLocalVariable("clazz", "Ljava/lang/Class;", "Ljava/lang/Class<*>;", l4, l9, 0);
|
||||
// mv.visitLocalVariable("fieldname", "Ljava/lang/String;", null, l4, l9, 1);
|
||||
// mv.visitLocalVariable("ite", "Ljava/lang/reflect/InvocationTargetException;", null, l6, l3, 2);
|
||||
mv.visitMaxs(6, 3);
|
||||
mv.visitEnd();
|
||||
}
|
||||
|
||||
public static void generateJLCGetXXXMethods(ClassWriter cw, String classname, String variant) {
|
||||
if (variant.equals("getDeclaredMethods")) {
|
||||
generateJLCGDMS(cw, classname, "__sljlcgdms", "getDeclaredMethods");
|
||||
} else if (variant.equals("getMethods")) {
|
||||
generateJLCGDMS(cw, classname, "__sljlcgms", "getMethods");
|
||||
} else {
|
||||
throw new IllegalStateException(variant);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO remove extraneous visits to things like lvar names
|
||||
public static void generateJLCGDMS(ClassWriter cw, String classname, String field, String methodname) {
|
||||
FieldVisitor fv = cw.visitField(ACC_PUBLIC + ACC_STATIC, field, "Ljava/lang/reflect/Method;", null, null);
|
||||
fv.visitEnd();
|
||||
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC, field, "(Ljava/lang/Class;)[Ljava/lang/reflect/Method;", null,
|
||||
null);
|
||||
mv.visitCode();
|
||||
Label l0 = new Label();
|
||||
Label l1 = new Label();
|
||||
Label l2 = new Label();
|
||||
mv.visitTryCatchBlock(l0, l1, l2, "java/lang/Exception");
|
||||
Label l3 = new Label();
|
||||
mv.visitLabel(l3);
|
||||
mv.visitFieldInsn(GETSTATIC, classname, field, "Ljava/lang/reflect/Method;");
|
||||
mv.visitJumpInsn(IFNONNULL, l0);
|
||||
Label l4 = new Label();
|
||||
mv.visitLabel(l4);
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", methodname, "()[Ljava/lang/reflect/Method;");
|
||||
mv.visitInsn(ARETURN);
|
||||
mv.visitLabel(l0);
|
||||
mv.visitFieldInsn(GETSTATIC, classname, field, "Ljava/lang/reflect/Method;");
|
||||
mv.visitInsn(ACONST_NULL);
|
||||
mv.visitInsn(ICONST_1);
|
||||
mv.visitTypeInsn(ANEWARRAY, "java/lang/Object");
|
||||
mv.visitInsn(DUP);
|
||||
mv.visitInsn(ICONST_0);
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitInsn(AASTORE);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/Method", "invoke",
|
||||
"(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;");
|
||||
mv.visitTypeInsn(CHECKCAST, "[Ljava/lang/reflect/Method;");
|
||||
mv.visitLabel(l1);
|
||||
mv.visitInsn(ARETURN);
|
||||
mv.visitLabel(l2);
|
||||
mv.visitVarInsn(ASTORE, 1);
|
||||
Label l5 = new Label();
|
||||
mv.visitLabel(l5);
|
||||
mv.visitInsn(ACONST_NULL);
|
||||
mv.visitInsn(ARETURN);
|
||||
Label l6 = new Label();
|
||||
mv.visitLabel(l6);
|
||||
// mv.visitLocalVariable("clazz", "Ljava/lang/Class;", "Ljava/lang/Class<*>;", l3, l6, 0);
|
||||
// mv.visitLocalVariable("e", "Ljava/lang/Exception;", null, l5, l6, 1);
|
||||
mv.visitMaxs(6, 2);
|
||||
mv.visitEnd();
|
||||
}
|
||||
|
||||
public static void generateJLCGDFS(ClassWriter cw, String classname) {
|
||||
FieldVisitor fv = cw.visitField(ACC_PUBLIC_STATIC, "__sljlcgdfs", "Ljava/lang/reflect/Method;", null, null);
|
||||
fv.visitEnd();
|
||||
|
||||
MethodVisitor mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC, "__sljlcgdfs", "(Ljava/lang/Class;)[Ljava/lang/reflect/Field;",
|
||||
null, null);
|
||||
mv.visitCode();
|
||||
Label l0 = new Label();
|
||||
Label l1 = new Label();
|
||||
Label l2 = new Label();
|
||||
mv.visitTryCatchBlock(l0, l1, l2, "java/lang/Exception");
|
||||
Label l3 = new Label();
|
||||
mv.visitLabel(l3);
|
||||
mv.visitFieldInsn(GETSTATIC, classname, "__sljlcgdfs", "Ljava/lang/reflect/Method;");
|
||||
mv.visitJumpInsn(IFNONNULL, l0);
|
||||
Label l4 = new Label();
|
||||
mv.visitLabel(l4);
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getDeclaredFields", "()[Ljava/lang/reflect/Field;");
|
||||
mv.visitInsn(ARETURN);
|
||||
mv.visitLabel(l0);
|
||||
mv.visitFieldInsn(GETSTATIC, classname, "__sljlcgdfs", "Ljava/lang/reflect/Method;");
|
||||
mv.visitInsn(ACONST_NULL);
|
||||
mv.visitInsn(ICONST_1);
|
||||
mv.visitTypeInsn(ANEWARRAY, "java/lang/Object");
|
||||
mv.visitInsn(DUP);
|
||||
mv.visitInsn(ICONST_0);
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitInsn(AASTORE);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/Method", "invoke",
|
||||
"(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;");
|
||||
mv.visitTypeInsn(CHECKCAST, "[Ljava/lang/reflect/Field;");
|
||||
mv.visitLabel(l1);
|
||||
mv.visitInsn(ARETURN);
|
||||
mv.visitLabel(l2);
|
||||
mv.visitVarInsn(ASTORE, 1);
|
||||
Label l5 = new Label();
|
||||
mv.visitLabel(l5);
|
||||
mv.visitInsn(ACONST_NULL);
|
||||
mv.visitInsn(ARETURN);
|
||||
Label l6 = new Label();
|
||||
mv.visitLabel(l6);
|
||||
// mv.visitLocalVariable("clazz", "Ljava/lang/Class;", "Ljava/lang/Class<*>;", l3, l6, 0);
|
||||
// mv.visitLocalVariable("e", "Ljava/lang/Exception;", null, l5, l6, 1);
|
||||
mv.visitMaxs(6, 2);
|
||||
mv.visitEnd();
|
||||
}
|
||||
|
||||
// Can be useful for debugging, insert printlns
|
||||
// private static void insertPrintln(MethodVisitor mv, String message) {
|
||||
// mv.visitFieldInsn(GETSTATIC, "java/lang/System", "err", "Ljava/io/PrintStream;");
|
||||
// mv.visitLdcInsn(message);
|
||||
// mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.security.CodeSource;
|
||||
import java.security.ProtectionDomain;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import org.springsource.loaded.agent.ReloadDecision;
|
||||
|
||||
|
||||
/**
|
||||
* This is not a 'default' plugin, it must be registered by specifying the following on the springloaded option:
|
||||
* "plugins=org.springsource.loaded.SystemPropertyConfiguredIsReloadableTypePlugin". The behaviour of this plugin is configured by a
|
||||
* system property that is constantly checked (not cached), this property determines whether files in certain paths are reloadable
|
||||
* or not.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.7.3
|
||||
*/
|
||||
public class SystemPropertyConfiguredIsReloadableTypePlugin implements IsReloadableTypePlugin {
|
||||
|
||||
public final static boolean debug;
|
||||
|
||||
static {
|
||||
boolean value = false;
|
||||
try {
|
||||
value = System.getProperty("springloaded.directoriesContainingReloadableCode.debug", "false").equalsIgnoreCase("true");
|
||||
} catch (Exception e) {
|
||||
|
||||
}
|
||||
debug = value;
|
||||
}
|
||||
|
||||
public SystemPropertyConfiguredIsReloadableTypePlugin() {
|
||||
if (debug) {
|
||||
System.out.println("SystemPropertyConfiguredIsReloadableTypePlugin: instantiated");
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> includes = new ArrayList<String>();
|
||||
private List<String> excludes = new ArrayList<String>();
|
||||
private String mostRecentReloadableDirs = null;
|
||||
|
||||
// TODO need try/catch protection when calling plugins, in case of bad ones
|
||||
public ReloadDecision shouldBeMadeReloadable(String typename, ProtectionDomain protectionDomain, byte[] bytes) {
|
||||
if (debug) {
|
||||
System.out.println("SystemPropertyConfiguredIsReloadableTypePlugin: entered, for typename " + typename);
|
||||
}
|
||||
if (protectionDomain == null) {
|
||||
return ReloadDecision.PASS;
|
||||
}
|
||||
String reloadableDirs = System.getProperty("springloaded.directoriesContainingReloadableCode");
|
||||
if (debug) {
|
||||
System.out.println("SystemPropertyConfiguredIsReloadableTypePlugin: reloadableDirs=" + reloadableDirs);
|
||||
}
|
||||
if (reloadableDirs == null) {
|
||||
return ReloadDecision.PASS;
|
||||
} else {
|
||||
if (mostRecentReloadableDirs != reloadableDirs) {
|
||||
synchronized (includes) {
|
||||
if (mostRecentReloadableDirs != reloadableDirs) {
|
||||
includes.clear();
|
||||
excludes.clear();
|
||||
// update our cached information
|
||||
StringTokenizer st = new StringTokenizer(reloadableDirs, ",");
|
||||
while (st.hasMoreTokens()) {
|
||||
String nextDir = st.nextToken();
|
||||
boolean isNot = nextDir.charAt(0) == '!';
|
||||
if (isNot) {
|
||||
excludes.add(nextDir.substring(1));
|
||||
} else {
|
||||
includes.add(nextDir);
|
||||
}
|
||||
}
|
||||
mostRecentReloadableDirs = reloadableDirs;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Typical example:
|
||||
// typename = com/vmware/rabbit/HomeController
|
||||
// codeSource.getLocation() = file:/Users/aclement/springsource/tc-server-developer-2.1.1.RELEASE/spring-insight-instance/wtpwebapps/hello-rabbit-client/WEB-INF/classes/com/vmware/rabbit/HomeController.class
|
||||
CodeSource codeSource = protectionDomain.getCodeSource();
|
||||
if (codeSource == null || codeSource.getLocation() == null) {
|
||||
return ReloadDecision.NO; // nothing to watch...
|
||||
// if (debug) {
|
||||
// System.out.println("SystemPropertyConfiguredIsReloadableTypePlugin: " + typename + " does not have a codeSource");
|
||||
// }
|
||||
} else {
|
||||
if (debug) {
|
||||
System.out.println("SystemPropertyConfiguredIsReloadableTypePlugin: " + typename + " codeSource.getLocation() is "
|
||||
+ codeSource.getLocation());
|
||||
}
|
||||
}
|
||||
try {
|
||||
URI uri = codeSource.getLocation().toURI();
|
||||
File file = new File(uri);
|
||||
String path = file.toString();
|
||||
|
||||
synchronized (includes) {
|
||||
for (String exclude : excludes) {
|
||||
if (path.contains(exclude)) {
|
||||
if (debug) {
|
||||
System.out.println("SystemPropertyConfiguredIsReloadableTypePlugin: " + typename
|
||||
+ " is not being made reloadable");
|
||||
}
|
||||
return ReloadDecision.NO;
|
||||
}
|
||||
}
|
||||
|
||||
for (String include : includes) {
|
||||
if (path.contains(include)) {
|
||||
if (debug) {
|
||||
System.out.println("SystemPropertyConfiguredIsReloadableTypePlugin: " + typename
|
||||
+ " is being made reloadable");
|
||||
}
|
||||
return ReloadDecision.YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// StringTokenizer st = new StringTokenizer(reloadableDirs, ",");
|
||||
// while (st.hasMoreTokens()) {
|
||||
// String nextDir = st.nextToken();
|
||||
// boolean isNot = nextDir.charAt(0) == '!';
|
||||
// if (isNot)
|
||||
// nextDir = nextDir.substring(1);
|
||||
// if (path.contains(nextDir)) {
|
||||
// if (isNot) {
|
||||
// if (debug) {
|
||||
// System.out.println("SystemPropertyConfiguredIsReloadableTypePlugin: " + typename
|
||||
// + " is not being made reloadable");
|
||||
// }
|
||||
// return ReloadDecision.NO;
|
||||
// } else {
|
||||
// if (debug) {
|
||||
// System.out.println("SystemPropertyConfiguredIsReloadableTypePlugin: " + typename
|
||||
// + " is being made reloadable");
|
||||
// }
|
||||
// return ReloadDecision.YES;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
} catch (URISyntaxException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IllegalArgumentException iae) {
|
||||
// grails-9654
|
||||
// On File.<init>() call:
|
||||
// IAE: URI is not hierarchical
|
||||
if (debug) {
|
||||
try {
|
||||
System.out.println("IllegalArgumentException: URI is not hierarchical, uri is "+codeSource.getLocation().toURI());
|
||||
} catch (URISyntaxException use) {
|
||||
System.out.println("IllegalArgumentException: URI is not hierarchical, uri is "+codeSource.getLocation());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (debug) {
|
||||
System.out.println("SystemPropertyConfiguredIsReloadableTypePlugin: " + typename + " is being PASSed on");
|
||||
}
|
||||
return ReloadDecision.PASS;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.objectweb.asm.tree.FieldNode;
|
||||
import org.objectweb.asm.tree.MethodNode;
|
||||
|
||||
/**
|
||||
* Encapsulates what has changed between two versions of a type - it is used to determine if a reload is possible and also passed on
|
||||
* events related to reloading so that the plugins can tailor their actions based on what prevented reloading. The various
|
||||
* <tt>hasXXX</tt> and <tt>getXXX</tt> methods should be used to query it.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class TypeDelta {
|
||||
private long changed;
|
||||
|
||||
private final static long CHANGED_VERSION = 0x00000001;
|
||||
private final static long CHANGED_ACCESS = 0x00000002;
|
||||
private final static long CHANGED_SUPERNAME = 0x00000004;
|
||||
private final static long CHANGED_INTERFACES = 0x00000008;
|
||||
private final static long CHANGED_NAME = 0x00000010;
|
||||
private final static long CHANGED_SIGNATURE = 0x00000020;
|
||||
private final static long CHANGED_TYPE_MASK = CHANGED_VERSION | CHANGED_ACCESS | CHANGED_SUPERNAME | CHANGED_INTERFACES
|
||||
| CHANGED_NAME | CHANGED_SIGNATURE;
|
||||
|
||||
private final static long CHANGED_NEWFIELDS = 0x00000040;
|
||||
private final static long CHANGED_LOSTFIELDS = 0x00000080;
|
||||
private final static long CHANGED_CHANGEDFIELDS = 0x00000100;
|
||||
private final static long CHANGED_FIELD_MASK = CHANGED_NEWFIELDS | CHANGED_LOSTFIELDS | CHANGED_CHANGEDFIELDS;
|
||||
|
||||
private final static long CHANGED_NEWMETHODS = 0x00000200;
|
||||
private final static long CHANGED_LOSTMETHODS = 0x00000400;
|
||||
private final static long CHANGED_CHANGEDMETHODS = 0x0000800;
|
||||
private final static long CHANGED_METHOD_MASK = CHANGED_NEWMETHODS | CHANGED_LOSTMETHODS | CHANGED_CHANGEDMETHODS;
|
||||
|
||||
private final static long CHANGES = CHANGED_TYPE_MASK | CHANGED_FIELD_MASK | CHANGED_METHOD_MASK;
|
||||
|
||||
public int oAccess, nAccess;
|
||||
public int oVersion, nVersion;
|
||||
public String oName, nName;
|
||||
public String oSignature, nSignature;
|
||||
public String oSuperName, nSuperName;
|
||||
public List<String> oInterfaces, nInterfaces;
|
||||
Map<String, FieldNode> brandNewFields;
|
||||
Map<String, FieldNode> lostFields;
|
||||
Map<String, FieldDelta> changedFields;
|
||||
Map<String, MethodNode> brandNewMethods;
|
||||
Map<String, MethodNode> lostMethods;
|
||||
Map<String, MethodDelta> changedMethods;
|
||||
|
||||
public String toString() {
|
||||
StringBuilder s = new StringBuilder();
|
||||
s.append("TypeDelta Summary\n");
|
||||
// type declaration
|
||||
s.append("TypeDeclaration changes:\n");
|
||||
if (hasTypeVersionChanged()) {
|
||||
s.append("typeversion changed: o=" + oVersion + " n=" + nVersion + "\n");
|
||||
}
|
||||
if (hasTypeAccessChanged()) {
|
||||
s.append("typeaccess changed: o=" + oAccess + " n=" + nAccess + "\n");
|
||||
}
|
||||
if (hasTypeSupertypeChanged()) {
|
||||
s.append("typesupertype changed: o=" + oSuperName + " n=" + nSuperName + "\n");
|
||||
}
|
||||
if (hasTypeInterfacesChanged()) {
|
||||
s.append("typeinterfaces changed: o=" + oInterfaces + " n=" + nInterfaces + "\n");
|
||||
}
|
||||
if (hasTypeNameChanged()) {
|
||||
s.append("typename changed: o=" + oName + " n=" + nName + "\n");
|
||||
}
|
||||
if (hasTypeSignatureChanged()) {
|
||||
s.append("typesignature changed: o=" + oSignature + " n=" + nSignature + "\n");
|
||||
}
|
||||
// ...
|
||||
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
void setTypeAccessChange(int oldAccess, int newAccess) {
|
||||
this.oAccess = oldAccess;
|
||||
this.nAccess = newAccess;
|
||||
this.changed |= CHANGED_ACCESS;
|
||||
}
|
||||
|
||||
void setTypeNameChange(String oldName, String newName) {
|
||||
this.oName = oldName;
|
||||
this.nName = newName;
|
||||
this.changed |= CHANGED_NAME;
|
||||
}
|
||||
|
||||
void setTypeSignatureChange(String oldSignature, String newSignature) {
|
||||
this.oSignature = oldSignature;
|
||||
this.nSignature = newSignature;
|
||||
this.changed |= CHANGED_SIGNATURE;
|
||||
}
|
||||
|
||||
// public void setTypeVersionChange(int oldVersion, int newVersion) {
|
||||
// this.oVersion = oldVersion;
|
||||
// this.nVersion = newVersion;
|
||||
// this.changed |= CHANGED_VERSION;
|
||||
// }
|
||||
|
||||
void setTypeSuperNameChange(String oldSuperName, String newSuperName) {
|
||||
this.oSuperName = oldSuperName;
|
||||
this.nSuperName = newSuperName;
|
||||
this.changed |= CHANGED_SUPERNAME;
|
||||
}
|
||||
|
||||
void setTypeInterfacesChange(List<String> oldInterfaces, List<String> newInterfaces) {
|
||||
this.oInterfaces = oldInterfaces;
|
||||
this.nInterfaces = newInterfaces;
|
||||
this.changed |= CHANGED_INTERFACES;
|
||||
}
|
||||
|
||||
void addNewField(FieldNode nField) {
|
||||
if (brandNewFields == null) {
|
||||
brandNewFields = new HashMap<String, FieldNode>();
|
||||
}
|
||||
brandNewFields.put(nField.name, nField);
|
||||
this.changed |= CHANGED_NEWFIELDS;
|
||||
}
|
||||
|
||||
void addLostField(FieldNode lField) {
|
||||
if (lostFields == null) {
|
||||
lostFields = new HashMap<String, FieldNode>();
|
||||
}
|
||||
lostFields.put(lField.name, lField);
|
||||
this.changed |= CHANGED_LOSTFIELDS;
|
||||
}
|
||||
|
||||
void addChangedField(FieldDelta fd) {
|
||||
if (changedFields == null) {
|
||||
changedFields = new HashMap<String, FieldDelta>();
|
||||
}
|
||||
changedFields.put(fd.name, fd);
|
||||
this.changed |= CHANGED_CHANGEDFIELDS;
|
||||
}
|
||||
|
||||
void addNewMethod(MethodNode nMethod) {
|
||||
if (brandNewMethods == null) {
|
||||
brandNewMethods = new HashMap<String, MethodNode>();
|
||||
}
|
||||
brandNewMethods.put(nMethod.name + nMethod.desc, nMethod);
|
||||
this.changed |= CHANGED_NEWMETHODS;
|
||||
}
|
||||
|
||||
void addLostMethod(MethodNode nMethod) {
|
||||
if (lostMethods == null) {
|
||||
lostMethods = new HashMap<String, MethodNode>();
|
||||
}
|
||||
lostMethods.put(nMethod.name + nMethod.desc, nMethod);
|
||||
this.changed |= CHANGED_LOSTMETHODS;
|
||||
}
|
||||
|
||||
void addChangedMethod(MethodDelta md) {
|
||||
if (changedMethods == null) {
|
||||
changedMethods = new HashMap<String, MethodDelta>();
|
||||
}
|
||||
changedMethods.put(md.name + md.desc, md);
|
||||
this.changed |= CHANGED_CHANGEDMETHODS;
|
||||
}
|
||||
|
||||
public boolean hasTypeDeclarationChanged() {
|
||||
return (changed & CHANGED_TYPE_MASK) != 0;
|
||||
}
|
||||
|
||||
public boolean hasTypeNameChanged() {
|
||||
return (changed & CHANGED_NAME) != 0;
|
||||
}
|
||||
|
||||
public boolean hasTypeVersionChanged() {
|
||||
return (changed & CHANGED_VERSION) != 0;
|
||||
}
|
||||
|
||||
public boolean hasTypeAccessChanged() {
|
||||
return (changed & CHANGED_ACCESS) != 0;
|
||||
}
|
||||
|
||||
public boolean hasTypeSupertypeChanged() {
|
||||
return (changed & CHANGED_SUPERNAME) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the list of interfaces implemented by this type has changed
|
||||
*/
|
||||
public boolean hasTypeInterfacesChanged() {
|
||||
return (changed & CHANGED_INTERFACES) != 0;
|
||||
}
|
||||
|
||||
public boolean hasTypeSignatureChanged() {
|
||||
return (changed & CHANGED_SIGNATURE) != 0;
|
||||
}
|
||||
|
||||
public boolean hasAnythingChanged() {
|
||||
return (changed & CHANGES) != 0;
|
||||
}
|
||||
|
||||
public boolean hasNewFields() {
|
||||
return (changed & CHANGED_NEWFIELDS) != 0;
|
||||
}
|
||||
|
||||
public boolean hasLostFields() {
|
||||
return (changed & CHANGED_LOSTFIELDS) != 0;
|
||||
}
|
||||
|
||||
public boolean haveFieldsChangedOrBeenAddedOrRemoved() {
|
||||
return (changed & CHANGED_FIELD_MASK) != 0;
|
||||
}
|
||||
|
||||
public boolean haveFieldsChanged() {
|
||||
return (changed & CHANGED_CHANGEDFIELDS) != 0;
|
||||
}
|
||||
|
||||
public boolean haveMethodsChanged() {
|
||||
return (changed & CHANGED_CHANGEDMETHODS) != 0;
|
||||
}
|
||||
|
||||
public boolean haveMethodsChangedOrBeenAddedOrRemoved() {
|
||||
return (changed & CHANGED_METHOD_MASK) != 0;
|
||||
}
|
||||
|
||||
public boolean haveMethodsBeenAdded() {
|
||||
return (changed & CHANGED_NEWMETHODS) != 0;
|
||||
}
|
||||
|
||||
public boolean haveMethodsBeenDeleted() {
|
||||
return (changed & CHANGED_LOSTMETHODS) != 0;
|
||||
}
|
||||
|
||||
public Map<String, FieldNode> getNewFields() {
|
||||
return brandNewFields;
|
||||
}
|
||||
|
||||
public Map<String, FieldNode> getLostFields() {
|
||||
return lostFields;
|
||||
}
|
||||
|
||||
public Map<String, FieldDelta> getChangedFields() {
|
||||
return changedFields;
|
||||
}
|
||||
|
||||
public Map<String, MethodDelta> getChangedMethods() {
|
||||
return changedMethods;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,330 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Encapsulates the information about a type relevant to reloading. The TypeDescriptor for a type is sometimes extracted whilst
|
||||
* performing some other operation (eg. {@link InterfaceExtractor}) but can also be retrieved directly using
|
||||
* {@link TypeDescriptorExtractor}.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class TypeDescriptor implements Constants {
|
||||
|
||||
private final int modifiers;
|
||||
final String typename; // slashed
|
||||
final String supertypeName; // slashed
|
||||
final String[] superinterfaceNames; // slashed // empty array if there are none
|
||||
private final MethodMember[] constructors; // empty array if there are none (but this doesn't ever happen!)
|
||||
private final MethodMember[] methods; // empty array if there are none
|
||||
private final MethodMember[] nonprivateMethods; // empty array if there are none
|
||||
private final FieldMember[] fields; // empty array if there are none
|
||||
private final FieldMember[] fieldsRequiringAccessors; // empty array if there are none
|
||||
private List<String> finalInHierarchy; // nameAndDescriptor strings for methods final in the hierarchy (e.g. ordinal()I for an enum)
|
||||
private final TypeRegistry registry;
|
||||
private final boolean isReloadable;
|
||||
private final boolean hasClinit;
|
||||
|
||||
private final static int IS_GROOVY_TYPE = 0x0001;
|
||||
private int bits = 0x0000;
|
||||
|
||||
private ReloadableType reloadableType;
|
||||
private int nextId = 0;
|
||||
|
||||
public TypeDescriptor(String slashedTypeName, String supertypeName, String[] superinterfaceNames, int modifiers,
|
||||
List<? extends MethodMember> constructors, List<MethodMember> methods, List<? extends FieldMember> fields,
|
||||
List<? extends FieldMember> fieldsRequiringAccessors, boolean isReloadable, TypeRegistry registry, boolean hasClinit,
|
||||
List<String> finalInHierarchy) {
|
||||
this.typename = slashedTypeName;
|
||||
this.supertypeName = supertypeName;
|
||||
this.superinterfaceNames = (superinterfaceNames == null ? NO_STRINGS : superinterfaceNames);
|
||||
this.finalInHierarchy = finalInHierarchy;
|
||||
this.modifiers = modifiers;
|
||||
this.fields = fields.size() == 0 ? FieldMember.NONE : fields.toArray(new FieldMember[fields.size()]);
|
||||
this.fieldsRequiringAccessors = fieldsRequiringAccessors.size() == 0 ? FieldMember.NONE : fieldsRequiringAccessors
|
||||
.toArray(new FieldMember[fieldsRequiringAccessors.size()]);
|
||||
this.constructors = constructors.size() == 0 ? MethodMember.NONE : constructors.toArray(new MethodMember[constructors
|
||||
.size()]);
|
||||
this.methods = methods.size() == 0 ? MethodMember.NONE : methods.toArray(new MethodMember[methods.size()]);
|
||||
this.nonprivateMethods = filterNonPrivateMethods(this.methods);
|
||||
this.isReloadable = isReloadable;
|
||||
this.registry = registry;
|
||||
this.hasClinit = hasClinit;
|
||||
allocateIds();
|
||||
}
|
||||
|
||||
private static MethodMember[] filterNonPrivateMethods(MethodMember[] allMethods) {
|
||||
List<MethodMember> result = null;
|
||||
for (MethodMember method : allMethods) {
|
||||
if (!method.isPrivate()) {
|
||||
if (result == null) {
|
||||
result = new ArrayList<MethodMember>();
|
||||
}
|
||||
result.add(method);
|
||||
}
|
||||
}
|
||||
if (result == null) {
|
||||
return MethodMember.NONE;
|
||||
} else {
|
||||
return result.toArray(new MethodMember[result.size()]);
|
||||
}
|
||||
}
|
||||
|
||||
private void allocateIds() {
|
||||
// Give the methods awareness of their index
|
||||
for (MethodMember method : methods) {
|
||||
method.setId(nextId++);
|
||||
}
|
||||
}
|
||||
|
||||
public MethodMember[] getMethods() {
|
||||
return methods;
|
||||
}
|
||||
|
||||
public MethodMember[] getConstructors() {
|
||||
return constructors;
|
||||
}
|
||||
|
||||
public FieldMember[] getFields() {
|
||||
return fields;
|
||||
}
|
||||
|
||||
public FieldMember[] getFieldsRequiringAccessors() {
|
||||
return fieldsRequiringAccessors;
|
||||
}
|
||||
|
||||
public int getModifiers() {
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the (slashed) type name
|
||||
*/
|
||||
public String getName() {
|
||||
return typename;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the (slashed) supertype name
|
||||
*/
|
||||
public String getSupertypeName() {
|
||||
return supertypeName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array of (slashed) superinterface names (or an empty array if none)
|
||||
*/
|
||||
public String[] getSuperinterfacesName() {
|
||||
return superinterfaceNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this descriptor defines the specified method. A strict check on all aspects of the method - names/exceptions/flags,
|
||||
* etc.
|
||||
*
|
||||
* @return true if this descriptor defines the specified method.
|
||||
*/
|
||||
public boolean defines(MethodMember method) {
|
||||
for (MethodMember existingMethod : methods) {
|
||||
// make sure it *really* defines it (i.e. it is not a catcher)
|
||||
if (!MethodMember.isCatcher(existingMethod) && existingMethod.equals(method)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this descriptor defines a method with the specified name and descriptor. Return the method if it is found.
|
||||
* Modifiers, generic signature and exceptions are ignored in this search.
|
||||
*/
|
||||
public MethodMember getByDescriptor(String name, String descriptor) {
|
||||
for (MethodMember existingMethod : methods) {
|
||||
if (existingMethod.getName().equals(name) && existingMethod.getDescriptor().equals(descriptor)) {
|
||||
return existingMethod;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public MethodMember getByNameAndDescriptor(String nameAndDescriptor) {
|
||||
for (MethodMember existingMethod : methods) {
|
||||
if (nameAndDescriptor.startsWith(existingMethod.getName())
|
||||
&& nameAndDescriptor.endsWith(existingMethod.getDescriptor())) {
|
||||
return existingMethod;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if this type descriptor has been created for a reloadable type
|
||||
*/
|
||||
public boolean isReloadable() {
|
||||
return isReloadable;
|
||||
}
|
||||
|
||||
public MethodMember getMethod(int methodId) {
|
||||
// Should never be an AIOOBE if the woven code is behaving
|
||||
return methods[methodId];
|
||||
}
|
||||
|
||||
public MethodMember getConstructor(int ctorId) {
|
||||
// Should never be an AIOOBE if the woven code is behaving
|
||||
return constructors[ctorId];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the type is an interface
|
||||
*/
|
||||
public boolean isInterface() {
|
||||
return (modifiers & ACC_INTERFACE) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the type is an annotation
|
||||
*/
|
||||
public boolean isAnnotation() {
|
||||
return (modifiers & ACC_ANNOTATION) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the type is an enum
|
||||
*/
|
||||
public boolean isEnum() {
|
||||
return (modifiers & ACC_ENUM) != 0;
|
||||
}
|
||||
|
||||
public boolean definesNonPrivate(String nameAndDescriptor) {
|
||||
for (MethodMember existingMethod : nonprivateMethods) {
|
||||
if (existingMethod.nameAndDescriptor.equals(nameAndDescriptor)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isFinalInHierarchy(String nad) {
|
||||
return finalInHierarchy.contains(nad);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for a field on this type descriptor - do not try supertypes. This lookup does not differentiate between
|
||||
* static/instance fields.
|
||||
*
|
||||
* @param name
|
||||
* @return a FieldMember if the field is found, otherwise null
|
||||
*/
|
||||
public FieldMember getField(String name) {
|
||||
for (FieldMember field : fields) {
|
||||
if (field.getName().equals(name)) {
|
||||
return field;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public ReloadableType getReloadableType() {
|
||||
if (!isReloadable) {
|
||||
return null;
|
||||
}
|
||||
if (reloadableType == null) {
|
||||
reloadableType = registry.getReloadableType(this.typename);
|
||||
if (reloadableType == null) {
|
||||
throw new IllegalStateException("There is no ReloadableType instance for " + typename);
|
||||
}
|
||||
}
|
||||
return reloadableType;
|
||||
}
|
||||
|
||||
public TypeRegistry getTypeRegistry() {
|
||||
return registry;
|
||||
}
|
||||
|
||||
// could be worth caching if used for more than error messages...
|
||||
public String getDottedName() {
|
||||
return getName().replace('/', '.');
|
||||
}
|
||||
|
||||
public MethodMember getConstructor(String desc) {
|
||||
for (MethodMember ctor : constructors) {
|
||||
String d = ctor.getDescriptor();
|
||||
if (d.equals(desc)) {
|
||||
return ctor;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isGroovyType() {
|
||||
return (bits & IS_GROOVY_TYPE) != 0;
|
||||
}
|
||||
|
||||
public void setIsGroovyType(boolean b) {
|
||||
bits |= IS_GROOVY_TYPE;
|
||||
}
|
||||
|
||||
public boolean hasClinit() {
|
||||
return hasClinit;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuilder s = new StringBuilder();
|
||||
s.append("TypeDescriptor: name=" + typename + " superclass=" + supertypeName + " superinterfaces=" + interfacesToString());
|
||||
s.append(" flags=0x" + Integer.toHexString(modifiers).toUpperCase()).append("\n");
|
||||
s.append("Fields: #" + fields.length + "\n" + fieldsToString());
|
||||
s.append("Constructors:#" + constructors.length + "\n" + methodsToString(constructors));
|
||||
s.append("Methods:#" + methods.length + "\n" + methodsToString(methods));
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
private String fieldsToString() {
|
||||
StringBuilder s = new StringBuilder();
|
||||
int count = 0;
|
||||
for (FieldMember field : fields) {
|
||||
s.append(" field #" + Utils.toPaddedNumber((count++), 3)).append(' ').append(field.toString()).append('\n');
|
||||
}
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
private String interfacesToString() {
|
||||
if (superinterfaceNames == null) {
|
||||
return "";
|
||||
} else {
|
||||
StringBuilder s = new StringBuilder();
|
||||
for (String superinterfaceName : superinterfaceNames) {
|
||||
s.append(superinterfaceName);
|
||||
s.append(" ");
|
||||
}
|
||||
return s.toString().trim();
|
||||
}
|
||||
}
|
||||
|
||||
public String methodsToString(MethodMember[] methods) {
|
||||
StringBuilder s = new StringBuilder();
|
||||
int count = 0;
|
||||
for (MethodMember method : methods) {
|
||||
s.append(" method #" + Utils.toPaddedNumber((count++), 3)).append(' ').append(method.toString()).append(" ")
|
||||
.append(method.bitsToString()).append('\n');
|
||||
}
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,320 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.objectweb.asm.AnnotationVisitor;
|
||||
import org.objectweb.asm.Attribute;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.ClassVisitor;
|
||||
import org.objectweb.asm.FieldVisitor;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
/**
|
||||
* A type descriptor describes the type, methods, fields, etc - two type descriptors are comparable to discover what has changed
|
||||
* between versions.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class TypeDescriptorExtractor {
|
||||
|
||||
private TypeRegistry registry;
|
||||
|
||||
public TypeDescriptorExtractor(TypeRegistry registry) {
|
||||
this.registry = registry;
|
||||
}
|
||||
|
||||
public TypeDescriptor extract(byte[] bytes, boolean isReloadableType) {
|
||||
ClassReader fileReader = new ClassReader(bytes);
|
||||
ExtractionVisitor extractionVisitor = new ExtractionVisitor(isReloadableType);
|
||||
fileReader.accept(extractionVisitor, 0);
|
||||
return extractionVisitor.getTypeDescriptor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Visit a class and accumulate enough information to build a TypeDescriptor.
|
||||
*/
|
||||
class ExtractionVisitor implements ClassVisitor, Opcodes {
|
||||
|
||||
private boolean isReloadableType;
|
||||
private int flags;
|
||||
private String typename;
|
||||
private String superclassName;
|
||||
private String[] interfaceNames;
|
||||
private boolean isGroovy = false;
|
||||
private boolean hasClinit = false;
|
||||
private List<MethodMember> constructors = new ArrayList<MethodMember>();
|
||||
private List<MethodMember> methods = new ArrayList<MethodMember>();
|
||||
private List<FieldMember> fieldsRequiringAccessors = new ArrayList<FieldMember>();
|
||||
private List<FieldMember> fields = new ArrayList<FieldMember>();
|
||||
private List<String> finalInHierarchy = new ArrayList<String>();
|
||||
|
||||
public ExtractionVisitor(boolean isReloadableType) {
|
||||
this.isReloadableType = isReloadableType;
|
||||
}
|
||||
|
||||
|
||||
public TypeDescriptor getTypeDescriptor() {
|
||||
if (isReloadableType) {
|
||||
computeCatchers();
|
||||
}
|
||||
computeFieldsRequiringAccessors();
|
||||
computeClashes();
|
||||
TypeDescriptor td = new TypeDescriptor(typename, superclassName, interfaceNames, flags, constructors, methods, fields,
|
||||
fieldsRequiringAccessors, isReloadableType, registry, hasClinit, finalInHierarchy);
|
||||
if (isGroovy) {
|
||||
td.setIsGroovyType(true);
|
||||
}
|
||||
return td;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if there are clashes. A clash is where a static method takes the this reloadable type as its first parameter
|
||||
* but in all other ways is the same as an existing instance method. For example this instance method A.foo(String) clashes
|
||||
* with this static method A.foo(A, String). 'clashing' means the executor will have to do something to avoid a duplicate
|
||||
* method problem and we'll have to differentiate between the two.
|
||||
*/
|
||||
private void computeClashes() {
|
||||
String clashDescriptorPrefix = "(L" + typename + ";";
|
||||
for (MethodMember member : methods) {
|
||||
if (member.isStatic()) {
|
||||
String desc = member.descriptor;
|
||||
if (desc.startsWith(clashDescriptorPrefix)) {
|
||||
// might be a clash, need to check the instance methods
|
||||
for (MethodMember member2 : methods) {
|
||||
if (member2.name.equals(member.name)) {
|
||||
// really might be a clash
|
||||
String instanceParams = member2.descriptor;
|
||||
instanceParams = instanceParams.substring(1, instanceParams.indexOf(')') + 1);
|
||||
String staticParams = desc.substring(clashDescriptorPrefix.length(), desc.indexOf(')') + 1);
|
||||
if (instanceParams.equals(staticParams)) {
|
||||
// CLASH
|
||||
member.bits |= MethodMember.BIT_CLASH;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private TypeDescriptor getTypeDescriptorFor(String slashedname) {
|
||||
return registry.getDescriptorFor(slashedname);
|
||||
}
|
||||
|
||||
private TypeDescriptor findTypeDescriptor(TypeRegistry registry, String typename) {
|
||||
// follow the pattern for a classloader: recurse up trying to find it, then recurse down trying to load it
|
||||
TypeRegistry regToTry = registry;
|
||||
TypeDescriptor td = regToTry.getDescriptorForReloadableType(typename);
|
||||
while (td == null) {
|
||||
regToTry = regToTry.getParentRegistry();
|
||||
if (regToTry == null) {
|
||||
break;
|
||||
}
|
||||
td = regToTry.getDescriptorForReloadableType(typename);
|
||||
}
|
||||
if (td == null) {
|
||||
td = getTypeDescriptorFor(typename);
|
||||
}
|
||||
return td;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create catcher methods for methods from our super-hierarchy that we don't yet override (but may after the initial define
|
||||
* has happened).
|
||||
*/
|
||||
private void computeCatchers() {
|
||||
// When walking up the hierarchy we may hit a 'final' method which means we must not catch it.
|
||||
// The 'shouldNotCatch' list stores things we discover like this that should not be caught
|
||||
List<String> shouldNotCatch = new ArrayList<String>();
|
||||
|
||||
String type = superclassName;
|
||||
// Don't need catchers in interfaces
|
||||
if (Modifier.isInterface(this.flags)) {
|
||||
return;
|
||||
}
|
||||
while (type != null) {
|
||||
TypeDescriptor supertypeDescriptor = findTypeDescriptor(registry, type);
|
||||
// TODO review the need to create catchers for methods where the supertype is reloadable. In this situation we are already going to
|
||||
// be intercepting the call side of these methods so we don't need the catcher. Could be a large performance increase and reduction in
|
||||
// permgen, and simplification of stack traces
|
||||
// if (!supertypeDescriptor.isReloadable()) {
|
||||
for (MethodMember method : supertypeDescriptor.getMethods()) {
|
||||
if (shouldCatchMethod(method) && !shouldNotCatch.contains(method.getNameAndDescriptor())) {
|
||||
// don't need the catcher if method is already defined since when the existing method is rewritten
|
||||
// it will be kind of morphed into a catcher
|
||||
// TODO what about a private method that is overridden by a static method (same name/descriptor but not
|
||||
// an overrides relationship)
|
||||
// if (supertypeDescriptor.isGroovyType() && !isGroovy) {
|
||||
// if (method.getName().startsWith("super$")) {
|
||||
// continue;
|
||||
// }
|
||||
// }
|
||||
MethodMember found = null;
|
||||
for (MethodMember existingMethod : methods) {
|
||||
if (existingMethod.equalsApartFromModifiers(method)) {
|
||||
found = existingMethod;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found != null) {
|
||||
continue;
|
||||
}
|
||||
MethodMember catcherCopy = method.catcherCopyOf();
|
||||
// System.out.println("catcher is " + catcherCopy + " is groovy type? " + this.isGroovy);
|
||||
methods.add(catcherCopy);
|
||||
} else {
|
||||
if (method.isFinal()) {
|
||||
shouldNotCatch.add(method.getNameAndDescriptor());
|
||||
}
|
||||
}
|
||||
}
|
||||
// }
|
||||
type = supertypeDescriptor.supertypeName;
|
||||
}
|
||||
|
||||
// ought to look in interfaces *if* we are an abstract class
|
||||
if (Modifier.isAbstract(this.flags)/* && !Modifier.isInterface(this.flags)*/) {
|
||||
// abstract class
|
||||
for (String interfaceName : interfaceNames) {
|
||||
addCatchersForNonImplementedMethodsFrom(interfaceName);
|
||||
}
|
||||
}
|
||||
|
||||
finalInHierarchy.addAll(shouldNotCatch);
|
||||
}
|
||||
|
||||
private void addCatchersForNonImplementedMethodsFrom(String interfacename) {
|
||||
TypeDescriptor interfaceDescriptor = findTypeDescriptor(registry, interfacename);
|
||||
for (MethodMember method : interfaceDescriptor.getMethods()) {
|
||||
// If this class doesn't implement this interface method, add it
|
||||
boolean found = false;
|
||||
for (MethodMember existingMethod : methods) {
|
||||
if (existingMethod.equalsApartFromModifiers(method)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
methods.add(method.catcherCopyOfWithAbstractRemoved());
|
||||
}
|
||||
}
|
||||
for (String interfaceName : interfaceDescriptor.superinterfaceNames) {
|
||||
addCatchersForNonImplementedMethodsFrom(interfaceName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Field
|
||||
*/
|
||||
private void computeFieldsRequiringAccessors() {
|
||||
String type = superclassName;
|
||||
while (type != null) {
|
||||
TypeDescriptor supertypeDescriptor = findTypeDescriptor(registry, type);
|
||||
if (!supertypeDescriptor.isReloadable()) {
|
||||
for (FieldMember field : supertypeDescriptor.getFields()) {
|
||||
if (field.isProtected()) {
|
||||
boolean found = false;
|
||||
for (FieldMember existingField : fields) {
|
||||
if (existingField.getName().equals(field.getName())) {
|
||||
// no need for accessor... this type defines a field that overrides it
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
fieldsRequiringAccessors.add(field);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
type = supertypeDescriptor.supertypeName;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a method gets a catcher. Deliberately not catching final methods, static methods, private methods or
|
||||
* finalize()V.
|
||||
*
|
||||
* @return true if it should be caught
|
||||
*/
|
||||
private boolean shouldCatchMethod(MethodMember method) {
|
||||
return !(method.isPrivateStaticFinal() || (method.getName().equals("finalize") && method.getDescriptor().equals("()V")));
|
||||
}
|
||||
|
||||
public void visit(int version, int flags, String name, String signature, String superclassName, String[] interfaceNames) {
|
||||
this.flags = flags;
|
||||
this.superclassName = superclassName;
|
||||
this.interfaceNames = interfaceNames;
|
||||
this.typename = name;
|
||||
}
|
||||
|
||||
public AnnotationVisitor visitAnnotation(String classDesc, boolean isRuntime) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void visitAttribute(Attribute attribute) {
|
||||
}
|
||||
|
||||
public void visitInnerClass(String name, String outername, String innerName, int access) {
|
||||
if (name.equals(typename)) {
|
||||
this.flags = access;
|
||||
}
|
||||
}
|
||||
|
||||
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
|
||||
fields.add(new FieldMember(typename, access, name, desc, signature));
|
||||
if (name.equals("$callSiteArray")) {
|
||||
isGroovy = true;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
// For each method, copy it into the new class making appropriate adjustments
|
||||
/**
|
||||
* Visit a method in the class and build an appropriate representation for it to include in the extracted output.
|
||||
*/
|
||||
public MethodVisitor visitMethod(int flags, String name, String descriptor, String genericSignature, String[] exceptions) {
|
||||
if (name.charAt(0) != '<') {
|
||||
methods.add(new MethodMember(flags, name, descriptor, genericSignature, exceptions));
|
||||
} else {
|
||||
if (name.equals("<init>")) {
|
||||
//Even though constructors are not reloadable at present, we need to add them to type descriptors to know
|
||||
//about their original modifiers (these are promoted to public to allow executors access to them).
|
||||
constructors.add(new MethodMember(flags, name, descriptor, genericSignature, exceptions));
|
||||
} else if (name.equals("<clinit>")) {
|
||||
hasClinit = true;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void visitOuterClass(String owner, String name, String desc) {
|
||||
}
|
||||
|
||||
public void visitSource(String source, String debug) {
|
||||
}
|
||||
|
||||
public void visitEnd() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,674 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.Type;
|
||||
import org.objectweb.asm.tree.AbstractInsnNode;
|
||||
import org.objectweb.asm.tree.AnnotationNode;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
import org.objectweb.asm.tree.FieldInsnNode;
|
||||
import org.objectweb.asm.tree.FieldNode;
|
||||
import org.objectweb.asm.tree.IincInsnNode;
|
||||
import org.objectweb.asm.tree.InsnList;
|
||||
import org.objectweb.asm.tree.InsnNode;
|
||||
import org.objectweb.asm.tree.IntInsnNode;
|
||||
import org.objectweb.asm.tree.JumpInsnNode;
|
||||
import org.objectweb.asm.tree.LabelNode;
|
||||
import org.objectweb.asm.tree.LdcInsnNode;
|
||||
import org.objectweb.asm.tree.LineNumberNode;
|
||||
import org.objectweb.asm.tree.LookupSwitchInsnNode;
|
||||
import org.objectweb.asm.tree.MethodInsnNode;
|
||||
import org.objectweb.asm.tree.MethodNode;
|
||||
import org.objectweb.asm.tree.MultiANewArrayInsnNode;
|
||||
import org.objectweb.asm.tree.TableSwitchInsnNode;
|
||||
import org.objectweb.asm.tree.TypeInsnNode;
|
||||
import org.objectweb.asm.tree.VarInsnNode;
|
||||
|
||||
/**
|
||||
* Compute the differences between two versions of a type as a series of deltas. Entry point is the computeDifferences method.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class TypeDiffComputer implements Opcodes {
|
||||
|
||||
public static TypeDelta computeDifferences(byte[] oldbytes, byte[] newbytes) {
|
||||
ClassNode oldClassNode = new ClassNode();
|
||||
new ClassReader(oldbytes).accept(oldClassNode, 0);
|
||||
ClassNode newClassNode = new ClassNode();
|
||||
new ClassReader(newbytes).accept(newClassNode, 0);
|
||||
TypeDelta delta = computeDelta(oldClassNode, newClassNode);
|
||||
return delta;
|
||||
}
|
||||
|
||||
private static TypeDelta computeDelta(ClassNode oldClassNode, ClassNode newClassNode) {
|
||||
// The type itself: (int version, int access, String name, String signature, String superName, String[] interfaces) {
|
||||
TypeDelta td = new TypeDelta();
|
||||
computeTypeDelta(oldClassNode, newClassNode, td);
|
||||
computeFieldDelta(oldClassNode, newClassNode, td);
|
||||
computeMethodDelta(oldClassNode, newClassNode, td);
|
||||
// TODO delta: implement the rest of computeDelta. These methods from ClassVisitor should help in knowing what is left to do:
|
||||
// public void visitSource(String source, String debug) {
|
||||
// public void visitOuterClass(String owner, String name, String desc) {
|
||||
// public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
|
||||
// public void visitAttribute(Attribute attr) {
|
||||
// public void visitInnerClass(String name, String outerName, String innerName, int access) {
|
||||
// public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
|
||||
// public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
|
||||
// public void visitEnd() {
|
||||
return td;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static void computeMethodDelta(ClassNode oldClassNode, ClassNode newClassNode, TypeDelta td) {
|
||||
List<MethodNode> nMethods = newClassNode.methods;
|
||||
List<MethodNode> oMethods = new ArrayList<MethodNode>(oldClassNode.methods);
|
||||
|
||||
// Going through the new methods and comparing them to the old
|
||||
if (nMethods != null) {
|
||||
for (MethodNode nMethod : nMethods) {
|
||||
MethodNode found = null;
|
||||
for (MethodNode oMethod : oMethods) {
|
||||
if (oMethod.name.equals(nMethod.name) && oMethod.desc.equals(nMethod.desc)) { // TODO modifiers compared?
|
||||
found = oMethod;
|
||||
computeAnyMethodDifferences(oMethod, nMethod, td);
|
||||
}
|
||||
}
|
||||
if (found == null) {
|
||||
td.addNewMethod(nMethod);
|
||||
} else {
|
||||
oMethods.remove(found);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (MethodNode lostMethod : oMethods) {
|
||||
td.addLostMethod(lostMethod);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static void computeFieldDelta(ClassNode oldClassNode, ClassNode newClassNode, TypeDelta td) {
|
||||
// int oSize = oldClassNode.fields.size();
|
||||
int nSize = newClassNode.fields.size();
|
||||
|
||||
// Take a copy as we are going to delete entries in the next loop
|
||||
List<FieldNode> oFields = new ArrayList<FieldNode>(oldClassNode.fields);
|
||||
|
||||
// Going through the new fields comparing them to the old
|
||||
for (int n = 0; n < nSize; n++) {
|
||||
FieldNode nField = (FieldNode) newClassNode.fields.get(n);
|
||||
FieldNode found = null;
|
||||
for (FieldNode oField : oFields) {
|
||||
if (oField.name.equals(nField.name)) {
|
||||
// found it!
|
||||
found = oField;
|
||||
// is it exactly the same?
|
||||
computeAnyFieldDifferences(oField, nField, td);
|
||||
}
|
||||
}
|
||||
if (found == null) {
|
||||
// this is a new field
|
||||
td.addNewField(nField);
|
||||
} else {
|
||||
oFields.remove(found);
|
||||
}
|
||||
}
|
||||
|
||||
// Those left in oFields were not in nFields so have been removed!
|
||||
for (FieldNode lostField : oFields) {
|
||||
td.addLostField(lostField);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the properties of the field - if they have changed at all then record what kind of change for the field. Thinking the
|
||||
* type delta should have a map from names to a delta describing (capturing) the change.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static void computeAnyFieldDifferences(FieldNode oField, FieldNode nField, TypeDelta td) {
|
||||
// Want to record things that are different between these two fields...
|
||||
FieldDelta fd = new FieldDelta(oField.name);
|
||||
if (oField.access != nField.access) {
|
||||
// access changed
|
||||
fd.setAccessChanged(oField.access, nField.access);
|
||||
}
|
||||
if (!oField.desc.equals(nField.desc)) {
|
||||
// type changed
|
||||
fd.setTypeChanged(oField.desc, nField.desc);
|
||||
}
|
||||
String annotationChange = compareAnnotations(oField.invisibleAnnotations, nField.invisibleAnnotations);
|
||||
annotationChange = annotationChange + compareAnnotations(oField.visibleAnnotations, nField.visibleAnnotations);
|
||||
if (annotationChange.length() != 0) {
|
||||
fd.setAnnotationsChanged(annotationChange);
|
||||
}
|
||||
if (fd.hasAnyChanges()) {
|
||||
// it needs recording
|
||||
td.addChangedField(fd);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if there any differences between the methods supplied. A MethodDelta object is built to record any differences and
|
||||
* stored against the type delta.
|
||||
*
|
||||
* @param oMethod 'old' method
|
||||
* @param nMethod 'new' method
|
||||
* @param td the type delta where changes are currently being accumulated
|
||||
*/
|
||||
private static void computeAnyMethodDifferences(MethodNode oMethod, MethodNode nMethod, TypeDelta td) {
|
||||
MethodDelta md = new MethodDelta(oMethod.name, oMethod.desc);
|
||||
if (oMethod.access != nMethod.access) {
|
||||
md.setAccessChanged(oMethod.access, nMethod.access);
|
||||
}
|
||||
// TODO annotations
|
||||
InsnList oInstructions = oMethod.instructions;
|
||||
InsnList nInstructions = nMethod.instructions;
|
||||
if (oInstructions.size() != nInstructions.size()) {
|
||||
md.setInstructionsChanged(oInstructions.toArray(), nInstructions.toArray());
|
||||
} else {
|
||||
// TODO Just interested in constructors right now - should add others
|
||||
if (oMethod.name.charAt(0) == '<') {
|
||||
String oInvokeSpecialDescriptor = null;
|
||||
String nInvokeSpecialDescriptor = null;
|
||||
int oUninitCount = 0;
|
||||
int nUninitCount = 0;
|
||||
boolean codeChange = false;
|
||||
for (int i = 0, max = oInstructions.size(); i < max; i++) {
|
||||
AbstractInsnNode oInstruction = oInstructions.get(i);
|
||||
AbstractInsnNode nInstruction = nInstructions.get(i);
|
||||
if (!codeChange) {
|
||||
if (!sameInstruction(oInstruction, nInstruction)) {
|
||||
codeChange = true;
|
||||
}
|
||||
|
||||
}
|
||||
if (oInstruction.getType() == AbstractInsnNode.TYPE_INSN) {
|
||||
if (oInstruction.getOpcode() == Opcodes.NEW) {
|
||||
oUninitCount++;
|
||||
}
|
||||
}
|
||||
if (nInstruction.getType() == AbstractInsnNode.TYPE_INSN) {
|
||||
if (nInstruction.getOpcode() == Opcodes.NEW) {
|
||||
nUninitCount++;
|
||||
}
|
||||
}
|
||||
if (oInstruction.getType() == AbstractInsnNode.METHOD_INSN) {
|
||||
MethodInsnNode mi = (MethodInsnNode) oInstruction;
|
||||
if (mi.getOpcode() == INVOKESPECIAL && mi.name.equals("<init>")) {
|
||||
if (oUninitCount == 0) {
|
||||
// this is the one!
|
||||
oInvokeSpecialDescriptor = mi.desc;
|
||||
} else {
|
||||
oUninitCount--;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nInstruction.getType() == AbstractInsnNode.METHOD_INSN) {
|
||||
MethodInsnNode mi = (MethodInsnNode) nInstruction;
|
||||
if (mi.getOpcode() == INVOKESPECIAL && mi.name.equals("<init>")) {
|
||||
if (nUninitCount == 0) {
|
||||
// this is the one!
|
||||
nInvokeSpecialDescriptor = mi.desc;
|
||||
} else {
|
||||
nUninitCount--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Has the invokespecial changed?
|
||||
if (oInvokeSpecialDescriptor == null) {
|
||||
if (nInvokeSpecialDescriptor != null) {
|
||||
md.setInvokespecialChanged(oInvokeSpecialDescriptor, nInvokeSpecialDescriptor);
|
||||
}
|
||||
} else {
|
||||
if (!oInvokeSpecialDescriptor.equals(nInvokeSpecialDescriptor)) {
|
||||
md.setInvokespecialChanged(oInvokeSpecialDescriptor, nInvokeSpecialDescriptor);
|
||||
}
|
||||
}
|
||||
if (codeChange) {
|
||||
md.setCodeChanged(oInstructions.toArray(), nInstructions.toArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (md.hasAnyChanges()) {
|
||||
// it needs recording
|
||||
td.addChangedMethod(md);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static boolean sameInstruction(AbstractInsnNode o, AbstractInsnNode n) {
|
||||
if (o.getType() != o.getType() || o.getOpcode() != n.getOpcode()) {
|
||||
return false;
|
||||
}
|
||||
switch (o.getType()) {
|
||||
case (AbstractInsnNode.INSN): // 0
|
||||
if (!sameInsnNode(o, n)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case (AbstractInsnNode.INT_INSN): // 1
|
||||
if (!sameIntInsnNode(o, n)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case (AbstractInsnNode.VAR_INSN): // 2
|
||||
if (!sameVarInsn(o, n)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case (AbstractInsnNode.TYPE_INSN):// 3
|
||||
if (!sameTypeInsn(o, n)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case (AbstractInsnNode.FIELD_INSN): // 4
|
||||
if (!sameFieldInsn(o, n)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case (AbstractInsnNode.METHOD_INSN): // 5
|
||||
if (!sameMethodInsnNode(o, n)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case (AbstractInsnNode.JUMP_INSN): // 6
|
||||
if (!sameJumpInsnNode(o, n)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case (AbstractInsnNode.LABEL): // 7
|
||||
if (!sameLabelNode(o, n)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case (AbstractInsnNode.LDC_INSN): // 8
|
||||
if (!sameLdcInsnNode(o, n)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case (AbstractInsnNode.IINC_INSN): // 9
|
||||
if (!sameIincInsn(o, n)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case (AbstractInsnNode.TABLESWITCH_INSN): // 10
|
||||
if (!sameTableSwitchInsn(o, n)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case (AbstractInsnNode.LOOKUPSWITCH_INSN): // 11
|
||||
if (!sameLookupSwitchInsn(o, n)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case (AbstractInsnNode.MULTIANEWARRAY_INSN): // 12
|
||||
if (!sameMultiANewArrayInsn(o, n)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case (AbstractInsnNode.FRAME): // 13
|
||||
if (!sameFrameInsn(o, n)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case (AbstractInsnNode.LINE): // 14
|
||||
if (!sameLineNumberNode(o, n)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("nyi " + o.getType());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean sameFrameInsn(AbstractInsnNode o, AbstractInsnNode n) {
|
||||
// given that these nodes are computed based on everything else. if everything else is the same then these
|
||||
// must be the same. A full comparison could be a little ugly as different frames can be equivalent (maybe
|
||||
// the compiler produces an incremental frame on one run then a full frame on the next).
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean sameMultiANewArrayInsn(AbstractInsnNode o, AbstractInsnNode n) {
|
||||
if (!(n instanceof MultiANewArrayInsnNode)) {
|
||||
return false;
|
||||
}
|
||||
MultiANewArrayInsnNode mnao = (MultiANewArrayInsnNode) o;
|
||||
MultiANewArrayInsnNode mnan = (MultiANewArrayInsnNode) n;
|
||||
if (!mnao.desc.equals(mnan.desc)) {
|
||||
return false;
|
||||
}
|
||||
if (mnao.dims != mnan.dims) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static boolean sameLookupSwitchInsn(AbstractInsnNode o, AbstractInsnNode n) {
|
||||
if (!(n instanceof LookupSwitchInsnNode)) {
|
||||
return false;
|
||||
}
|
||||
LookupSwitchInsnNode lsio = (LookupSwitchInsnNode) o;
|
||||
LookupSwitchInsnNode lsin = (LookupSwitchInsnNode) n;
|
||||
if (sameLabels(lsio.dflt, lsin.dflt)) {
|
||||
return false;
|
||||
}
|
||||
List<Integer> keyso = lsio.keys;
|
||||
List<Integer> keysn = lsin.keys;
|
||||
if (keyso.size() != keysn.size()) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0, max = keyso.size(); i < max; i++) {
|
||||
if (keyso.get(i) != keysn.get(i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
List<LabelNode> labelso = lsio.labels;
|
||||
List<LabelNode> labelsn = lsin.labels;
|
||||
if (labelso.size() != labelsn.size()) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0, max = labelso.size(); i < max; i++) {
|
||||
if (!sameLabelNode(labelso.get(i), labelsn.get(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static boolean sameTableSwitchInsn(AbstractInsnNode o, AbstractInsnNode n) {
|
||||
if (!(n instanceof TableSwitchInsnNode)) {
|
||||
return false;
|
||||
}
|
||||
TableSwitchInsnNode tsio = (TableSwitchInsnNode) o;
|
||||
TableSwitchInsnNode tsin = (TableSwitchInsnNode) n;
|
||||
if (sameLabels(tsio.dflt, tsin.dflt)) {
|
||||
return false;
|
||||
}
|
||||
if (tsio.min != tsin.min) {
|
||||
return false;
|
||||
}
|
||||
if (tsio.max != tsin.max) {
|
||||
return false;
|
||||
}
|
||||
List<LabelNode> labelso = tsio.labels;
|
||||
List<LabelNode> labelsn = tsin.labels;
|
||||
if (labelso.size() != labelsn.size()) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0, max = labelso.size(); i < max; i++) {
|
||||
if (!sameLabelNode(labelso.get(i), labelsn.get(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean sameLabels(LabelNode lno, LabelNode lnn) {
|
||||
// TODO implement?
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean sameFieldInsn(AbstractInsnNode o, AbstractInsnNode n) {
|
||||
FieldInsnNode oi = (FieldInsnNode) o;
|
||||
if (!(n instanceof FieldInsnNode)) {
|
||||
return false;
|
||||
}
|
||||
FieldInsnNode ni = (FieldInsnNode) n;
|
||||
return oi.name.equals(ni.name) && oi.desc.equals(ni.desc) && oi.owner.equals(ni.owner);
|
||||
}
|
||||
|
||||
private static boolean sameMethodInsnNode(AbstractInsnNode o, AbstractInsnNode n) {
|
||||
MethodInsnNode oi = (MethodInsnNode) o;
|
||||
if (!(n instanceof MethodInsnNode)) {
|
||||
return false;
|
||||
}
|
||||
MethodInsnNode ni = (MethodInsnNode) n;
|
||||
return oi.name.equals(ni.name) && oi.desc.equals(ni.desc) && oi.owner.equals(ni.owner);
|
||||
}
|
||||
|
||||
private static boolean sameVarInsn(AbstractInsnNode o, AbstractInsnNode n) {
|
||||
VarInsnNode oi = (VarInsnNode) o;
|
||||
if (!(n instanceof VarInsnNode)) {
|
||||
return false;
|
||||
}
|
||||
VarInsnNode ni = (VarInsnNode) n;
|
||||
return oi.var == ni.var;
|
||||
}
|
||||
|
||||
private static boolean sameInsnNode(AbstractInsnNode o, AbstractInsnNode n) {
|
||||
InsnNode oi = (InsnNode) o;
|
||||
if (!(n instanceof InsnNode)) {
|
||||
return false;
|
||||
}
|
||||
InsnNode ni = (InsnNode) n;
|
||||
return oi.getOpcode() == ni.getOpcode();
|
||||
}
|
||||
|
||||
private static boolean sameJumpInsnNode(AbstractInsnNode o, AbstractInsnNode n) {
|
||||
// JumpInsnNode oJumpInsnNode = (JumpInsnNode) o;
|
||||
if (!(n instanceof JumpInsnNode)) {
|
||||
return false;
|
||||
}
|
||||
// JumpInsnNode nJumpInsnNode = (JumpInsnNode) n;
|
||||
// TODO tricky to compare destinations when captured as labels with no exposed identifier/position
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean sameLdcInsnNode(AbstractInsnNode o, AbstractInsnNode n) {
|
||||
LdcInsnNode oi = (LdcInsnNode) o;
|
||||
if (!(n instanceof LdcInsnNode)) {
|
||||
return false;
|
||||
}
|
||||
LdcInsnNode ni = (LdcInsnNode) n;
|
||||
Object ocst = oi.cst;
|
||||
if (ocst instanceof Integer) {
|
||||
if (!(ni.cst instanceof Integer)) {
|
||||
return false;
|
||||
}
|
||||
return ((Integer) ocst).equals(ni.cst);
|
||||
}
|
||||
if (ocst instanceof Float) {
|
||||
if (!(ni.cst instanceof Float)) {
|
||||
return false;
|
||||
}
|
||||
return ((Float) ocst).equals(ni.cst);
|
||||
}
|
||||
if (ocst instanceof Long) {
|
||||
if (!(ni.cst instanceof Long)) {
|
||||
return false;
|
||||
}
|
||||
return ((Long) ocst).equals(ni.cst);
|
||||
}
|
||||
if (ocst instanceof Double) {
|
||||
if (!(ni.cst instanceof Double)) {
|
||||
return false;
|
||||
}
|
||||
return ((Double) ocst).equals(ni.cst);
|
||||
}
|
||||
if (ocst instanceof String) {
|
||||
if (!(ni.cst instanceof String)) {
|
||||
return false;
|
||||
}
|
||||
return ((String) ocst).equals(ni.cst);
|
||||
}
|
||||
// must be Type
|
||||
return ((Type) ocst).equals(ni.cst);
|
||||
}
|
||||
|
||||
private static boolean sameIntInsnNode(AbstractInsnNode o, AbstractInsnNode n) {
|
||||
IntInsnNode oi = (IntInsnNode) o;
|
||||
if (!(n instanceof IntInsnNode)) {
|
||||
return false;
|
||||
}
|
||||
IntInsnNode ni = (IntInsnNode) n;
|
||||
return oi.operand == ni.operand;
|
||||
}
|
||||
|
||||
private static boolean sameLineNumberNode(AbstractInsnNode o, AbstractInsnNode n) {
|
||||
LineNumberNode oi = (LineNumberNode) o;
|
||||
if (!(n instanceof LineNumberNode)) {
|
||||
return false;
|
||||
}
|
||||
LineNumberNode ni = (LineNumberNode) n;
|
||||
return oi.line == ni.line;
|
||||
// TODO check oi.start?
|
||||
}
|
||||
|
||||
private static boolean sameIincInsn(AbstractInsnNode o, AbstractInsnNode n) {
|
||||
IincInsnNode oi = (IincInsnNode) o;
|
||||
if (!(n instanceof IincInsnNode)) {
|
||||
return false;
|
||||
}
|
||||
IincInsnNode ni = (IincInsnNode) n;
|
||||
return oi.var == ni.var && oi.incr == ni.incr;
|
||||
}
|
||||
|
||||
private static boolean sameTypeInsn(AbstractInsnNode o, AbstractInsnNode n) {
|
||||
TypeInsnNode oi = (TypeInsnNode) o;
|
||||
if (!(n instanceof TypeInsnNode)) {
|
||||
return false;
|
||||
}
|
||||
TypeInsnNode ni = (TypeInsnNode) n;
|
||||
return oi.desc.equals(ni.desc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two labels to check they are the same.
|
||||
*
|
||||
* @param o 'old' label
|
||||
* @param n 'new' label
|
||||
* @return true if they are different
|
||||
*/
|
||||
private static boolean sameLabelNode(AbstractInsnNode o, AbstractInsnNode n) {
|
||||
// LabelNode oi = (LabelNode) o;
|
||||
if (!(n instanceof LabelNode)) {
|
||||
return false;
|
||||
}
|
||||
// LabelNode ni = (LabelNode) n;
|
||||
|
||||
// TODO tricky to get right. Unfortunately the positions aren't always available - and we can't check if they are, we have to call the
|
||||
// getOffset() method on label and catch an exception if they aren't
|
||||
return true;
|
||||
}
|
||||
|
||||
private static String compareAnnotations(List<AnnotationNode> oldAnnos, List<AnnotationNode> newAnnos) {
|
||||
if (oldAnnos == null) {
|
||||
if (newAnnos == null) {
|
||||
return "";
|
||||
}
|
||||
oldAnnos = Collections.emptyList();
|
||||
}
|
||||
if (newAnnos == null) {
|
||||
newAnnos = Collections.emptyList();
|
||||
}
|
||||
StringBuilder diff = new StringBuilder();
|
||||
// Which have been removed
|
||||
for (AnnotationNode o : oldAnnos) {
|
||||
boolean found = false;
|
||||
String oFormatted = Utils.annotationNodeFormat(o);
|
||||
for (AnnotationNode n : newAnnos) {
|
||||
String nFormatted = Utils.annotationNodeFormat(n);
|
||||
if (oFormatted.equals(nFormatted)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
diff.append("-").append(oFormatted);
|
||||
}
|
||||
}
|
||||
// Which have been added
|
||||
for (AnnotationNode n : newAnnos) {
|
||||
boolean found = false;
|
||||
String nFormatted = Utils.annotationNodeFormat(n);
|
||||
for (AnnotationNode o : oldAnnos) {
|
||||
String oFormatted = Utils.annotationNodeFormat(o);
|
||||
if (oFormatted.equals(nFormatted)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
diff.append("+").append(nFormatted);
|
||||
}
|
||||
}
|
||||
return diff.toString();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static void computeTypeDelta(ClassNode oldClassNode, ClassNode newClassNode, TypeDelta td) {
|
||||
// if (oldClassNode.version != newClassNode.version) {
|
||||
// td.setTypeVersionChange(oldClassNode.version, newClassNode.version);
|
||||
// }
|
||||
if (oldClassNode.access != newClassNode.access) {
|
||||
td.setTypeAccessChange(oldClassNode.access, newClassNode.access);
|
||||
}
|
||||
if (!oldClassNode.name.equals(newClassNode.name)) {
|
||||
td.setTypeNameChange(oldClassNode.name, newClassNode.name);
|
||||
}
|
||||
// if (oldClassNode.signature == null) {
|
||||
// if (newClassNode.signature != null) {
|
||||
// td.setTypeSignatureChange(oldClassNode.signature, newClassNode.signature);
|
||||
// }
|
||||
// } else if (newClassNode.signature == null) {
|
||||
// if (oldClassNode.signature != null) {
|
||||
// td.setTypeSignatureChange(oldClassNode.signature, newClassNode.signature);
|
||||
// }
|
||||
// } else if (!oldClassNode.signature.equals(newClassNode.signature)) {
|
||||
// td.setTypeSignatureChange(oldClassNode.signature, newClassNode.signature);
|
||||
// }
|
||||
if (oldClassNode.superName == null) {
|
||||
if (newClassNode.superName != null) {
|
||||
td.setTypeSuperNameChange(oldClassNode.superName, newClassNode.superName);
|
||||
}
|
||||
} else if (newClassNode.superName == null) {
|
||||
if (oldClassNode.superName != null) {
|
||||
td.setTypeSuperNameChange(oldClassNode.superName, newClassNode.superName);
|
||||
}
|
||||
} else if (!oldClassNode.superName.equals(newClassNode.superName)) {
|
||||
td.setTypeSuperNameChange(oldClassNode.superName, newClassNode.superName);
|
||||
}
|
||||
if (oldClassNode.interfaces.size() == 0) {
|
||||
if (newClassNode.interfaces.size() != 0) {
|
||||
td.setTypeInterfacesChange(oldClassNode.interfaces, newClassNode.interfaces);
|
||||
}
|
||||
} else if (newClassNode.interfaces.size() == 0) {
|
||||
if (oldClassNode.interfaces.size() != 0) {
|
||||
td.setTypeInterfacesChange(oldClassNode.interfaces, newClassNode.interfaces);
|
||||
}
|
||||
} else {
|
||||
if (oldClassNode.interfaces.size() != newClassNode.interfaces.size()) {
|
||||
td.setTypeInterfacesChange(oldClassNode.interfaces, newClassNode.interfaces);
|
||||
}
|
||||
HashSet<String> oldInterfaceSet = new HashSet<String>(oldClassNode.interfaces);
|
||||
HashSet<String> newInterfaceSet = new HashSet<String>(newClassNode.interfaces);
|
||||
if (!oldInterfaceSet.equals(newInterfaceSet)) { // TODO expensive? keep the interfaces list sorted instead?
|
||||
td.setTypeInterfacesChange(oldClassNode.interfaces, newClassNode.interfaces);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
/**
|
||||
* Similar to the AspectJ type pattern model - used for defining reloadable type inclusions/exclusions.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public abstract class TypePattern {
|
||||
|
||||
public boolean matches(String dottedname) {
|
||||
if (GlobalConfiguration.assertsOn) {
|
||||
Utils.assertDotted(dottedname);
|
||||
}
|
||||
return internalMatches(dottedname);
|
||||
}
|
||||
|
||||
protected abstract boolean internalMatches(String input);
|
||||
}
|
||||
1838
springloaded/src/main/java/org/springsource/loaded/TypeRegistry.java
Normal file
1838
springloaded/src/main/java/org/springsource/loaded/TypeRegistry.java
Normal file
File diff suppressed because it is too large
Load Diff
1045
springloaded/src/main/java/org/springsource/loaded/TypeRewriter.java
Normal file
1045
springloaded/src/main/java/org/springsource/loaded/TypeRewriter.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class UnableToLoadClassException extends RuntimeException {
|
||||
|
||||
public UnableToLoadClassException(String classname) {
|
||||
super("Unable to find data for class '" + classname + "'");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
/**
|
||||
* UnableToReloadEventProcessor Plugins are called when a type cannot be reloaded due to an unsupported change. For information on
|
||||
* registering them, see {@link Plugin}
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.7.3
|
||||
*/
|
||||
public interface UnableToReloadEventProcessorPlugin extends Plugin {
|
||||
|
||||
/**
|
||||
* Called when a type cannot be reloaded, due to a change being made that is not supported by the agent, for example when the
|
||||
* set of interfaces for a type is changed. Note, the class is only truly defined to the VM once, and so the Class object (clazz
|
||||
* parameter) is always the same for the same type (ignoring multiple classloader situations). It is passed here so that plugins
|
||||
* processing events can clear any cached state related to it. The encodedTimestamp is an encoding of the ID that the agent has
|
||||
* assigned to this reloaded version of this type. The TypeDelta (a work in progress) captures details about what changed in the
|
||||
* type that could not be reloaded.
|
||||
*
|
||||
* @param typename the (dotted) type name, for example java.lang.String
|
||||
* @param clazz the Class object that has been reloaded
|
||||
* @param typeDelta encapsulates information about the changes made in this version of the type that prevented the reload
|
||||
* @param encodedTimestamp an encoded time stamp for this version, containing chars (A-Za-z0-9)
|
||||
*/
|
||||
void unableToReloadEvent(String typename, Class<?> clazz, TypeDelta typeDelta, String encodedTimestamp);
|
||||
}
|
||||
1755
springloaded/src/main/java/org/springsource/loaded/Utils.java
Normal file
1755
springloaded/src/main/java/org/springsource/loaded/Utils.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded;
|
||||
|
||||
/**
|
||||
* Interface implemented by all dispatchers so code can be generated to call the dynamic executor regardless of the dispatcher
|
||||
* instance the code is actually working with. The method name here lines up with that defined in Constants - see
|
||||
* mDynamicDispatchName.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public interface __DynamicallyDispatchable {
|
||||
|
||||
Object __execute(Object[] parameters, Object instance, String nameAndDescriptor);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded.agent;
|
||||
|
||||
import java.security.ProtectionDomain;
|
||||
|
||||
import org.springsource.loaded.LoadtimeInstrumentationPlugin;
|
||||
|
||||
|
||||
/**
|
||||
* The CGLIB plugin recognizes when elements of cglib are loaded and rewrites them to catch certain events occuring.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.8.3
|
||||
*/
|
||||
public class CglibPlugin implements LoadtimeInstrumentationPlugin {
|
||||
|
||||
// private static Logger log = Logger.getLogger(CglibPlugin.class.getName());
|
||||
|
||||
// implementing LoadtimeInstrumentationPlugin
|
||||
public boolean accept(String slashedTypeName, ClassLoader classLoader, ProtectionDomain protectionDomain, byte[] bytes) {
|
||||
if (slashedTypeName==null) {
|
||||
return false;
|
||||
}
|
||||
return slashedTypeName.equals("net/sf/cglib/core/AbstractClassGenerator");
|
||||
// || slashedTypeName.equals("net/sf/cglib/reflect/FastClass");
|
||||
}
|
||||
|
||||
public byte[] modify(String slashedClassName, ClassLoader classLoader, byte[] bytes) {
|
||||
// if (slashedClassName.equals("net/sf/cglib/core/AbstractClassGenerator")) {
|
||||
return CglibPluginCapturing.catchGenerate(bytes);
|
||||
// } else {
|
||||
// net/sf/cglib/reflect/FastClass
|
||||
// We must empty the FastClass constructor. Why? Due to current limitations with
|
||||
// SpringLoaded we consider a constructor to have changed when the type is reloaded
|
||||
// (so regardless of whether the code did actually change). Due to this the
|
||||
// 'new' constructors driven once reloaded call super.<init>(). The default FastClass
|
||||
// empty constructor throws an exception. We are just removing that throw.
|
||||
// return bytes;//EmptyCtor.invoke(bytes, "()V");
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded.agent;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.objectweb.asm.ClassAdapter;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.MethodAdapter;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
import org.springsource.loaded.Constants;
|
||||
|
||||
|
||||
/**
|
||||
* This bytecode rewriter intercepts calls to generate made in the CGLIB framework and allows us to record what generator is called
|
||||
* to create the proxy for some type. The same generator can then be driven again if the type is reloaded.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.8.3
|
||||
*/
|
||||
public class CglibPluginCapturing extends ClassAdapter implements Constants {
|
||||
|
||||
public static Map<Class<?>, Object[]> clazzToGeneratorStrategyAndClassGeneratorMap = new HashMap<Class<?>, Object[]>();
|
||||
public static Map<Class<?>, Object[]> clazzToGeneratorStrategyAndFastClassGeneratorMap = new HashMap<Class<?>, Object[]>();
|
||||
|
||||
public static byte[] catchGenerate(byte[] bytesIn) {
|
||||
ClassReader cr = new ClassReader(bytesIn);
|
||||
CglibPluginCapturing ca = new CglibPluginCapturing();
|
||||
cr.accept(ca, 0);
|
||||
byte[] newbytes = ca.getBytes();
|
||||
return newbytes;
|
||||
}
|
||||
|
||||
private CglibPluginCapturing() {
|
||||
super(new ClassWriter(0)); // TODO review 0 here
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
return ((ClassWriter) cv).toByteArray();
|
||||
}
|
||||
|
||||
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
|
||||
if (name.equals("create")) {
|
||||
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
|
||||
return new CreateMethodInterceptor(mv);
|
||||
} else {
|
||||
return super.visitMethod(access, name, desc, signature, exceptions);
|
||||
}
|
||||
}
|
||||
|
||||
static class CreateMethodInterceptor extends MethodAdapter implements Constants {
|
||||
|
||||
public CreateMethodInterceptor(MethodVisitor mv) {
|
||||
super(mv);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitCode() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Recognize a call to 'generate' being made. When we see it add some extra code after it that calls the record method in
|
||||
* this type so that we can remember the generator used (and drive it again later when the related type is reloaded).
|
||||
*/
|
||||
@Override
|
||||
public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc) {
|
||||
super.visitMethodInsn(opcode, owner, name, desc);
|
||||
if (name.equals("generate")) {
|
||||
// Code that calls generate:
|
||||
// ALOAD 0
|
||||
// GETFIELD net/sf/cglib/core/AbstractClassGenerator.strategy : Lnet/sf/cglib/core/GeneratorStrategy;
|
||||
// ALOAD 0
|
||||
// INVOKEINTERFACE net/sf/cglib/core/GeneratorStrategy.generate(Lnet/sf/cglib/core/ClassGenerator;)[B
|
||||
mv.visitVarInsn(ALOAD, 0); // AbstractClassGenerator instance
|
||||
mv.visitFieldInsn(GETFIELD, "net/sf/cglib/core/AbstractClassGenerator", "strategy",
|
||||
"Lnet/sf/cglib/core/GeneratorStrategy;");
|
||||
mv.visitVarInsn(ALOAD, 0); // AbstractClassGenerator instance
|
||||
mv.visitMethodInsn(INVOKESTATIC, "org/springsource/loaded/agent/CglibPluginCapturing", "record",
|
||||
"(Ljava/lang/Object;Ljava/lang/Object;)V");//Lnet/sf/cglib/core/GeneratorStrategy;Lnet/sf/cglib/core/AbstractClassGenerator);");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The classloader for class artifacts is used to load the generated classes for call sites. We need to rewrite these classes
|
||||
* because they may be either calling something that disappears on a later reload (so need to fail appropriately) or calling
|
||||
* something that isnt there on the first load - in this latter case they are changed to route the dynamic executor method.
|
||||
*
|
||||
* @param classloader
|
||||
* @param name
|
||||
* @param bytes
|
||||
* @return
|
||||
*/
|
||||
public static void record(Object a, Object b) {
|
||||
// a is a Lnet/sf/cglib/core/GeneratorStrategy;
|
||||
// b is a Lnet/sf/cglib/core/AbstractClassGenerator (or specifically net/sf/cglib/reflect/FastClass$Generator)
|
||||
// a is something like 'UndeclaredThrowableStrategy'
|
||||
// b is an Enhancer: namePrefix="example.Simple" superclass=example.Simple
|
||||
String generatorName = b.getClass().getName();
|
||||
if (generatorName.equals("net.sf.cglib.proxy.Enhancer")) {
|
||||
try {
|
||||
Field f = b.getClass().getDeclaredField("superclass");
|
||||
f.setAccessible(true);
|
||||
Class<?> clazz = (Class<?>) f.get(b);
|
||||
// System.out.println("Recording pair " + clazz.getName() + " > " + b);
|
||||
clazzToGeneratorStrategyAndClassGeneratorMap.put(clazz, new Object[] { a, b });
|
||||
} catch (Throwable re) {
|
||||
re.printStackTrace();
|
||||
}
|
||||
} else if (generatorName.equals("net.sf.cglib.reflect.FastClass$Generator")) {
|
||||
try {
|
||||
Field f = b.getClass().getDeclaredField("type");
|
||||
f.setAccessible(true);
|
||||
Class<?> clazz = (Class<?>) f.get(b);
|
||||
// System.out.println("Recording pair (fastclass) " + clazz.getName() + " > " + b);
|
||||
clazzToGeneratorStrategyAndFastClassGeneratorMap.put(clazz, new Object[] { a, b });
|
||||
} catch (Throwable re) {
|
||||
re.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded.agent;
|
||||
|
||||
import java.lang.instrument.ClassFileTransformer;
|
||||
import java.lang.instrument.IllegalClassFormatException;
|
||||
import java.security.ProtectionDomain;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.springsource.loaded.GlobalConfiguration;
|
||||
import org.springsource.loaded.ReloadableType;
|
||||
import org.springsource.loaded.TypeRegistry;
|
||||
|
||||
|
||||
/**
|
||||
* Class pre-processor.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class ClassPreProcessorAgentAdapter implements ClassFileTransformer {
|
||||
|
||||
private static Logger log = Logger.getLogger(ClassPreProcessorAgentAdapter.class.getName());
|
||||
|
||||
private static SpringLoadedPreProcessor preProcessor;
|
||||
|
||||
private static ClassPreProcessorAgentAdapter instance;
|
||||
|
||||
public ClassPreProcessorAgentAdapter() {
|
||||
instance = this;
|
||||
}
|
||||
|
||||
static {
|
||||
try {
|
||||
preProcessor = new SpringLoadedPreProcessor();
|
||||
preProcessor.initialize();
|
||||
} catch (Exception e) {
|
||||
throw new ExceptionInInitializerError("could not initialize JSR163 preprocessor due to: " + e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param loader the defining class loader
|
||||
* @param className the name of class being loaded
|
||||
* @param classBeingRedefined when hotswap is called
|
||||
* @param protectionDomain
|
||||
* @param bytes the bytecode before weaving
|
||||
* @return the weaved bytecode
|
||||
*/
|
||||
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
|
||||
byte[] bytes) throws IllegalClassFormatException {
|
||||
try {
|
||||
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) {
|
||||
log.info("> (loader=" + loader + " className=" + className + ", classBeingRedefined=" + classBeingRedefined
|
||||
+ ", protectedDomain=" + (protectionDomain != null) + ", bytes= " + (bytes == null ? "null" : bytes.length));
|
||||
}
|
||||
|
||||
// TODO determine if this is the right behaviour for hot code replace:
|
||||
// Handling class redefinition (hot code replace) - what to do depends on whether the type is a reloadable type or not
|
||||
// If reloadable - return the class as originally defined, and treat this new input data as the new version to make live
|
||||
// If not-reloadable - rewrite the call sites and attempt hot code replace
|
||||
|
||||
if (classBeingRedefined != null) {
|
||||
// pretend no-one attempted the reload by returning original bytes. The 'watcher' for the class
|
||||
// should see the changes and pick them up. Should we force it here?
|
||||
TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(loader);
|
||||
if (typeRegistry == null) {
|
||||
return null;
|
||||
}
|
||||
boolean isRTN = typeRegistry.isReloadableTypeName(className);
|
||||
if (isRTN) {
|
||||
ReloadableType rtype = typeRegistry.getReloadableType(className, false);
|
||||
// CurrentLiveVersion clv = rtype.getLiveVersion();
|
||||
// String suffix = "0";
|
||||
// if (clv != null) {
|
||||
// suffix = clv.getVersionStamp() + "H";
|
||||
// }
|
||||
// rtype.loadNewVersion(suffix, bytes);
|
||||
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) {
|
||||
log.info("Tricking HCR for " + className);
|
||||
}
|
||||
return rtype.bytesLoaded; // returning original bytes
|
||||
}
|
||||
return null;
|
||||
}
|
||||
// System.err.println("transform(" + loader.getClass().getName() + ",classname=" + className +
|
||||
// ",classBeingRedefined=" + classBeingRedefined + ",protectionDomain=" + protectionDomain + ")");
|
||||
return preProcessor.preProcess(loader, className, protectionDomain, bytes);
|
||||
} catch (Throwable t) {
|
||||
new RuntimeException("Reloading agent exited via exception, please raise a jira", t).printStackTrace();
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
|
||||
public static void reload(ClassLoader loader, String className, Class<?> classBeingRedefined,
|
||||
ProtectionDomain protectionDomain, byte[] bytes) throws IllegalClassFormatException {
|
||||
instance.transform(loader, className, classBeingRedefined, protectionDomain, bytes);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded.agent;
|
||||
|
||||
import org.objectweb.asm.ClassAdapter;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.MethodAdapter;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
import org.springsource.loaded.Constants;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class ClassVisitingConstructorAppender extends ClassAdapter implements Constants {
|
||||
|
||||
private String calleeOwner;
|
||||
private String calleeName;
|
||||
|
||||
/**
|
||||
* This ClassAdapter will visit a class and within the constructors it will add a call to the specified method (assumed static)
|
||||
* just before each constructor returns. The target of the call should be a collecting method that will likely do something with
|
||||
* the instances later on class reload.
|
||||
*
|
||||
* @param owner
|
||||
* @param name
|
||||
*/
|
||||
public ClassVisitingConstructorAppender(String owner, String name) {
|
||||
super(new ClassWriter(0)); // TODO review 0 here
|
||||
this.calleeOwner = owner;
|
||||
this.calleeName = name;
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
return ((ClassWriter) cv).toByteArray();
|
||||
}
|
||||
|
||||
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
|
||||
if (name.equals("<init>")) {
|
||||
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
|
||||
return new ConstructorAppender(mv);
|
||||
} else {
|
||||
return super.visitMethod(access, name, desc, signature, exceptions);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This constructor appender includes a couple of instructions at the end of each constructor it is asked to visit. It
|
||||
* recognizes the end by observing a RETURN instruction. The instructions are inserted just before the RETURN.
|
||||
*/
|
||||
class ConstructorAppender extends MethodAdapter implements Constants {
|
||||
|
||||
public ConstructorAppender(MethodVisitor mv) {
|
||||
super(mv);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitInsn(int opcode) {
|
||||
if (opcode == RETURN) {
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitMethodInsn(INVOKESTATIC, calleeOwner, calleeName, "(Ljava/lang/Object;)V");
|
||||
}
|
||||
super.visitInsn(opcode);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded.agent;
|
||||
|
||||
import org.objectweb.asm.ClassAdapter;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
import org.springsource.loaded.Constants;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.7.0
|
||||
*/
|
||||
public class FalseReturner extends ClassAdapter implements Constants {
|
||||
|
||||
private String methodname;
|
||||
|
||||
public FalseReturner(String methodname) {
|
||||
super(new ClassWriter(0)); // TODO review 0 here
|
||||
this.methodname = methodname;
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
return ((ClassWriter) cv).toByteArray();
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
|
||||
// if (name.equals(methodname)) {
|
||||
// return super.visitField(access & (~Modifier.FINAL), name, desc, signature, value);
|
||||
// } else {
|
||||
// return super.visitField(access, name, desc, signature, value);
|
||||
// }
|
||||
// }
|
||||
|
||||
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
|
||||
if (name.equals(methodname)) {
|
||||
// return new FakeMethodVisitor();
|
||||
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
|
||||
mv.visitCode();
|
||||
mv.visitInsn(ICONST_0);
|
||||
mv.visitInsn(IRETURN);
|
||||
mv.visitMaxs(3, 1);
|
||||
mv.visitEnd();
|
||||
return mv;
|
||||
// return new FalseReturnerMV(mv);
|
||||
} else {
|
||||
return super.visitMethod(access, name, desc, signature, exceptions);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean m() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// class FakeMethodVisitor implements MethodVisitor, Constants {
|
||||
//
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded.agent;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springsource.loaded.FileChangeListener;
|
||||
import org.springsource.loaded.TypeRegistry;
|
||||
|
||||
|
||||
/**
|
||||
* A simple watcher for the file system. Uses a thread to keep an eye on a number of files and calls back registered interested
|
||||
* parties when a change is observed. The thread only starts when there is something to watch. The thread is given a name indicating
|
||||
* the classloader for which it is watching files. Once it starts to watch files the name will be enhanced to indicate how many.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class FileSystemWatcher {
|
||||
|
||||
// the thread being managed
|
||||
private Thread thread;
|
||||
|
||||
// whether the thread is running
|
||||
private boolean threadRunning = false;
|
||||
|
||||
// The Watcher running inside the thread
|
||||
private Watcher watchThread;
|
||||
|
||||
public FileSystemWatcher(FileChangeListener listener, int typeRegistryId, String classloadername) {
|
||||
watchThread = new Watcher(listener, typeRegistryId, classloadername);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the thread if it isn't already started.
|
||||
*/
|
||||
private void ensureWatchThreadRunning() {
|
||||
if (!threadRunning) {
|
||||
thread = new Thread(watchThread);
|
||||
thread.setDaemon(true);
|
||||
thread.start();
|
||||
watchThread.setThread(thread);
|
||||
watchThread.updateName();
|
||||
threadRunning = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shutdown the thread.
|
||||
*/
|
||||
public void shutdown() {
|
||||
if (threadRunning) {
|
||||
watchThread.timeToStop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new file to the list of those being monitored. If the file is something that can be watched, then this method will
|
||||
* cause the thread to start (if it hasn't already been started).
|
||||
*
|
||||
* @param fileToMonitor the file to start monitor
|
||||
*/
|
||||
public void register(File fileToMonitor) {
|
||||
if (watchThread.addFile(fileToMonitor)) {
|
||||
ensureWatchThreadRunning();
|
||||
watchThread.updateName();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables the filesystem watching to be paused/unpaused.
|
||||
*
|
||||
* @param shouldBePaused watching should be paused?
|
||||
*/
|
||||
public void setPaused(boolean shouldBePaused) {
|
||||
watchThread.paused = shouldBePaused;
|
||||
}
|
||||
}
|
||||
|
||||
class Watcher implements Runnable {
|
||||
|
||||
long lastScanTime;
|
||||
|
||||
// TODO configurable scan interval?
|
||||
private static long interval = 1100;// ms
|
||||
List<File> watchListFiles = new ArrayList<File>();
|
||||
List<Long> watchListLMTs = new ArrayList<Long>();
|
||||
// Map<File, Long> watchList = new ConcurrentHashMap<File, Long>();
|
||||
FileChangeListener listener;
|
||||
private boolean timeToStop = false;
|
||||
public boolean paused = false;
|
||||
private Thread thread = null;
|
||||
private int typeRegistryId;
|
||||
private String classloadername;
|
||||
private int registryLivenessCount = 0;
|
||||
private static int registryLivenessCountInterval = 300;
|
||||
|
||||
public Watcher(FileChangeListener listener, int typeRegistryId, String classloadername) {
|
||||
this.listener = listener;
|
||||
this.typeRegistryId = typeRegistryId;
|
||||
this.classloadername = classloadername;
|
||||
}
|
||||
|
||||
public void setThread(Thread thread) {
|
||||
this.thread = thread;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new File that the thread should start watching. If the file does not exist nothing happens (this may be because a class
|
||||
* has been generated on the fly and really there is nothing to watch on disk).
|
||||
*
|
||||
* @param fileToWatch the new file to watch
|
||||
* @return true if the file is now being watched, false otherwise
|
||||
*/
|
||||
public boolean addFile(File fileToWatch) {
|
||||
if (!fileToWatch.exists()) {
|
||||
return false;
|
||||
}
|
||||
synchronized (this) {
|
||||
int insertionPos = findPosition(fileToWatch);
|
||||
if (insertionPos == -1) {
|
||||
watchListFiles.add(fileToWatch);
|
||||
watchListLMTs.add(fileToWatch.lastModified());
|
||||
} else {
|
||||
watchListFiles.add(insertionPos, fileToWatch);
|
||||
watchListLMTs.add(insertionPos, fileToWatch.lastModified());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public void updateName() {
|
||||
if (thread != null) {
|
||||
thread.setName("FileSystemWatcher: files=#" + watchListFiles.size() + " cl=" + classloadername);
|
||||
}
|
||||
}
|
||||
|
||||
private int findPosition(File file) {
|
||||
String filename = file.getName();
|
||||
int len = watchListFiles.size();
|
||||
if (len == 0) {
|
||||
return 0;
|
||||
}
|
||||
for (int f = 0; f < len; f++) {
|
||||
File file2 = watchListFiles.get(f);
|
||||
int cmp = file2.getName().compareTo(filename);
|
||||
// as we are using 'names' we are only considering the last part, so foo/bar/Goo.class and foo/Goo.class look the same
|
||||
// and will return cmp==0. Not really sure it matters about using fq names
|
||||
if (cmp > 0) {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
while (!timeToStop) {
|
||||
registryLivenessCount++;
|
||||
if ((registryLivenessCount % registryLivenessCountInterval) == 0) {
|
||||
// Time to check if the registry is still alive!
|
||||
if (!TypeRegistry.typeRegistryExistsForId(typeRegistryId)) {
|
||||
// System.out.println("TypeRegistry " + typeRegistryId + " gone, no point in thread continuing!");
|
||||
return;
|
||||
// } else {
|
||||
// System.out.println("TypeRegistry " + typeRegistryId + " seems to still be around!");
|
||||
}
|
||||
registryLivenessCount = 0;
|
||||
}
|
||||
try {
|
||||
Thread.sleep(interval);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
if (!paused) {
|
||||
List<File> changedFiles = new ArrayList<File>();
|
||||
synchronized (this) {
|
||||
int len = watchListFiles.size();
|
||||
for (int f = 0; f < len; f++) {
|
||||
File file = watchListFiles.get(f);
|
||||
long lastModTime = file.lastModified();
|
||||
if (lastModTime > watchListLMTs.get(f)) {
|
||||
// System.out.println("Watcher: " + lastScanTime + " change detected in " + file);
|
||||
watchListLMTs.set(f, lastModTime);
|
||||
changedFiles.add(file);
|
||||
}
|
||||
}
|
||||
lastScanTime = System.currentTimeMillis();
|
||||
}
|
||||
for (File changedFile: changedFiles) {
|
||||
determineChangesSince(changedFile, lastScanTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* problem is that we check some file X, it hasn't changed - we then take longer than interval to check all the
|
||||
* other files we are watching.
|
||||
*/
|
||||
|
||||
private void determineChangesSince(File file, long lastScanTime) {
|
||||
try {
|
||||
listener.fileChanged(file);
|
||||
if (file.isDirectory()) {
|
||||
File[] filesOfInterest = file.listFiles(new RecentChangeFilter(lastScanTime));
|
||||
for (File f : filesOfInterest) {
|
||||
if (f.isDirectory()) {
|
||||
determineChangesSince(f, lastScanTime);
|
||||
} else {
|
||||
listener.fileChanged(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
new RuntimeException("FileWatcher caught serious error, see cause.", t).printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
static class RecentChangeFilter implements FileFilter {
|
||||
|
||||
private long lastScanTime;
|
||||
|
||||
public RecentChangeFilter(long lastScanTime) {
|
||||
this.lastScanTime = lastScanTime;
|
||||
}
|
||||
|
||||
public boolean accept(File pathname) {
|
||||
return (pathname.lastModified() > lastScanTime);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void timeToStop() {
|
||||
timeToStop = true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded.agent;
|
||||
|
||||
import java.lang.ref.Reference;
|
||||
import java.lang.ref.ReferenceQueue;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.security.ProtectionDomain;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springsource.loaded.GlobalConfiguration;
|
||||
import org.springsource.loaded.LoadtimeInstrumentationPlugin;
|
||||
import org.springsource.loaded.ReloadEventProcessorPlugin;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.7.3
|
||||
*/
|
||||
public class GrailsPlugin implements LoadtimeInstrumentationPlugin, ReloadEventProcessorPlugin {
|
||||
|
||||
// private static Logger log = Logger.getLogger(GrailsPlugin.class.getName());
|
||||
|
||||
private static final String DefaultClassPropertyFetcher = "org/codehaus/groovy/grails/commons/ClassPropertyFetcher";
|
||||
|
||||
private static List<WeakReference<Object>> classPropertyFetcherInstances = new ArrayList<WeakReference<Object>>();
|
||||
|
||||
private static ReferenceQueue<Object> rq = new ReferenceQueue<Object>();
|
||||
|
||||
/**
|
||||
* @return true for types this plugin would like to change on startup
|
||||
*/
|
||||
public boolean accept(String slashedTypeName, ClassLoader classLoader, ProtectionDomain protectionDomain, byte[] bytes) {
|
||||
// TODO take classloader into account?
|
||||
return false;//DefaultClassPropertyFetcher.equals(slashedTypeName);
|
||||
}
|
||||
|
||||
public byte[] modify(String slashedClassName, ClassLoader classLoader, byte[] bytes) {
|
||||
return PluginUtils.addInstanceTracking(bytes, "org/springsource/loaded/agent/GrailsPlugin");
|
||||
}
|
||||
|
||||
// called by the modified code
|
||||
public static void recordInstance(Object obj) {
|
||||
// obj will be a ClassPropertyFetcher instance
|
||||
System.err.println("new instance queued " + System.identityHashCode(obj));
|
||||
// TODO urgent - race condition here, can create Co-modification problem if adding whilst another thread is processing
|
||||
classPropertyFetcherInstances.add(new WeakReference<Object>(obj, rq));
|
||||
}
|
||||
|
||||
private Field classPropertyFetcher_clazz;
|
||||
private Method classPropertyFetcher_init;
|
||||
|
||||
public void reloadEvent(String typename, Class<?> reloadedClazz, String versionsuffix) {
|
||||
// Clear references to objects that have been GCd
|
||||
// Do they ever get cleared out??
|
||||
Reference<?> r = rq.poll();
|
||||
while (r != null) {
|
||||
classPropertyFetcherInstances.remove(r);
|
||||
r = rq.poll();
|
||||
}
|
||||
try {
|
||||
// Currently not needing to track classPropertyFetcherInstances
|
||||
for (WeakReference<Object> ref : classPropertyFetcherInstances) {
|
||||
Object instance = ref.get();
|
||||
if (instance != null) {
|
||||
if (classPropertyFetcher_clazz == null) {
|
||||
classPropertyFetcher_clazz = instance.getClass().getDeclaredField("clazz");
|
||||
}
|
||||
classPropertyFetcher_clazz.setAccessible(true);
|
||||
Class<?> clazz = (Class<?>) classPropertyFetcher_clazz.get(instance);
|
||||
if (clazz == reloadedClazz) {
|
||||
if (classPropertyFetcher_init == null) {
|
||||
classPropertyFetcher_init = instance.getClass().getDeclaredMethod("init");
|
||||
}
|
||||
classPropertyFetcher_init.setAccessible(true);
|
||||
classPropertyFetcher_init.invoke(instance);
|
||||
if (GlobalConfiguration.debugplugins) {
|
||||
System.err.println("GrailsPlugin: re-initing classPropertyFetcher instance for " + clazz.getName()
|
||||
+ " " + System.identityHashCode(instance));
|
||||
}
|
||||
// System.out.println("re-initing " + reloadedClazz.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean shouldRerunStaticInitializer(String typename, Class<?> clazz, String encodedTimestamp) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded.agent;
|
||||
|
||||
import java.security.ProtectionDomain;
|
||||
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.springsource.loaded.LoadtimeInstrumentationPlugin;
|
||||
import org.springsource.loaded.ReloadEventProcessorPlugin;
|
||||
|
||||
|
||||
/**
|
||||
* What does it do?
|
||||
* <p>
|
||||
* So far the GroovyPlugin can do two different things - configurable through the 'allowCompilableCallSites' flag.
|
||||
*
|
||||
* <p>
|
||||
* If the flag is false: The plugin intercepts two of the main system types in groovy and turns OFF call site compilation. Without
|
||||
* this compilation the compiler will not be generating classes, it will instead be using reflection all the time. This is simpler
|
||||
* to handle (as we intercept reflection) but performance == thesuck.
|
||||
* <p>
|
||||
* If the flag is true: The plugin leaves groovy to compile call sites. We intercept the define method in the classloader used to
|
||||
* define these generated call site classes and ensure they are rewritten correctly. Note there is an alternative here of getting
|
||||
* the SpringLoadedPreProcessor to recognize these special classloaders and just instrument them that way. However, if we let the
|
||||
* plugin do it it is easier to test!
|
||||
* <p>
|
||||
* To see the difference in these approaches, check the numbers in the Groovy Benchmark tests.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.7.0
|
||||
*/
|
||||
public class GroovyPlugin implements LoadtimeInstrumentationPlugin, ReloadEventProcessorPlugin {
|
||||
|
||||
boolean allowCompilableCallSites = true;
|
||||
|
||||
// GroovySunClassLoader - can make the final field non final so it can be set to null (it is checked as part of the isCompilable methodin the callsitegenerator)
|
||||
// CallSiteGenerator - make isCompilable return false, which means we will never generate a direct call to a method that may not yet be on the target
|
||||
// implementing LoadtimeInstrumentationPlugin
|
||||
public boolean accept(String slashedTypeName, ClassLoader classLoader, ProtectionDomain protectionDomain, byte[] bytes) {
|
||||
// TODO take classloader into account?
|
||||
if (slashedTypeName==null) {
|
||||
return false;
|
||||
}
|
||||
if (!allowCompilableCallSites) {
|
||||
return slashedTypeName.equals("org/codehaus/groovy/runtime/callsite/GroovySunClassLoader")
|
||||
|| slashedTypeName.equals("org/codehaus/groovy/runtime/callsite/CallSiteGenerator");
|
||||
} else {
|
||||
if (slashedTypeName.equals("org/codehaus/groovy/reflection/ClassLoaderForClassArtifacts")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public byte[] modify(String slashedClassName, ClassLoader classLoader, byte[] bytes) {
|
||||
if (allowCompilableCallSites) {
|
||||
return modifyDefineInClassLoaderForClassArtifacts(bytes);
|
||||
} else {
|
||||
// Deactivate compilation
|
||||
if (slashedClassName.equals("org/codehaus/groovy/runtime/callsite/GroovySunClassLoader")) {
|
||||
// if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) {
|
||||
// log.info("loadtime modifying " + slashedClassName);
|
||||
// }
|
||||
ClassReader cr = new ClassReader(bytes);
|
||||
NonFinalizer ca = new NonFinalizer("sunVM");
|
||||
// ClassVisitingConstructorAppender ca = new ClassVisitingConstructorAppender("org/springsource/loaded/agent/SpringPlugin",
|
||||
// "recordInstance");
|
||||
cr.accept(ca, 0);
|
||||
byte[] newbytes = ca.getBytes();
|
||||
return newbytes;
|
||||
} else {
|
||||
// must be the CallSiteGenerator
|
||||
ClassReader cr = new ClassReader(bytes);
|
||||
FalseReturner ca = new FalseReturner("isCompilable");
|
||||
cr.accept(ca, 0);
|
||||
byte[] newbytes = ca.getBytes();
|
||||
return newbytes;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] modifyDefineInClassLoaderForClassArtifacts(byte[] bytes) {
|
||||
ClassReader cr = new ClassReader(bytes);
|
||||
ModifyDefineInClassLoaderForClassArtifactsType ca = new ModifyDefineInClassLoaderForClassArtifactsType();
|
||||
cr.accept(ca, 0);
|
||||
byte[] newbytes = ca.getBytes();
|
||||
return newbytes;
|
||||
}
|
||||
|
||||
// called by the modified code
|
||||
public static void recordInstance(Object obj) {
|
||||
}
|
||||
|
||||
// implementing CallbackPlugin
|
||||
public void reloadEvent(String typename, Class<?> clazz, String versionsuffix) {
|
||||
}
|
||||
|
||||
public boolean shouldRerunStaticInitializer(String typename, Class<?> clazz, String encodedTimestamp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded.agent;
|
||||
|
||||
/**
|
||||
* This exception is thrown when something completely unexpected happens (an assertion is violated).
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.7.3
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class Impossible extends RuntimeException {
|
||||
|
||||
public Impossible(Exception cause) {
|
||||
super("This is completely unexpected!", cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded.agent;
|
||||
|
||||
import java.beans.BeanInfo;
|
||||
import java.beans.Introspector;
|
||||
import java.lang.ref.Reference;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.security.ProtectionDomain;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springsource.loaded.GlobalConfiguration;
|
||||
import org.springsource.loaded.LoadtimeInstrumentationPlugin;
|
||||
import org.springsource.loaded.ReloadEventProcessorPlugin;
|
||||
|
||||
|
||||
/**
|
||||
* Reloading plugin for 'poking' JVM classes that are known to cache reflective state. Some of the behaviour is switched ON based on
|
||||
* which classes are loaded. For example the Introspector clearing logic is only activated if the Introspector gets loaded.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.7.3
|
||||
*/
|
||||
public class JVMPlugin implements ReloadEventProcessorPlugin, LoadtimeInstrumentationPlugin {
|
||||
|
||||
private boolean pluginBroken = false;
|
||||
|
||||
private boolean introspectorLoaded = false;
|
||||
private boolean threadGroupContextLoaded = false;
|
||||
|
||||
private Field beanInfoCacheField;
|
||||
private Field declaredMethodCacheField;
|
||||
private Method putMethod;
|
||||
private Class<?> threadGroupContextClass;
|
||||
private Field threadGroupContext_contextsField; /* Map<ThreadGroup,ThreadGroupContext> */
|
||||
private Method threadGroupContext_removeBeanInfoMethod; /* removeBeanInfo(Class<?> type) { */
|
||||
|
||||
@SuppressWarnings({ "restriction", "unchecked" })
|
||||
public void reloadEvent(String typename, Class<?> clazz, String encodedTimestamp) {
|
||||
if (pluginBroken) {
|
||||
return;
|
||||
}
|
||||
if (introspectorLoaded) {
|
||||
// Clear out the Introspector BeanInfo cache entry that might exist for this class
|
||||
|
||||
boolean beanInfoCacheCleared = false;
|
||||
// In Java7 the AppContext stuff is gone, replaced by a ThreadGroupContext.
|
||||
// This code grabs the contexts map from the ThreadGroupContext object and clears out the bean info for the reloaded clazz
|
||||
if (threadGroupContextLoaded) { // In Java 7
|
||||
beanInfoCacheCleared = clearThreadGroupContext(clazz);
|
||||
}
|
||||
|
||||
// GRAILS-9505 - had to introduce the flushFromCaches(). The appcontext we seem to be able to
|
||||
// access from AppContext.getAppContext() isn't the same one the Introspector will be using
|
||||
// so we can fail to clean up the cache. Strangely calling getAppContexts() and clearing them
|
||||
// all (the code commented out below) doesn't fetch all the contexts. I'm sure it is a nuance of
|
||||
// app context handling but for now the introspector call is sufficient.
|
||||
// TODO doesn't this just only clear the beaninfocache for the thread the reload event
|
||||
// is occurring on? which may not be the thread that was actually using the cache.
|
||||
if (!beanInfoCacheCleared) {
|
||||
try {
|
||||
if (beanInfoCacheField == null) {
|
||||
beanInfoCacheField = Introspector.class.getDeclaredField("BEANINFO_CACHE");
|
||||
}
|
||||
beanInfoCacheField.setAccessible(true);
|
||||
Object key = beanInfoCacheField.get(null);
|
||||
Map<Class<?>, BeanInfo> map = (Map<Class<?>, BeanInfo>) sun.awt.AppContext.getAppContext().get(key);
|
||||
if (map != null) {
|
||||
if (GlobalConfiguration.debugplugins) {
|
||||
System.err.println("JVMPlugin: clearing out BeanInfo for " + clazz.getName());
|
||||
}
|
||||
map.remove(clazz);
|
||||
}
|
||||
|
||||
// Set<sun.awt.AppContext> appcontexts = sun.awt.AppContext.getAppContexts();
|
||||
// for (sun.awt.AppContext appcontext: appcontexts) {
|
||||
// map = (Map<Class<?>, BeanInfo>) appcontext.get(key);
|
||||
// if (map != null) {
|
||||
// if (GlobalConfiguration.debugplugins) {
|
||||
// System.err.println("JVMPlugin: clearing out BeanInfo for " + clazz.getName());
|
||||
// }
|
||||
// map.remove(clazz);
|
||||
// }
|
||||
// }
|
||||
Introspector.flushFromCaches(clazz);
|
||||
} catch (NoSuchFieldException nsfe) {
|
||||
// this can happen on Java7 as the field isn't there any more, see the code above.
|
||||
System.out.println("Reloading: JVMPlugin: warning: unable to clear BEANINFO_CACHE, cant find field");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// Clear out the declaredMethodCache that may exist for this class
|
||||
try {
|
||||
if (declaredMethodCacheField == null) {
|
||||
declaredMethodCacheField = Introspector.class.getDeclaredField("declaredMethodCache");
|
||||
}
|
||||
declaredMethodCacheField.setAccessible(true);
|
||||
Object theCache = declaredMethodCacheField.get(null);
|
||||
if (putMethod == null) {
|
||||
putMethod = theCache.getClass().getDeclaredMethod("put", Object.class, Object.class);
|
||||
}
|
||||
putMethod.setAccessible(true);
|
||||
|
||||
if (GlobalConfiguration.debugplugins) {
|
||||
System.err.println("JVMPlugin: clearing out declaredMethodCache in Introspector for class " + clazz.getName());
|
||||
}
|
||||
putMethod.invoke(theCache, clazz, null);
|
||||
} catch (NoSuchFieldException nsfe) {
|
||||
pluginBroken = true;
|
||||
System.out
|
||||
.println("Reloading: JVMPlugin: warning: unable to clear declaredMethodCache, cant find field (JDK update may fix it)");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean clearThreadGroupContext(Class<?> clazz) {
|
||||
boolean beanInfoCacheCleared = false;
|
||||
try {
|
||||
if (threadGroupContextClass == null) {
|
||||
threadGroupContextClass = Class.forName("java.beans.ThreadGroupContext", true,
|
||||
Introspector.class.getClassLoader());
|
||||
}
|
||||
if (threadGroupContextClass != null) {
|
||||
if (threadGroupContext_contextsField == null) {
|
||||
threadGroupContext_contextsField = threadGroupContextClass.getDeclaredField("contexts");
|
||||
threadGroupContext_removeBeanInfoMethod = threadGroupContextClass.getDeclaredMethod("removeBeanInfo",
|
||||
Class.class);
|
||||
}
|
||||
if (threadGroupContext_contextsField != null) {
|
||||
threadGroupContext_contextsField.setAccessible(true);
|
||||
Object threadGroupContext_contextsField_value = threadGroupContext_contextsField.get(null);
|
||||
if (threadGroupContext_contextsField_value == null) {
|
||||
beanInfoCacheCleared = true;
|
||||
} else {
|
||||
if (threadGroupContext_contextsField_value instanceof Map) {
|
||||
// Indicates Java 7 up to rev21
|
||||
Map<?, ?> m = (Map<?, ?>) threadGroupContext_contextsField_value;
|
||||
Collection<?> threadGroupContexts = m.values();
|
||||
for (Object o : threadGroupContexts) {
|
||||
threadGroupContext_removeBeanInfoMethod.setAccessible(true);
|
||||
threadGroupContext_removeBeanInfoMethod.invoke(o, clazz);
|
||||
}
|
||||
beanInfoCacheCleared = true;
|
||||
} else {
|
||||
// At update Java7u21 it changes
|
||||
Class weakIdentityMapClazz = threadGroupContext_contextsField.getType();
|
||||
Field tableField = weakIdentityMapClazz.getDeclaredField("table");
|
||||
tableField.setAccessible(true);
|
||||
Reference<?>[] refs = (Reference[])tableField.get(threadGroupContext_contextsField_value);
|
||||
Field valueField = null;
|
||||
if (refs!=null) {
|
||||
for (int i=0; i<refs.length; i++) {
|
||||
Reference<?> r = refs[i];
|
||||
Object o = (r==null?null:r.get());
|
||||
if (o!=null) {
|
||||
if (valueField==null) {
|
||||
valueField = r.getClass().getDeclaredField("value");
|
||||
}
|
||||
valueField.setAccessible(true);
|
||||
Object threadGroupContext = valueField.get(r);
|
||||
threadGroupContext_removeBeanInfoMethod.setAccessible(true);
|
||||
threadGroupContext_removeBeanInfoMethod.invoke(threadGroupContext, clazz);
|
||||
}
|
||||
}
|
||||
}
|
||||
beanInfoCacheCleared = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
System.err.println("Unexpected problem clearing ThreadGroupContext beaninfo: ");
|
||||
t.printStackTrace();
|
||||
}
|
||||
return beanInfoCacheCleared;
|
||||
}
|
||||
|
||||
public boolean accept(String slashedTypeName, ClassLoader classLoader, ProtectionDomain protectionDomain, byte[] bytes) {
|
||||
if (slashedTypeName!=null) {
|
||||
if (slashedTypeName.equals("java/beans/Introspector")) {
|
||||
introspectorLoaded = true;
|
||||
} else if (slashedTypeName.equals("java/beans/ThreadGroupContext")) {
|
||||
threadGroupContextLoaded = true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public byte[] modify(String slashedClassName, ClassLoader classLoader, byte[] bytes) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean shouldRerunStaticInitializer(String typename, Class<?> clazz, String encodedTimestamp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded.agent;
|
||||
|
||||
import org.objectweb.asm.ClassAdapter;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.MethodAdapter;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
import org.springsource.loaded.Constants;
|
||||
import org.springsource.loaded.GlobalConfiguration;
|
||||
import org.springsource.loaded.MethodInvokerRewriter;
|
||||
import org.springsource.loaded.TypeRegistry;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.7.3
|
||||
*/
|
||||
public class ModifyDefineInClassLoaderForClassArtifactsType extends ClassAdapter implements Constants {
|
||||
|
||||
public ModifyDefineInClassLoaderForClassArtifactsType() {
|
||||
super(new ClassWriter(0)); // TODO review 0 here
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
return ((ClassWriter) cv).toByteArray();
|
||||
}
|
||||
|
||||
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
|
||||
if (name.equals("define")) {
|
||||
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
|
||||
return new DefineClassModifierVisitor(mv);
|
||||
} else {
|
||||
return super.visitMethod(access, name, desc, signature, exceptions);
|
||||
}
|
||||
}
|
||||
|
||||
class DefineClassModifierVisitor extends MethodAdapter implements Constants {
|
||||
|
||||
public DefineClassModifierVisitor(MethodVisitor mv) {
|
||||
super(mv);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitCode() {
|
||||
mv.visitVarInsn(ALOAD, 0); // ClassLoaderForClassArtifacts this
|
||||
mv.visitVarInsn(ALOAD, 1); // String name
|
||||
mv.visitVarInsn(ALOAD, 2); // byte[] bytes
|
||||
mv.visitMethodInsn(INVOKESTATIC, "org/springsource/loaded/agent/ModifyDefineInClassLoaderForClassArtifactsType",
|
||||
"modify", "(Ljava/lang/ClassLoader;Ljava/lang/String;[B)[B");
|
||||
mv.visitVarInsn(ASTORE, 2);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The classloader for class artifacts is used to load the generated classes for call sites. We need to rewrite these classes
|
||||
* because they may be either calling something that disappears on a later reload (so need to fail appropriately) or calling
|
||||
* something that isnt there on the first load - in this latter case they are changed to route the dynamic executor method.
|
||||
*
|
||||
* @param classloader
|
||||
* @param name
|
||||
* @param bytes
|
||||
* @return
|
||||
*/
|
||||
public static byte[] modify(ClassLoader classloader, String name, byte[] bytes) {
|
||||
// System.out.println("Seen '" + name + "' being defined by " + classloader);
|
||||
// ClassPrinter.print(bytes, true);
|
||||
ClassLoader parent = classloader.getParent();
|
||||
if (parent != null) {
|
||||
TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(parent);
|
||||
// classloader.getParent() can return null - I've seen it when the target of the call being compiled is
|
||||
// java_lang_class$getDeclaredFields (i.e. a system class that cant change, so rewriting is unnecessary...)
|
||||
if (typeRegistry != null) {
|
||||
bytes = typeRegistry.methodCallRewrite(bytes);
|
||||
} else {
|
||||
if (GlobalConfiguration.verboseMode) {
|
||||
System.out.println("No type registry found for parent classloader: " + parent);
|
||||
}
|
||||
bytes = MethodInvokerRewriter.rewrite(null, bytes, true);
|
||||
}
|
||||
} else {
|
||||
bytes = MethodInvokerRewriter.rewrite(null, bytes, true);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded.agent;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
import org.objectweb.asm.ClassAdapter;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.FieldVisitor;
|
||||
import org.springsource.loaded.Constants;
|
||||
|
||||
|
||||
/**
|
||||
* Makes a field or fields non final.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.7.0
|
||||
*/
|
||||
public class NonFinalizer extends ClassAdapter implements Constants {
|
||||
|
||||
private String fieldname;
|
||||
|
||||
/**
|
||||
* This ClassAdapter will visit a class and within the constructors it will add a call to the specified method (assumed static)
|
||||
* just before each constructor returns. The target of the call should be a collecting method that will likely do something with
|
||||
* the instances later on class reload.
|
||||
*
|
||||
* @param owner
|
||||
* @param name
|
||||
*/
|
||||
public NonFinalizer(String fieldname) {
|
||||
super(new ClassWriter(0)); // TODO review 0 here
|
||||
this.fieldname = fieldname;
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
return ((ClassWriter) cv).toByteArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
|
||||
if (name.equals(fieldname)) {
|
||||
return super.visitField(access & (~Modifier.FINAL), name, desc, signature, value);
|
||||
} else {
|
||||
return super.visitField(access, name, desc, signature, value);
|
||||
}
|
||||
}
|
||||
|
||||
// public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
|
||||
// if (name.equals("<init>")) {
|
||||
// MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
|
||||
// return new ConstructorAppender(mv);
|
||||
// } else {
|
||||
// return super.visitMethod(access, name, desc, signature, exceptions);
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* This constructor appender includes a couple of instructions at the end of each constructor it is asked to visit. It
|
||||
* recognizes the end by observing a RETURN instruction. The instructions are inserted just before the RETURN.
|
||||
*/
|
||||
// class ConstructorAppender extends MethodAdapter implements Constants {
|
||||
//
|
||||
// public ConstructorAppender(MethodVisitor mv) {
|
||||
// super(mv);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void visitInsn(int opcode) {
|
||||
// if (opcode == RETURN) {
|
||||
// mv.visitVarInsn(ALOAD, 0);
|
||||
// mv.visitMethodInsn(INVOKESTATIC, calleeOwner, calleeName, "(Ljava/lang/Object;)V");
|
||||
// }
|
||||
// super.visitInsn(opcode);
|
||||
// }
|
||||
//
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded.agent;
|
||||
|
||||
import org.objectweb.asm.ClassReader;
|
||||
|
||||
public class PluginUtils {
|
||||
/**
|
||||
* If adding instance tracking, the classToCall must implement: <tt>public static void recordInstance(Object obj)</tt>.
|
||||
*
|
||||
* @param bytes the bytes for the class to which instance tracking is being added
|
||||
* @param classToCall the class to call when a new instance is created
|
||||
* @return the modified bytes for the class
|
||||
*/
|
||||
public static byte[] addInstanceTracking(byte[] bytes, String classToCall) {
|
||||
ClassReader cr = new ClassReader(bytes);
|
||||
ClassVisitingConstructorAppender ca = new ClassVisitingConstructorAppender(classToCall, "recordInstance");
|
||||
cr.accept(ca, 0);
|
||||
byte[] newbytes = ca.getBytes();
|
||||
return newbytes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded.agent;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.7.1
|
||||
*/
|
||||
public enum ReloadDecision {
|
||||
/**
|
||||
* YES means the plugin thinks it should definetly be reloadable
|
||||
*/
|
||||
YES,
|
||||
/**
|
||||
* NO means the plugin thinks is should definetly not be reloadable
|
||||
*/
|
||||
NO,
|
||||
/**
|
||||
* PASS means the plugin has no opinion, leave it to other plugins to decide
|
||||
*/
|
||||
PASS
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded.agent;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.springsource.loaded.FileChangeListener;
|
||||
import org.springsource.loaded.GlobalConfiguration;
|
||||
import org.springsource.loaded.ReloadableType;
|
||||
import org.springsource.loaded.TypeRegistry;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class ReloadableFileChangeListener implements FileChangeListener {
|
||||
|
||||
private static Logger log = Logger.getLogger(ReloadableFileChangeListener.class.getName());
|
||||
|
||||
private TypeRegistry typeRegistry;
|
||||
private Map<File, ReloadableType> correspondingReloadableTypes = new HashMap<File, ReloadableType>();
|
||||
|
||||
public ReloadableFileChangeListener(TypeRegistry typeRegistry) {
|
||||
this.typeRegistry = typeRegistry;
|
||||
}
|
||||
|
||||
public void fileChanged(File file) {
|
||||
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) {
|
||||
log.info("ReloadableFileChangeListener: change detected in " + file);
|
||||
}
|
||||
ReloadableType rtype = correspondingReloadableTypes.get(file);
|
||||
typeRegistry.loadNewVersion(rtype, file);
|
||||
}
|
||||
|
||||
public void register(ReloadableType rtype, File file) {
|
||||
correspondingReloadableTypes.put(file, rtype);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2010-2014 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded.agent;
|
||||
|
||||
import java.lang.instrument.ClassFileTransformer;
|
||||
import java.lang.instrument.Instrumentation;
|
||||
|
||||
/**
|
||||
* Basic agent implementation. This agent is declared in the META-INF/MANIFEST.MF file - that is how
|
||||
* it is 'plugged in' to the JVM when '-javaagent:springloaded.jar' is used.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @sinc 0.5.0
|
||||
*/
|
||||
public class SpringLoadedAgent {
|
||||
|
||||
private static ClassFileTransformer transformer = new ClassPreProcessorAgentAdapter();
|
||||
|
||||
private static Instrumentation instrumentation;
|
||||
|
||||
public static void premain(String options, Instrumentation inst) {
|
||||
// Handle duplicate agents
|
||||
if (instrumentation != null) {
|
||||
return;
|
||||
}
|
||||
instrumentation = inst;
|
||||
instrumentation.addTransformer(transformer);
|
||||
}
|
||||
|
||||
public static void agentmain(String options, Instrumentation inst) {
|
||||
if (instrumentation != null) {
|
||||
return;
|
||||
}
|
||||
instrumentation = inst;
|
||||
instrumentation.addTransformer(transformer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Instrumentation instance
|
||||
*/
|
||||
public static Instrumentation getInstrumentation() {
|
||||
if (instrumentation == null) {
|
||||
throw new UnsupportedOperationException("Java 5 was not started with preMain -javaagent for SpringLoaded");
|
||||
}
|
||||
return instrumentation;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,605 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded.agent;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.security.CodeSource;
|
||||
import java.security.ProtectionDomain;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.springsource.loaded.Constants;
|
||||
import org.springsource.loaded.GlobalConfiguration;
|
||||
import org.springsource.loaded.IsReloadableTypePlugin;
|
||||
import org.springsource.loaded.LoadtimeInstrumentationPlugin;
|
||||
import org.springsource.loaded.Plugin;
|
||||
import org.springsource.loaded.ReloadableType;
|
||||
import org.springsource.loaded.SystemClassReflectionRewriter;
|
||||
import org.springsource.loaded.TypeRegistry;
|
||||
import org.springsource.loaded.Utils;
|
||||
import org.springsource.loaded.SystemClassReflectionRewriter.RewriteResult;
|
||||
import org.springsource.loaded.ri.ReflectiveInterceptor;
|
||||
|
||||
|
||||
/**
|
||||
* The entry point for the agent - all classes that can be modified will be passed into preProcess(). They have to be dealt with in
|
||||
* many different ways:
|
||||
* <ul>
|
||||
* <li>reloadable types need their bytecode rewriting
|
||||
* <li>'framework' types (not loaded by the system classloader) need their reflection rewritten
|
||||
* <li>system classes need their reflection rewritten in a slightly different way
|
||||
* </ul>
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class SpringLoadedPreProcessor implements Constants {
|
||||
|
||||
private static Logger log = Logger.getLogger(SpringLoadedPreProcessor.class.getName());
|
||||
private static List<Plugin> plugins = null;
|
||||
|
||||
// Global control to turn off the agent, used when testing
|
||||
public static boolean disabled = false;
|
||||
|
||||
// Once the first reloadabletype is hit, we can start initializing the system class with reflective interceptors.
|
||||
// Doing it early can lead to hangs
|
||||
private static boolean firstReloadableTypeHit = false;
|
||||
|
||||
// These are system classes that contain reflection code and so need instrumenting when encountered.
|
||||
private static List<String> systemClassesContainingReflection;
|
||||
|
||||
// Once the system classes have been encountered and instrumented, they need initialization once they have been defined
|
||||
// to the VM. This records the list of those that have not yet been initialized.
|
||||
private Map<String, Integer> systemClassesRequiringInitialization = new HashMap<String, Integer>();
|
||||
|
||||
public void initialize() {
|
||||
// When spring loaded is running as an agent, it should not be defining types directly (this setting does not apply to
|
||||
// the generated types)
|
||||
GlobalConfiguration.directlyDefineTypes = false;
|
||||
GlobalConfiguration.fileSystemMonitoring = true;
|
||||
systemClassesContainingReflection = new ArrayList<String>();
|
||||
// So that jaxb annotations will cause discovery of the correct properties:
|
||||
systemClassesContainingReflection.add("com/sun/xml/internal/bind/v2/model/nav/ReflectionNavigator");
|
||||
// So that proxies are generated with the right set of methods inside
|
||||
systemClassesContainingReflection.add("sun/misc/ProxyGenerator");
|
||||
// (at least) the call to getModifiers() needs interception
|
||||
systemClassesContainingReflection.add("java/lang/reflect/Proxy");
|
||||
// So that javabeans introspection is intercepter
|
||||
systemClassesContainingReflection.add("java/beans/Introspector");
|
||||
// Don't need this right now, instead we are not removing 'final' from the serialVersionUID
|
||||
// // Need to catch at least the call to access the serialVersionUID made in getDeclaredSUID()
|
||||
// systemClassesContainingReflection.add("java/io/ObjectStreamClass$2");
|
||||
}
|
||||
|
||||
/**
|
||||
* Main entry point to Spring Loaded when it is running as an agent. This method will use the classLoader and the class name in
|
||||
* order to determine whether the type should be made reloadable. Non-reloadable types will at least get their call sites
|
||||
* rewritten.
|
||||
*
|
||||
* @return modified bytes
|
||||
*/
|
||||
public byte[] preProcess(ClassLoader classLoader, String slashedClassName, ProtectionDomain protectionDomain, byte[] bytes) {
|
||||
if (disabled) {
|
||||
return bytes;
|
||||
}
|
||||
// System.err.println("> SpringLoadedPreProcessor.preProcess(classLoader=" + classLoader + ",slashedClassName="
|
||||
// + slashedClassName + ",...)");
|
||||
|
||||
// TODO need configurable debug here, ability to dump any code before/after
|
||||
for (Plugin plugin : getGlobalPlugins()) {
|
||||
if (plugin instanceof LoadtimeInstrumentationPlugin) {
|
||||
LoadtimeInstrumentationPlugin loadtimeInstrumentationPlugin = (LoadtimeInstrumentationPlugin) plugin;
|
||||
if (loadtimeInstrumentationPlugin.accept(slashedClassName, classLoader, protectionDomain, bytes)) {
|
||||
bytes = loadtimeInstrumentationPlugin.modify(slashedClassName, classLoader, bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tryToEnsureSystemClassesInitialized(slashedClassName);
|
||||
|
||||
TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(classLoader);
|
||||
// if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINER)) {
|
||||
// logEntryToPreprocess(classLoader, slashedClassName, typeRegistry);
|
||||
// }
|
||||
// NULL typeRegistry means we should not be fiddling in what this classLoader is loading
|
||||
// TODO is that true? what about rewriting reflection code outside of the loader doing reloading?
|
||||
if (typeRegistry == null) {
|
||||
if (classLoader == null) {
|
||||
if (systemClassesContainingReflection.contains(slashedClassName)) {
|
||||
try {
|
||||
RewriteResult rr = SystemClassReflectionRewriter.rewrite(slashedClassName, bytes);
|
||||
// System.err.println("Type " + slashedClassName + " rewrite summary: " + rr.summarize());
|
||||
systemClassesRequiringInitialization.put(slashedClassName, rr.bits);
|
||||
return rr.bytes;
|
||||
} catch (Exception re) {
|
||||
re.printStackTrace();
|
||||
}
|
||||
|
||||
// make conditional?
|
||||
// } else {
|
||||
// // We should really track whether this type is using reflection...
|
||||
// if (SystemClassReflectionInvestigator.investigate(slashedClassName, bytes) > 0) {
|
||||
// RewriteResult rr = SystemClassReflectionRewriter.rewrite(slashedClassName, bytes);
|
||||
// System.err.println("Type " + slashedClassName + " rewrite summary: " + rr.summarize());
|
||||
// systemClassesRequiringInitialization.put(slashedClassName, rr.bits);
|
||||
// return rr.bytes;
|
||||
// }
|
||||
}
|
||||
// } else if (needsClientSideRewriting(slashedClassName)) {
|
||||
// bytes = typeRegistry.methodCallRewriteUseCacheIfAvailable(slashedClassName, bytes);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
// What happens here?
|
||||
// 1. Determine if the type should be made reloadable
|
||||
// 2. If NO, but something in this classloader might be, then rewrite the call sites.
|
||||
// 3. If NO, and nothing in this classloader might be, return the original bytes
|
||||
// 4. If YES, make the type reloadable (including rewriting call sites)
|
||||
|
||||
if (typeRegistry.isReloadableTypeName(slashedClassName, protectionDomain, bytes)) {
|
||||
if (!firstReloadableTypeHit) {
|
||||
firstReloadableTypeHit = true;
|
||||
// TODO move into the ctor for ReloadableType so that it can't block loading
|
||||
tryToEnsureSystemClassesInitialized(slashedClassName);
|
||||
}
|
||||
|
||||
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) {
|
||||
log.info("processing " + slashedClassName + " as a reloadable type");
|
||||
}
|
||||
|
||||
try {
|
||||
// TODO decide one way or the other on slashed/dotted from preprocessor to infrastructure
|
||||
String dottedClassName = slashedClassName.replace('/', '.');
|
||||
String watchPath = getWatchPathFromProtectionDomain(protectionDomain, slashedClassName);
|
||||
if (watchPath == null) {
|
||||
// For a CGLIB generated type, we may still need to make the type reloadable. For example:
|
||||
// type: com/vmware/rabbit/ApplicationContext$$EnhancerByCGLIB$$512eb60c
|
||||
// codesource determined to be: file:/Users/aclement/springsource/tc-server-developer-2.1.1.RELEASE/spring-insight-instance/wtpwebapps/hello-rabbit-client/WEB-INF/lib/cglib-nodep-2.2.jar <no signer certificates>
|
||||
// But if the type 'com/vmware/rabbit/ApplicationContext' is reloadable, then this should be too
|
||||
boolean makeReloadableAnyway = false;
|
||||
int cglibIndex = slashedClassName.indexOf("$$EnhancerByCGLIB");
|
||||
|
||||
if (cglibIndex != -1) {
|
||||
String originalType = slashedClassName.substring(0, cglibIndex);
|
||||
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) {
|
||||
log.info("Appears to be a CGLIB type, checking if type " + originalType + " is reloadable");
|
||||
}
|
||||
if (typeRegistry.isReloadableTypeName(originalType)) {
|
||||
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) {
|
||||
log.info("Type " + originalType + " is reloadable, so making CGLIB type " + slashedClassName
|
||||
+ " reloadable");
|
||||
}
|
||||
makeReloadableAnyway = true;
|
||||
}
|
||||
}
|
||||
|
||||
int cglibIndex2 = slashedClassName.indexOf("$$FastClassByCGLIB");
|
||||
if (cglibIndex2 != -1) {
|
||||
String originalType = slashedClassName.substring(0, cglibIndex2);
|
||||
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) {
|
||||
log.info("Appears to be a CGLIB FastClass type, checking if type " + originalType + " is reloadable");
|
||||
}
|
||||
if (typeRegistry.isReloadableTypeName(originalType)) {
|
||||
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) {
|
||||
log.info("Type " + originalType + " is reloadable, so making CGLIB type " + slashedClassName
|
||||
+ " reloadable");
|
||||
}
|
||||
makeReloadableAnyway = true;
|
||||
}
|
||||
}
|
||||
|
||||
int proxyIndex = slashedClassName.indexOf("$Proxy");
|
||||
if (proxyIndex == 0 || (proxyIndex > 0 && slashedClassName.charAt(proxyIndex - 1) == '/')) {
|
||||
// Determine if the interfaces being implemented are reloadable
|
||||
String[] interfacesImplemented = Utils.discoverInterfaces(bytes);
|
||||
if (interfacesImplemented != null) {
|
||||
for (int i = 0; i < interfacesImplemented.length; i++) {
|
||||
if (typeRegistry.isReloadableTypeName(interfacesImplemented[i])) {
|
||||
makeReloadableAnyway = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// GRAILS-8098
|
||||
// The scaffolding loader will load stuff in this innerloader - if we don't make the types in it reloadable then they will clash
|
||||
// with the original (ordinary version) controller loaded by URLClassLoader (e.g. in an istcheck for some type we will
|
||||
// not find it in the InnerClassLoader, but find it in the super classloader, and it'll be the wrong one).
|
||||
// I wonder if the more general rule should be that
|
||||
// all classloaders below one loading reloadable stuff should also load reloadable stuff.
|
||||
if (!makeReloadableAnyway && classLoader.getClass().getName().endsWith("GroovyClassLoader$InnerLoader")) {
|
||||
makeReloadableAnyway = true;
|
||||
}
|
||||
|
||||
if (!makeReloadableAnyway) {
|
||||
// can't watch it for updates (it comes from a jar perhaps) so just rewrite call sites and return
|
||||
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) {
|
||||
log.info("can't watch " + slashedClassName + ": not making it reloadable");
|
||||
}
|
||||
if (needsClientSideRewriting(slashedClassName)) {
|
||||
bytes = typeRegistry.methodCallRewriteUseCacheIfAvailable(slashedClassName, bytes);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
ReloadableType rtype = typeRegistry.addType(dottedClassName, bytes);
|
||||
if (rtype == null && GlobalConfiguration.callsideRewritingOn) {
|
||||
// it is not a candidate for being made reloadable (maybe it is an annotation type)
|
||||
// but we still need to rewrite call sites.
|
||||
bytes = typeRegistry.methodCallRewrite(bytes);
|
||||
} else {
|
||||
if (GlobalConfiguration.fileSystemMonitoring && watchPath != null) {
|
||||
typeRegistry.monitorForUpdates(rtype, watchPath);
|
||||
}
|
||||
return rtype.bytesLoaded;
|
||||
}
|
||||
} catch (RuntimeException re) {
|
||||
log.throwing("SpringLoadedPreProcessor", "preProcess", re);
|
||||
throw re;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
// TODO what happens across classloader boundaries? (for regular code and reflective calls)
|
||||
if (needsClientSideRewriting(slashedClassName)) {
|
||||
bytes = typeRegistry.methodCallRewriteUseCacheIfAvailable(slashedClassName, bytes);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
log.log(Level.SEVERE, "Unexpected problem transforming call sites", t);
|
||||
}
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private void tryToEnsureSystemClassesInitialized(String slashedClassName) {
|
||||
if (firstReloadableTypeHit && !systemClassesRequiringInitialization.isEmpty()) {
|
||||
int lastSlash = slashedClassName.lastIndexOf('/');
|
||||
String pkg = lastSlash == -1 ? null : slashedClassName.substring(0, lastSlash);
|
||||
ensurePreparedForInjection();
|
||||
List<String> toRemoveList = new ArrayList<String>();
|
||||
for (Map.Entry<String, Integer> me : systemClassesRequiringInitialization.entrySet()) {
|
||||
String classname = me.getKey();
|
||||
// A ClassCircularityError can occur in the injectReflectiveInterceptorMethods() method below. Reason:
|
||||
// ===
|
||||
// CCE: "A class or interface could not be loaded because it would be its own superclass or superinterface"
|
||||
// according to the Java Virtual Machine Specification (JVMS 2.17.2).
|
||||
// The implementation of the virtual machine generally detects this by noting the
|
||||
// beginning of an attempt to load a class and then noticing when the
|
||||
// same task thread attempts to load that same class again before the original
|
||||
// attempt has completed (is still in progress).
|
||||
// ===
|
||||
// So, if we attempt to 'fix up' a class here which has a relationship with the type we are currently
|
||||
// loading, then it looks like a CCE. The crude initial fix is to avoid working on anything in the
|
||||
// same package as us. This doesn't quite fix all the cases of course but addresses a chunk of them.
|
||||
// One remaining case I can clearly see in the log is that java.beans.Introspector (which needs fixing up)
|
||||
// uses a field of type com.sun.beans.WeakCache.
|
||||
|
||||
// A full list of the special relationships could be encoded here (don't touch X until Y,Z,etc loaded)
|
||||
// but that will just get out of date so quickly. Given that it isn't necessarily a problem because
|
||||
// the fixing up will be re-attempted again, the simplest thing would be just to avoid printing
|
||||
// CCEs (but log all other issues).
|
||||
if (pkg != null && classname.startsWith(pkg)) {
|
||||
continue;
|
||||
}
|
||||
int bits = me.getValue();
|
||||
try {
|
||||
Class<?> clazz = SpringLoadedPreProcessor.class.getClassLoader().loadClass(classname.replace('/', '.'));
|
||||
injectReflectiveInterceptorMethods(slashedClassName, bits, clazz);
|
||||
toRemoveList.add(classname);
|
||||
} catch (ClassCircularityError cce) {
|
||||
// See comment above. 'assume' this is OK, the initialization will happen again next time around.
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
for (String toRemove : toRemoveList) {
|
||||
systemClassesRequiringInitialization.remove(toRemove); // TODO threads?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO should cache these retrieved fields/methods for injection into types
|
||||
/**
|
||||
* This method tries to inject the ReflectiveInterceptor methods into any system types that have been rewritten.
|
||||
*/
|
||||
private void injectReflectiveInterceptorMethods(String slashedClassName, int bits, Class<?> clazz) throws NoSuchFieldException,
|
||||
IllegalAccessException, NoSuchMethodException {
|
||||
// TODO log the bits
|
||||
if ((bits & Constants.JLC_GETDECLAREDFIELDS) != 0) {
|
||||
Field f = clazz.getDeclaredField("__sljlcgdfs");
|
||||
f.setAccessible(true);
|
||||
f.set(null, method_jlcgdfs);
|
||||
}
|
||||
if ((bits & Constants.JLC_GETDECLAREDFIELD) != 0) {
|
||||
Field f = clazz.getDeclaredField(jlcgdf);
|
||||
f.setAccessible(true);
|
||||
f.set(null, method_jlcgdf);
|
||||
}
|
||||
if ((bits & Constants.JLC_GETFIELD) != 0) {
|
||||
Field f = clazz.getDeclaredField(jlcgf);
|
||||
f.setAccessible(true);
|
||||
f.set(null, method_jlcgf);
|
||||
}
|
||||
if ((bits & Constants.JLC_GETDECLAREDMETHODS) != 0) {
|
||||
Field f = clazz.getDeclaredField(jlcgdms);
|
||||
f.setAccessible(true);
|
||||
f.set(null, method_jlcgdms);
|
||||
}
|
||||
if ((bits & Constants.JLC_GETDECLAREDMETHOD) != 0) {
|
||||
Field f = clazz.getDeclaredField(jlcgdm);
|
||||
f.setAccessible(true);
|
||||
f.set(null, method_jlcgdm);
|
||||
}
|
||||
if ((bits & Constants.JLC_GETMETHOD) != 0) {
|
||||
Field f = clazz.getDeclaredField(jlcgm);
|
||||
f.setAccessible(true);
|
||||
f.set(null, method_jlcgm);
|
||||
}
|
||||
if ((bits & Constants.JLC_GETDECLAREDCONSTRUCTOR) != 0) {
|
||||
Field f = clazz.getDeclaredField(jlcgdc);
|
||||
f.setAccessible(true);
|
||||
f.set(null, method_jlcgdc);
|
||||
}
|
||||
if ((bits & Constants.JLC_GETMODIFIERS) != 0) {
|
||||
Field f = clazz.getDeclaredField(jlcgmods);
|
||||
f.setAccessible(true);
|
||||
f.set(null, method_jlcgmods);
|
||||
}
|
||||
if ((bits & Constants.JLC_GETMETHODS) != 0) {
|
||||
Field f = clazz.getDeclaredField(jlcgms);
|
||||
f.setAccessible(true);
|
||||
f.set(null, method_jlcgms);
|
||||
}
|
||||
if ((bits & Constants.JLC_GETCONSTRUCTOR) != 0) {
|
||||
Field f = clazz.getDeclaredField(jlcgc);
|
||||
f.setAccessible(true);
|
||||
f.set(null, method_jlcgc);
|
||||
}
|
||||
}
|
||||
|
||||
private static final Class<?> EMPTY_CLASS_ARRAY_CLAZZ = Class[].class;
|
||||
// TODO threads
|
||||
private static boolean prepared = false;
|
||||
private static Method method_jlcgdfs, method_jlcgdf, method_jlcgf, method_jlcgdms, method_jlcgdm, method_jlcgm, method_jlcgdc,
|
||||
method_jlcgc, method_jlcgmods, method_jlcgms;
|
||||
|
||||
/**
|
||||
* Cache the Method objects that will be injected.
|
||||
*/
|
||||
private void ensurePreparedForInjection() {
|
||||
if (!prepared) {
|
||||
try {
|
||||
Class<ReflectiveInterceptor> clazz = ReflectiveInterceptor.class;
|
||||
method_jlcgdfs = clazz.getDeclaredMethod("jlClassGetDeclaredFields", Class.class);
|
||||
method_jlcgdf = clazz.getDeclaredMethod("jlClassGetDeclaredField", Class.class, String.class);
|
||||
method_jlcgf = clazz.getDeclaredMethod("jlClassGetField", Class.class, String.class);
|
||||
method_jlcgdms = clazz.getDeclaredMethod("jlClassGetDeclaredMethods", Class.class);
|
||||
method_jlcgdm = clazz.getDeclaredMethod("jlClassGetDeclaredMethod", Class.class, String.class,
|
||||
EMPTY_CLASS_ARRAY_CLAZZ);
|
||||
method_jlcgm = clazz.getDeclaredMethod("jlClassGetMethod", Class.class, String.class, EMPTY_CLASS_ARRAY_CLAZZ);
|
||||
method_jlcgdc = clazz.getDeclaredMethod("jlClassGetDeclaredConstructor", Class.class, EMPTY_CLASS_ARRAY_CLAZZ);
|
||||
method_jlcgc = clazz.getDeclaredMethod("jlClassGetConstructor", Class.class, EMPTY_CLASS_ARRAY_CLAZZ);
|
||||
method_jlcgmods = clazz.getDeclaredMethod("jlClassGetModifiers", Class.class);
|
||||
method_jlcgms = clazz.getDeclaredMethod("jlClassGetMethods", Class.class);
|
||||
} catch (NoSuchMethodException nsme) {
|
||||
// cant happen, a-hahaha
|
||||
throw new Impossible(nsme);
|
||||
}
|
||||
prepared = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean needsClientSideRewriting(String slashedClassName) {
|
||||
if (slashedClassName!=null && slashedClassName.charAt(0)=='o' && slashedClassName.startsWith("org/springsource/loaded")) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine where to watch for changes based on the protectionDomain. Relying on the protectionDomain may prove fragile though,
|
||||
* as it is up to the classloader in question to create it. Some classloaders will create one protectionDomain per 'directory'
|
||||
* containing class files (and so the slashedClassName must be appended to the codesource). Some classloaders have a
|
||||
* protectiondomain per class.
|
||||
*
|
||||
* @param protectionDomain the protection domain passed in to the defineclass call
|
||||
* @param slashedClassName the slashed class name currently being defined
|
||||
* @return the path to watch for changes to this class
|
||||
*/
|
||||
private String getWatchPathFromProtectionDomain(ProtectionDomain protectionDomain, String slashedClassName) {
|
||||
String watchPath = null;
|
||||
// System.err.println("protectionDomain=" + protectionDomain + " slashedClassName=" + slashedClassName + " protdom="
|
||||
// + protectionDomain + " codesource=" + (protectionDomain == null ? "null" : protectionDomain.getCodeSource()));
|
||||
if (protectionDomain == null) {
|
||||
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.WARNING)) {
|
||||
log.warning("Changes to type cannot be tracked: " + slashedClassName + " - no protection domain");
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
CodeSource codeSource = protectionDomain.getCodeSource();
|
||||
if (codeSource.getLocation() == null) {
|
||||
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.WARNING)) {
|
||||
log.warning("null codesource for " + slashedClassName);
|
||||
}
|
||||
} else {
|
||||
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINEST)) {
|
||||
log.finest("Codesource.getLocation()=" + codeSource.getLocation());
|
||||
}
|
||||
// A 'URI is not hierarchical' message can come out when the File ctor is called. Cases seen
|
||||
// so far:
|
||||
// GRAILS-10384: relative URL file:../foo/bar - should have built it with new File().toURI.toURL() and not just new URL()
|
||||
File file = null;
|
||||
URI uri = null;
|
||||
try {
|
||||
uri = codeSource.getLocation().toURI();
|
||||
file = new File(uri);
|
||||
} catch (IllegalArgumentException iae) {
|
||||
boolean recovered = false;
|
||||
if (iae.toString().indexOf("URI is not hierarchical")!=-1) {
|
||||
// try another approach...
|
||||
String uristring = uri.toString();
|
||||
if (uristring.startsWith("file:../")) {
|
||||
file = new File(uristring.substring(8)).getAbsoluteFile();
|
||||
} else if (uristring.startsWith("file:./")) {
|
||||
file = new File(uristring.substring(7)).getAbsoluteFile();
|
||||
}
|
||||
if (file.exists()) {
|
||||
recovered = true;
|
||||
}
|
||||
}
|
||||
if (!recovered) {
|
||||
System.out.println("Unable to watch file: classname = "+slashedClassName+" codesource location = "+codeSource.getLocation()+" ex = "+iae.toString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (file.isDirectory()) {
|
||||
file = new File(file, slashedClassName + ".class");
|
||||
} else if (file.getName().endsWith(".class")) {
|
||||
// great! nothing to do
|
||||
} else if (file.getName().endsWith(".jar")) {
|
||||
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.WARNING)) {
|
||||
log.warning("unable to watch this jar file entry: " + slashedClassName.replace('/', '.')
|
||||
+ ". Computed location=" + file.toString());
|
||||
}
|
||||
return null;
|
||||
} else if (file.toString().equals("/groovy/script") || file.toString().equals("\\groovy\\script")) {
|
||||
// nothing to do, compiled/loaded by a GroovyClassLoader$InnerLoader - there is nothing to watch. If the type is to be
|
||||
// reloaded we will have to be told via an alternate route
|
||||
return null;
|
||||
} else if (!file.toString().endsWith(".class")) {
|
||||
// GRAILS-9076: it ended in .groovy
|
||||
// GRAILS-9069/GRAILS-9070: it was /groovy/shell
|
||||
// something other than a class, no point in watching it
|
||||
return null;
|
||||
} else {
|
||||
throw new UnsupportedOperationException("unable to watch " + slashedClassName.replace('/', '.')
|
||||
+ ". Computed location=" + file.toString());
|
||||
}
|
||||
watchPath = file.toString();
|
||||
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) {
|
||||
log.info("Watched location for changes to " + slashedClassName + " is " + watchPath);
|
||||
}
|
||||
}
|
||||
} catch (URISyntaxException e) {
|
||||
throw new IllegalStateException("Unexpected problem processing URI ", e);
|
||||
}
|
||||
}
|
||||
return watchPath;
|
||||
}
|
||||
|
||||
private static final String[] uninterestingPrefixes = new String[] { "org/codehaus/groovy/", "groovy/", "freemarker/",
|
||||
"org/springframework/" };
|
||||
|
||||
/**
|
||||
* Record expensive-to-compute log message about what we are doing.
|
||||
*/
|
||||
private void logEntryToPreprocess(ClassLoader classLoader, String slashedClassName, TypeRegistry typeRegistry) {
|
||||
String clname = classLoader == null ? "null" : classLoader.getClass().getName();
|
||||
if (clname.indexOf('.') != -1) {
|
||||
clname = clname.substring(clname.lastIndexOf('.') + 1);
|
||||
}
|
||||
if (typeRegistry == null) {
|
||||
// it is less interesting
|
||||
log.finer("classname=" + slashedClassName + " classloader=" + classLoader + " typeregistry=" + typeRegistry);
|
||||
} else {
|
||||
boolean ignore = false;
|
||||
for (String uninterestingPrefix : uninterestingPrefixes) {
|
||||
if (slashedClassName.startsWith(uninterestingPrefix)) {
|
||||
ignore = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!ignore) {
|
||||
log.info("classname=" + slashedClassName + " classloader=" + clname + " typeregistry=" + typeRegistry);
|
||||
}
|
||||
// more detailed log entry
|
||||
log.finer("classname=" + slashedClassName + " classloader=" + classLoader + " typeregistry=" + typeRegistry);
|
||||
}
|
||||
}
|
||||
|
||||
public static List<Plugin> getGlobalPlugins() {
|
||||
if (plugins == null) {
|
||||
plugins = new ArrayList<Plugin>();
|
||||
// Ordering is important here (for some of the plugins) - try to do the lowest level things first in case the higher level
|
||||
// operations cause something to happen that will drive the lower level function. For example, the JVM plugin clears the
|
||||
// Introspector class which is used by the Spring CachedIntrospectionResults class, which is used by the Grails ClassPropertyFetcher (
|
||||
// through its calls to BeanUtils). If you don't clear the lower level things first then the higher level reinit operations will
|
||||
// still see the old (incorrect) results.
|
||||
plugins.add(new JVMPlugin());
|
||||
plugins.add(new SpringPlugin());
|
||||
plugins.add(new GroovyPlugin());
|
||||
plugins.add(new CglibPlugin());
|
||||
// Not used right now, grails mechanisms are clearing the state that this plugin is trying to
|
||||
// plugins.add(new GrailsPlugin());
|
||||
List<String> extraGlobalPlugins = GlobalConfiguration.pluginClassnameList;
|
||||
if (extraGlobalPlugins != null) {
|
||||
for (String globalPlugin : extraGlobalPlugins) {
|
||||
try {
|
||||
Class<?> pluginClass = Class.forName(globalPlugin, false, SpringLoadedPreProcessor.class.getClassLoader());
|
||||
plugins.add((Plugin) pluginClass.newInstance());
|
||||
} catch (ClassNotFoundException e) {
|
||||
System.err.println("Unexpected problem loading global plugin:" + globalPlugin);
|
||||
e.printStackTrace(System.err);
|
||||
} catch (InstantiationException e) {
|
||||
System.err.println("Unexpected problem loading global plugin:" + globalPlugin);
|
||||
e.printStackTrace(System.err);
|
||||
} catch (IllegalAccessException e) {
|
||||
System.err.println("Unexpected problem loading global plugin:" + globalPlugin);
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return plugins;
|
||||
}
|
||||
|
||||
private static List<IsReloadableTypePlugin> isReloadableTypePlugins = null;
|
||||
|
||||
public static List<IsReloadableTypePlugin> getIsReloadableTypePlugins() {
|
||||
if (isReloadableTypePlugins == null) {
|
||||
synchronized (SpringLoadedPreProcessor.class) {
|
||||
if (isReloadableTypePlugins == null) {
|
||||
isReloadableTypePlugins = new ArrayList<IsReloadableTypePlugin>();
|
||||
for (Plugin p : getGlobalPlugins()) {
|
||||
if (p instanceof IsReloadableTypePlugin) {
|
||||
isReloadableTypePlugins.add((IsReloadableTypePlugin) p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return isReloadableTypePlugins;
|
||||
}
|
||||
|
||||
public static void registerGlobalPlugin(Plugin instance) {
|
||||
getGlobalPlugins(); // trigger initialization
|
||||
plugins.add(instance);
|
||||
isReloadableTypePlugins = null; // reset this cached value
|
||||
}
|
||||
|
||||
public static void unregisterGlobalPlugin(Plugin instance) {
|
||||
getGlobalPlugins(); // trigger initialization
|
||||
plugins.remove(instance);
|
||||
isReloadableTypePlugins = null; // reset this cached value
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded.agent;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.security.ProtectionDomain;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.springsource.loaded.GlobalConfiguration;
|
||||
import org.springsource.loaded.LoadtimeInstrumentationPlugin;
|
||||
import org.springsource.loaded.ReloadEventProcessorPlugin;
|
||||
|
||||
|
||||
/**
|
||||
* First stab at the Spring plugin for Spring-Loaded. Notes...<br>
|
||||
* <ul>
|
||||
* <li>On reload, removes the Class entry in
|
||||
* org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.methodResolverCache. This enables us to add/changes
|
||||
* request mappings in controllers.
|
||||
* <li>That was for Roo, if we create a simple spring template project and run it, this doesn't work. It seems we need to redrive
|
||||
* detectHandlers() on the DefaultAnnotationHandlerMapping type which will rediscover the URL mappings and add them into the handler
|
||||
* list. We don't clear old ones out (yet) but the old mappings appear not to work anyway.
|
||||
* </ul>
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class SpringPlugin implements LoadtimeInstrumentationPlugin, ReloadEventProcessorPlugin {
|
||||
|
||||
private static Logger log = Logger.getLogger(SpringPlugin.class.getName());
|
||||
|
||||
// TODO [gc] what about GC here - how do we know when they are finished with?
|
||||
public static List<Object> instancesOf_AnnotationMethodHandlerAdapter = new ArrayList<Object>();
|
||||
public static List<Object> instancesOf_DefaultAnnotationHandlerMapping = new ArrayList<Object>();
|
||||
public static List<Object> instancesOf_RequestMappingHandlerMapping = new ArrayList<Object>();
|
||||
|
||||
public static boolean support305 = true;
|
||||
|
||||
private Field classCacheField;
|
||||
|
||||
private boolean cachedIntrospectionResultsClassLoaded = false;
|
||||
|
||||
private Class<?> cachedIntrospectionResultsClass = null;
|
||||
|
||||
public boolean accept(String slashedTypeName, ClassLoader classLoader, ProtectionDomain protectionDomain, byte[] bytes) {
|
||||
// TODO take classloader into account?
|
||||
if (slashedTypeName == null) {
|
||||
return false;
|
||||
}
|
||||
// Just interested in whether this type got loaded
|
||||
if (slashedTypeName.equals("org/springframework/beans/CachedIntrospectionResults")) {
|
||||
cachedIntrospectionResultsClassLoaded = true;
|
||||
}
|
||||
return slashedTypeName.equals("org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter") ||
|
||||
slashedTypeName.equals("org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping") || // 3.1
|
||||
(support305 && slashedTypeName
|
||||
.equals("org/springframework/web/servlet/mvc/annotation/DefaultAnnotationHandlerMapping"));
|
||||
}
|
||||
|
||||
public byte[] modify(String slashedClassName, ClassLoader classLoader, byte[] bytes) {
|
||||
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) {
|
||||
log.info("loadtime modifying " + slashedClassName);
|
||||
}
|
||||
if (slashedClassName.equals("org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter")) {
|
||||
return bytesWithInstanceCreationCaptured(bytes, "org/springsource/loaded/agent/SpringPlugin",
|
||||
"recordAnnotationMethodHandlerAdapterInstance");
|
||||
} else if (slashedClassName.equals("org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping")) {
|
||||
// springmvc spring 3.1 - doesnt work on 3.1 post M2 snapshots
|
||||
return bytesWithInstanceCreationCaptured(bytes, "org/springsource/loaded/agent/SpringPlugin",
|
||||
"recordRequestMappingHandlerMappingInstance");
|
||||
} else { // "org/springframework/web/servlet/mvc/annotation/DefaultAnnotationHandlerMapping"
|
||||
// springmvc spring 3.0
|
||||
return bytesWithInstanceCreationCaptured(bytes, "org/springsource/loaded/agent/SpringPlugin",
|
||||
"recordDefaultAnnotationHandlerMappingInstance");
|
||||
}
|
||||
}
|
||||
|
||||
// called by the modified code
|
||||
public static void recordAnnotationMethodHandlerAdapterInstance(Object obj) {
|
||||
instancesOf_AnnotationMethodHandlerAdapter.add(obj);
|
||||
}
|
||||
|
||||
public static void recordRequestMappingHandlerMappingInstance(Object obj) {
|
||||
instancesOf_RequestMappingHandlerMapping.add(obj);
|
||||
}
|
||||
|
||||
private static boolean debug = false;
|
||||
|
||||
// called by the modified code
|
||||
public static void recordDefaultAnnotationHandlerMappingInstance(Object obj) {
|
||||
if (debug) {
|
||||
System.out.println("Recording new instance of DefaultAnnotationHandlerMappingInstance");
|
||||
}
|
||||
instancesOf_DefaultAnnotationHandlerMapping.add(obj);
|
||||
}
|
||||
|
||||
public void reloadEvent(String typename, Class<?> clazz, String versionsuffix) {
|
||||
removeClazzFromMethodResolverCache(clazz);
|
||||
clearCachedIntrospectionResults(clazz);
|
||||
reinvokeDetectHandlers(); // Spring 3.0
|
||||
reinvokeInitHandlerMethods(); // Spring 3.1
|
||||
}
|
||||
|
||||
private void removeClazzFromMethodResolverCache(Class<?> clazz) {
|
||||
for (Object o : instancesOf_AnnotationMethodHandlerAdapter) {
|
||||
try {
|
||||
Field f = o.getClass().getDeclaredField("methodResolverCache");
|
||||
f.setAccessible(true);
|
||||
Map<?, ?> map = (Map<?, ?>) f.get(o);
|
||||
Method removeMethod = Map.class.getDeclaredMethod("remove", Object.class);
|
||||
Object ret = removeMethod.invoke(map, clazz);
|
||||
if (GlobalConfiguration.debugplugins) {
|
||||
System.err.println("SpringPlugin: clearing methodResolverCache for " + clazz.getName());
|
||||
}
|
||||
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) {
|
||||
log.info("cleared a cache entry? " + (ret != null));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.log(Level.SEVERE, "Unexpected problem accessing methodResolverCache on " + o, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void clearCachedIntrospectionResults(Class<?> clazz) {
|
||||
if (cachedIntrospectionResultsClassLoaded) {
|
||||
try {
|
||||
// TODO not a fan of classloading like this
|
||||
if (cachedIntrospectionResultsClass == null) {
|
||||
// TODO what about two apps using reloading and diff versions of spring?
|
||||
cachedIntrospectionResultsClass = clazz.getClassLoader().loadClass(
|
||||
"org.springframework.beans.CachedIntrospectionResults");
|
||||
}
|
||||
if (classCacheField == null) {
|
||||
classCacheField = cachedIntrospectionResultsClass.getDeclaredField("classCache");
|
||||
}
|
||||
classCacheField.setAccessible(true);
|
||||
Map m = (Map) classCacheField.get(null);
|
||||
Object o = m.remove(clazz);
|
||||
if (GlobalConfiguration.debugplugins) {
|
||||
System.err
|
||||
.println("SpringPlugin: clearing CachedIntrospectionResults for " + clazz.getName() + " removed=" + o);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void reinvokeDetectHandlers() {
|
||||
// want to call detectHandlers on the DefaultAnnotationHandlerMapping type
|
||||
// protected void detectHandlers() throws BeansException { is defined on AbstractDetectingUrlHandlerMapping
|
||||
for (Object o : instancesOf_DefaultAnnotationHandlerMapping) {
|
||||
if (debug) {
|
||||
System.out.println("Invoking detectHandlers on instance of DefaultAnnotationHandlerMappingInstance");
|
||||
}
|
||||
try {
|
||||
Class<?> clazz_AbstractDetectingUrlHandlerMapping = o.getClass().getSuperclass();
|
||||
Method method_detectHandlers = clazz_AbstractDetectingUrlHandlerMapping.getDeclaredMethod("detectHandlers");
|
||||
method_detectHandlers.setAccessible(true);
|
||||
method_detectHandlers.invoke(o);
|
||||
} catch (Exception e) {
|
||||
// if debugging then print it
|
||||
if (GlobalConfiguration.debugplugins) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private void reinvokeInitHandlerMethods() {
|
||||
// org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping (super AbstractHandlerMethodMapping) - call protected void initHandlerMethods() on it.
|
||||
|
||||
for (Object o : instancesOf_RequestMappingHandlerMapping) {
|
||||
if (debug) {
|
||||
System.out.println("Invoking initHandlerMethods on instance of RequestMappingHandlerMapping");
|
||||
}
|
||||
try {
|
||||
Class<?> clazz_AbstractHandlerMethodMapping = o.getClass().getSuperclass().getSuperclass();
|
||||
|
||||
// private final Map<T, HandlerMethod> handlerMethods = new LinkedHashMap<T, HandlerMethod>();
|
||||
Field field_handlerMethods = clazz_AbstractHandlerMethodMapping.getDeclaredField("handlerMethods");
|
||||
field_handlerMethods.setAccessible(true);
|
||||
Map m = (Map) field_handlerMethods.get(o);
|
||||
m.clear();
|
||||
|
||||
Field field_urlMap = clazz_AbstractHandlerMethodMapping.getDeclaredField("urlMap");
|
||||
field_urlMap.setAccessible(true);
|
||||
m = (Map) field_urlMap.get(o);
|
||||
m.clear();
|
||||
|
||||
Method method_initHandlerMethods = clazz_AbstractHandlerMethodMapping.getDeclaredMethod("initHandlerMethods");
|
||||
method_initHandlerMethods.setAccessible(true);
|
||||
method_initHandlerMethods.invoke(o);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean shouldRerunStaticInitializer(String typename, Class<?> clazz, String encodedTimestamp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify the supplied bytes such that constructors are intercepted and will invoke the specified class/method so that the
|
||||
* instances can be tracked.
|
||||
*
|
||||
* @return modified bytes for the class
|
||||
*/
|
||||
private byte[] bytesWithInstanceCreationCaptured(byte[] bytes, String classToCall, String methodToCall) {
|
||||
ClassReader cr = new ClassReader(bytes);
|
||||
ClassVisitingConstructorAppender ca = new ClassVisitingConstructorAppender(classToCall, methodToCall);
|
||||
cr.accept(ca, 0);
|
||||
byte[] newbytes = ca.getBytes();
|
||||
return newbytes;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded.infra;
|
||||
|
||||
import java.util.logging.LogRecord;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class SLFormatter extends java.util.logging.Formatter {
|
||||
|
||||
public String format(LogRecord record) {
|
||||
StringBuilder s = new StringBuilder();
|
||||
String sourceClassName = record.getSourceClassName();
|
||||
int idx;
|
||||
if ((idx = sourceClassName.lastIndexOf('.')) == -1) {
|
||||
s.append(record.getSourceClassName());
|
||||
} else {
|
||||
s.append(record.getSourceClassName().substring(idx + 1));
|
||||
}
|
||||
s.append(".");
|
||||
s.append(record.getSourceMethodName());
|
||||
s.append(":");
|
||||
s.append(super.formatMessage(record));
|
||||
s.append("\n");
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded.infra;
|
||||
|
||||
/**
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public @interface UsedByGeneratedCode {
|
||||
|
||||
}
|
||||
231
springloaded/src/main/java/org/springsource/loaded/jvm/JVM.java
Normal file
231
springloaded/src/main/java/org/springsource/loaded/jvm/JVM.java
Normal file
@@ -0,0 +1,231 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded.jvm;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.springsource.loaded.ri.ReflectiveInterceptor;
|
||||
|
||||
|
||||
/**
|
||||
* Utility class containing operations that are "JVM" specific and may need porting when changing JVMs.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class JVM {
|
||||
|
||||
public static Logger log = Logger.getLogger(JVM.class.getName());
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static Constructor<Method> jlrMethodCtor = (Constructor<Method>) Method.class.getDeclaredConstructors()[0];
|
||||
|
||||
private static Method jlrMethodCopy;
|
||||
static {
|
||||
try {
|
||||
jlrMethodCopy = Method.class.getDeclaredMethod("copy");
|
||||
jlrMethodCopy.setAccessible(true);
|
||||
} catch (Exception e) {
|
||||
log.log(Level.SEVERE, "Problems getting 'Method.copy()' method. Incompatible JVM?", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static Method jlrFieldCopy;
|
||||
static {
|
||||
try {
|
||||
jlrFieldCopy = Field.class.getDeclaredMethod("copy");
|
||||
jlrFieldCopy.setAccessible(true);
|
||||
} catch (Exception e) {
|
||||
log.log(Level.SEVERE, "Problems getting 'Field.copy()' method. Incompatible JVM?", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static Method jlrConstructorCopy;
|
||||
static {
|
||||
try {
|
||||
jlrConstructorCopy = Constructor.class.getDeclaredMethod("copy");
|
||||
jlrConstructorCopy.setAccessible(true);
|
||||
} catch (Exception e) {
|
||||
log.log(Level.SEVERE, "Problems getting 'Constructor.copy()' method. Incompatible JVM?", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static Field jlrMethodModifiers;
|
||||
static {
|
||||
try {
|
||||
jlrMethodModifiers = Method.class.getDeclaredField("modifiers");
|
||||
jlrMethodModifiers.setAccessible(true);
|
||||
} catch (Exception e) {
|
||||
log.log(Level.SEVERE, "Problems getting Field 'Method.modifiers' method. Incompatible JVM?", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static Field jlrConstructorModifiers;
|
||||
static {
|
||||
try {
|
||||
jlrConstructorModifiers = Constructor.class.getDeclaredField("modifiers");
|
||||
jlrConstructorModifiers.setAccessible(true);
|
||||
} catch (Exception e) {
|
||||
log.log(Level.SEVERE, "Problems getting Field 'Constructor.modifiers' method. Incompatible JVM?", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static Field jlrFieldModifiers;
|
||||
static {
|
||||
try {
|
||||
jlrFieldModifiers = Field.class.getDeclaredField("modifiers");
|
||||
jlrFieldModifiers.setAccessible(true);
|
||||
} catch (Exception e) {
|
||||
log.log(Level.SEVERE, "Problems getting Field 'Field.modifiers' method. Incompatible JVM?", e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("restriction")
|
||||
public static void ensureMemberAccess(Class<?> callerClass, Class<?> declaringClass, Object target, int mods)
|
||||
throws IllegalAccessException {
|
||||
sun.reflect.Reflection.ensureMemberAccess(callerClass, declaringClass, target, mods);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Method object from scratch. This Method object is 'fake' and will not be "invokable". ReflectionInterceptor will
|
||||
* be responsible to make sure user code calling 'invoke' on this object will be intercepted and handled appropriately.
|
||||
*/
|
||||
public static Method newMethod(Class<?> clazz, String name, Class<?>[] params, Class<?> returnType, Class<?>[] exceptions,
|
||||
int modifiers, String signature) {
|
||||
// This is what the constructor looks like:
|
||||
// Method(Class declaringClass, String name, Class[] parameterTypes, Class returnType,
|
||||
// Class[] checkedExceptions, int modifiers, int slot, String signature,
|
||||
// byte[] annotations, byte[] parameterAnnotations, byte[] annotationDefault)
|
||||
Method returnMethod;
|
||||
try {
|
||||
jlrMethodCtor.setAccessible(true);
|
||||
returnMethod = jlrMethodCtor.newInstance(clazz, name, params, returnType, exceptions, modifiers, 0, signature, null,
|
||||
null, null);
|
||||
} catch (Exception e) {
|
||||
//This shouldn't happen...
|
||||
ReflectiveInterceptor.log.log(Level.SEVERE, "Internal Error", e);
|
||||
throw new Error(e);
|
||||
}
|
||||
return returnMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a copy of a method object that is equivalent to the original.
|
||||
*/
|
||||
public static Method copyMethod(Method method) {
|
||||
try {
|
||||
return (Method) jlrMethodCopy.invoke(method);
|
||||
} catch (Exception e) {
|
||||
log.log(Level.SEVERE, "Problems copying method. Incompatible JVM?", e);
|
||||
return method; // return original as the best we can do
|
||||
}
|
||||
}
|
||||
|
||||
public static Field copyField(Field field) {
|
||||
try {
|
||||
return (Field) jlrFieldCopy.invoke(field);
|
||||
} catch (Exception e) {
|
||||
log.log(Level.SEVERE, "Problems copying field. Incompatible JVM?", e);
|
||||
return field; // return original as the best we can do
|
||||
}
|
||||
}
|
||||
|
||||
public static Constructor<?> copyConstructor(Constructor<?> c) {
|
||||
try {
|
||||
return (Constructor<?>) jlrConstructorCopy.invoke(c);
|
||||
} catch (Exception e) {
|
||||
log.log(Level.SEVERE, "Problems copying constructor. Incompatible JVM?", e);
|
||||
return c; // return original as the best we can do
|
||||
}
|
||||
}
|
||||
|
||||
public static void setMethodModifiers(Method method, int modifiers) {
|
||||
try {
|
||||
jlrMethodModifiers.setInt(method, modifiers);
|
||||
} catch (Exception e) {
|
||||
log.log(Level.SEVERE, "Couldn't set correct modifiers on reflected method: " + method, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setConstructorModifiers(Constructor<?> c, int modifiers) {
|
||||
try {
|
||||
jlrConstructorModifiers.setInt(c, modifiers);
|
||||
} catch (Exception e) {
|
||||
log.log(Level.SEVERE, "Couldn't set correct modifiers on reflected constructor: " + c, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setFieldModifiers(Field field, int mods) {
|
||||
try {
|
||||
jlrFieldModifiers.setInt(field, mods);
|
||||
} catch (Exception e) {
|
||||
log.log(Level.SEVERE, "Couldn't set correct modifiers on reflected field: " + field, e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static final Constructor<Field> jlFieldCtor = (Constructor<Field>) Field.class.getDeclaredConstructors()[0];
|
||||
|
||||
public static Field newField(Class<?> declaring, Class<?> type, int mods, String name, String sig) {
|
||||
jlFieldCtor.setAccessible(true);
|
||||
// This is what the constructor looks like:
|
||||
// Field(Class declaringClass,String name,Class type,int modifiers, int slot, String signature,
|
||||
// byte[] annotations)
|
||||
try {
|
||||
return jlFieldCtor.newInstance(declaring, name, type, mods, 0, sig, null);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Problem creating reloadable Field: " + declaring.getName() + "." + name, e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static final Constructor<Constructor<?>> jlConstructorCtor = (Constructor<Constructor<?>>) Constructor.class
|
||||
.getDeclaredConstructors()[0];
|
||||
|
||||
public static Constructor<?> newConstructor(Class<?> clazz, Class<?>[] params, Class<?>[] exceptions, int modifiers,
|
||||
String signature) {
|
||||
jlConstructorCtor.setAccessible(true);
|
||||
// This is what the constructor looks like:
|
||||
// Constructor(Class<T> declaringClass,
|
||||
// Class[] parameterTypes,
|
||||
// Class[] checkedExceptions,
|
||||
// int modifiers,
|
||||
// int slot,
|
||||
// String signature,
|
||||
// byte[] annotations,
|
||||
// byte[] parameterAnnotations)
|
||||
try {
|
||||
return jlConstructorCtor.newInstance(clazz, params, exceptions, modifiers, 0, signature, null, null);
|
||||
} catch (Exception e) {
|
||||
StringBuffer msg = new StringBuffer("Problem creating reloadable Constructor: ");
|
||||
msg.append(clazz.getName());
|
||||
msg.append("(");
|
||||
for (int i = 0; i < params.length; i++) {
|
||||
if (i > 0) {
|
||||
msg.append(", ");
|
||||
}
|
||||
msg.append(params[i].getName());
|
||||
}
|
||||
msg.append(")");
|
||||
throw new IllegalStateException(msg.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded.pluginhelpers;
|
||||
|
||||
import org.objectweb.asm.AnnotationVisitor;
|
||||
import org.objectweb.asm.Attribute;
|
||||
import org.objectweb.asm.ClassAdapter;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.Label;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
import org.springsource.loaded.Constants;
|
||||
import org.springsource.loaded.test.infra.ClassPrinter;
|
||||
|
||||
|
||||
/**
|
||||
* Modifies a class and empties the specified constructors (not a common thing to do!)
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.8.3
|
||||
*/
|
||||
public class EmptyCtor extends ClassAdapter implements Constants {
|
||||
|
||||
private String[] descriptors;
|
||||
|
||||
/**
|
||||
* Empty the constructors with the specified descriptors.
|
||||
*
|
||||
* @param bytesIn input class as bytes
|
||||
* @param descriptors descriptors of interest (e.g. "()V")
|
||||
* @return modified class as byte array
|
||||
*/
|
||||
public static byte[] invoke(byte[] bytesIn, String... descriptors) {
|
||||
ClassReader cr = new ClassReader(bytesIn);
|
||||
EmptyCtor ca = new EmptyCtor(descriptors);
|
||||
cr.accept(ca, 0);
|
||||
byte[] newbytes = ca.getBytes();
|
||||
return newbytes;
|
||||
}
|
||||
|
||||
private EmptyCtor(String... descriptors) {
|
||||
super(new ClassWriter(0)); // TODO review 0 here
|
||||
this.descriptors = descriptors;
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
byte[] bs = ((ClassWriter) cv).toByteArray();
|
||||
ClassPrinter.print(bs);
|
||||
return bs;
|
||||
}
|
||||
|
||||
private boolean isInterestingDescriptor(String desc) {
|
||||
for (int i = 0, max = descriptors.length; i < max; i++) {
|
||||
if (descriptors[i].equals(desc)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
|
||||
if (name.equals("<init>") && isInterestingDescriptor(desc)) {
|
||||
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
|
||||
return new Emptier(mv);
|
||||
} else {
|
||||
return super.visitMethod(access, name, desc, signature, exceptions);
|
||||
}
|
||||
}
|
||||
|
||||
static class Emptier implements MethodVisitor, Constants {
|
||||
|
||||
MethodVisitor mv;
|
||||
|
||||
public Emptier(MethodVisitor mv) {
|
||||
this.mv = mv;
|
||||
}
|
||||
|
||||
public AnnotationVisitor visitAnnotationDefault() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void visitAttribute(Attribute attr) {
|
||||
}
|
||||
|
||||
public void visitCode() {
|
||||
}
|
||||
|
||||
public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) {
|
||||
}
|
||||
|
||||
public void visitInsn(int opcode) {
|
||||
}
|
||||
|
||||
public void visitIntInsn(int opcode, int operand) {
|
||||
}
|
||||
|
||||
public void visitVarInsn(int opcode, int var) {
|
||||
}
|
||||
|
||||
public void visitTypeInsn(int opcode, String type) {
|
||||
}
|
||||
|
||||
public void visitFieldInsn(int opcode, String owner, String name, String desc) {
|
||||
}
|
||||
|
||||
public void visitMethodInsn(int opcode, String owner, String name, String desc) {
|
||||
}
|
||||
|
||||
public void visitJumpInsn(int opcode, Label label) {
|
||||
}
|
||||
|
||||
public void visitLabel(Label label) {
|
||||
}
|
||||
|
||||
public void visitLdcInsn(Object cst) {
|
||||
}
|
||||
|
||||
public void visitIincInsn(int var, int increment) {
|
||||
}
|
||||
|
||||
public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) {
|
||||
}
|
||||
|
||||
public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
|
||||
}
|
||||
|
||||
public void visitMultiANewArrayInsn(String desc, int dims) {
|
||||
}
|
||||
|
||||
public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
|
||||
}
|
||||
|
||||
public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {
|
||||
}
|
||||
|
||||
public void visitLineNumber(int line, Label start) {
|
||||
}
|
||||
|
||||
public void visitMaxs(int maxStack, int maxLocals) {
|
||||
mv.visitMaxs(1, 1); // TODO adjust visit max numbers based on descriptor length
|
||||
}
|
||||
|
||||
public void visitEnd() {
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
|
||||
mv.visitInsn(RETURN);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded.ri;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Provides an implementation for dynamic method lookup in a given Method provider.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class DynamicLookup {
|
||||
|
||||
private String name;
|
||||
private String methodDescriptor;
|
||||
|
||||
/**
|
||||
* Create an object capable of performing a dynamic method lookup in some MethodProvider
|
||||
*/
|
||||
public DynamicLookup(String name, String methodDescriptor) {
|
||||
this.name = name;
|
||||
this.methodDescriptor = methodDescriptor;
|
||||
}
|
||||
|
||||
public Invoker lookup(MethodProvider methodProvider) {
|
||||
List<Invoker> methods = methodProvider.getDeclaredMethods();
|
||||
for (Invoker invoker : methods) {
|
||||
if (matches(invoker)) {
|
||||
return invoker;
|
||||
}
|
||||
}
|
||||
// Try the superclass context
|
||||
MethodProvider parent = methodProvider.getSuper();
|
||||
if (parent != null) {
|
||||
return lookup(parent);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected boolean matches(Invoker invoker) {
|
||||
return !Modifier.isPrivate(invoker.getModifiers()) && name.equals(invoker.getName())
|
||||
&& methodDescriptor.equals(invoker.getMethodDescriptor());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded.ri;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
import org.objectweb.asm.Type;
|
||||
|
||||
/**
|
||||
* Utility class to create correctly formatted Exceptions and Errors for different kinds of error conditions.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class Exceptions {
|
||||
|
||||
static IllegalAccessException illegalSetFinalFieldException(Field field, Class<?> valueType, Object value) {
|
||||
// Example of error when setting a primitive type final field:
|
||||
// Can not set final short field reflection.nonrelfields.NonReloadableClassWithFields.nrlShort to (short)2
|
||||
|
||||
String fieldType = field.getType().getName();
|
||||
String fieldQName = field.getDeclaringClass().getName() + "." + field.getName();
|
||||
String valueString;
|
||||
if (value == null) {
|
||||
valueString = "null value";
|
||||
} else if (valueType.isPrimitive()) {
|
||||
valueString = "(" + valueType.getName() + ")" + value;
|
||||
} else {
|
||||
valueString = value == null ? "null value" : value.getClass().getName();
|
||||
}
|
||||
return new IllegalAccessException("Can not set final " + fieldType + " field " + fieldQName + " to " + valueString);
|
||||
}
|
||||
|
||||
static IllegalArgumentException illegalSetFieldTypeException(Field field, Class<?> valueType, Object value) {
|
||||
int mods = field.getModifiers() & (Modifier.FINAL | Modifier.STATIC);
|
||||
String modStr = Modifier.toString(mods);
|
||||
if (!modStr.equals("")) {
|
||||
modStr = modStr + " ";
|
||||
}
|
||||
|
||||
String fieldType = field.getType().getName();
|
||||
String fieldQName = field.getDeclaringClass().getName() + "." + field.getName();
|
||||
String valueStr;
|
||||
if (valueType == null) {
|
||||
valueStr = "null value";
|
||||
} else if (valueType.isPrimitive()) {
|
||||
valueStr = "(" + valueType.getName() + ")" + value;
|
||||
} else {
|
||||
valueStr = valueType.getName();
|
||||
}
|
||||
return new IllegalArgumentException("Can not set " + modStr + fieldType + " field " + fieldQName + " to " + valueStr);
|
||||
}
|
||||
|
||||
public static NoSuchFieldError noSuchFieldError(Field field) {
|
||||
return new NoSuchFieldError(field.getName());
|
||||
}
|
||||
|
||||
public static NoSuchMethodError noSuchMethodError(Method method) {
|
||||
return Exceptions.noSuchMethodError(method.getDeclaringClass().getName(), method.getName(),
|
||||
Type.getMethodDescriptor(method));
|
||||
}
|
||||
|
||||
public static NoSuchMethodError noSuchMethodError(String dottedClassName, String methodName, String methodDescriptor) {
|
||||
return new NoSuchMethodError(dottedClassName + "." + methodName + methodDescriptor);
|
||||
}
|
||||
|
||||
static NoSuchMethodException noSuchMethodException(Class<?> clazz, String name, Class<?>... params) {
|
||||
return new NoSuchMethodException(clazz.getName() + "." + name + ReflectiveInterceptor.toParamString(params));
|
||||
}
|
||||
|
||||
static NoSuchFieldException noSuchFieldException(String name) {
|
||||
return new NoSuchFieldException(name);
|
||||
}
|
||||
|
||||
public static IllegalArgumentException illegalGetFieldType(Field field, Class<?> returnType) {
|
||||
String fieldQName = field.getDeclaringClass().getName() + "." + field.getName();
|
||||
String returnTypeName = returnType.getName();
|
||||
String fieldType = field.getType().getName();
|
||||
return new IllegalArgumentException("Attempt to get " + fieldType + " field \"" + fieldQName
|
||||
+ "\" with illegal data type conversion to " + returnTypeName);
|
||||
}
|
||||
|
||||
public static NoSuchMethodException noSuchConstructorException(Class<?> clazz, Class<?>[] params) {
|
||||
return noSuchMethodException(clazz, "<init>", params);
|
||||
}
|
||||
|
||||
public static NoSuchMethodError noSuchConstructorError(Constructor<?> c) {
|
||||
//Example error message from Sun JVM:
|
||||
// Exception in thread "main" java.lang.NoSuchMethodError: blah.Target.<init>(CC)V
|
||||
// at Main.main(Main.java:10)
|
||||
return new NoSuchMethodError(c.getDeclaringClass().getName() + ".<init>" + Type.getConstructorDescriptor(c));
|
||||
}
|
||||
|
||||
public static InstantiationException instantiation(Class<?> clazz) {
|
||||
return new InstantiationException(clazz.getName());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded.ri;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.objectweb.asm.Type;
|
||||
import org.springsource.loaded.CurrentLiveVersion;
|
||||
import org.springsource.loaded.FieldMember;
|
||||
import org.springsource.loaded.GlobalConfiguration;
|
||||
import org.springsource.loaded.ReloadableType;
|
||||
import org.springsource.loaded.TypeRegistry;
|
||||
import org.springsource.loaded.Utils;
|
||||
import org.springsource.loaded.jvm.JVM;
|
||||
|
||||
|
||||
/**
|
||||
* This class contains code that is used as support infrastructure to implement Field lookup algorithms.
|
||||
*
|
||||
* Mainly, it provides an abstraction to allows Java classes and reloadable types to be treated as instances of a common abstraction
|
||||
* "FieldProvider" and then implement algorithms to find fields in those providers independent of how the fields are being provided.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class FieldLookup {
|
||||
|
||||
private static class JavaFieldRef extends FieldRef {
|
||||
|
||||
private Field f;
|
||||
|
||||
public JavaFieldRef(Field f) {
|
||||
this.f = f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Field getField() {
|
||||
return f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return f.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPublic() {
|
||||
return Modifier.isPublic(f.getModifiers());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class JavaClassFieldProvider extends FieldProvider {
|
||||
|
||||
private Class<?> clazz;
|
||||
|
||||
public JavaClassFieldProvider(Class<?> clazz) {
|
||||
this.clazz = clazz;
|
||||
}
|
||||
|
||||
@Override
|
||||
List<FieldRef> getFields() {
|
||||
Field[] fields = clazz.getDeclaredFields();
|
||||
List<FieldRef> refs = new ArrayList<FieldLookup.FieldRef>();
|
||||
for (Field f : fields) {
|
||||
refs.add(new JavaFieldRef(f));
|
||||
}
|
||||
return refs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInterface() {
|
||||
return clazz.isInterface();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FieldProvider[] getInterfaces() {
|
||||
Class<?>[] itfs = clazz.getInterfaces();
|
||||
FieldProvider[] provs = new FieldProvider[itfs.length];
|
||||
for (int i = 0; i < itfs.length; i++) {
|
||||
provs[i] = FieldProvider.create(itfs[i]);
|
||||
}
|
||||
return provs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FieldProvider getSuper() {
|
||||
Class<?> supr = clazz.getSuperclass();
|
||||
if (supr != null) {
|
||||
FieldProvider.create(supr);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static abstract class FieldRef {
|
||||
|
||||
public abstract Field getField();
|
||||
|
||||
public abstract String getName();
|
||||
|
||||
public abstract boolean isPublic();
|
||||
|
||||
}
|
||||
|
||||
public static class ReloadedTypeFieldRef extends FieldRef {
|
||||
|
||||
private ReloadableType rtype;
|
||||
private FieldMember f;
|
||||
|
||||
public ReloadedTypeFieldRef(ReloadableType rtype, FieldMember f) {
|
||||
if (GlobalConfiguration.assertsOn) {
|
||||
Utils.assertTrue(rtype.hasBeenReloaded(), "Not yet reloaded: " + rtype.getName());
|
||||
}
|
||||
this.rtype = rtype;
|
||||
this.f = f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Field getField() {
|
||||
Class<?> declaring = Utils.toClass(rtype);
|
||||
Class<?> type;
|
||||
try {
|
||||
type = Utils.toClass(Type.getType(f.getDescriptor()), rtype.typeRegistry.getClassLoader());
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
return JVM.newField(declaring, type, f.getModifiers(), f.getName(), f.getGenericSignature());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return f.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPublic() {
|
||||
return f.isPublic();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected static abstract class FieldProvider {
|
||||
|
||||
abstract List<FieldRef> getFields();
|
||||
|
||||
public abstract boolean isInterface();
|
||||
|
||||
public abstract FieldProvider[] getInterfaces();
|
||||
|
||||
public abstract FieldProvider getSuper();
|
||||
|
||||
public static FieldProvider create(ReloadableType rtype) {
|
||||
return new ReloadableTypeFieldProvider(rtype);
|
||||
}
|
||||
|
||||
public static FieldProvider create(TypeRegistry typeRegistry, String slashyName) {
|
||||
if (typeRegistry.isReloadableTypeName(slashyName)) {
|
||||
return create(typeRegistry.getReloadableType(slashyName));
|
||||
} else {
|
||||
try {
|
||||
return create(Utils.toClass(Type.getObjectType(slashyName), typeRegistry.getClassLoader()));
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static FieldProvider create(Class<?> clazz) {
|
||||
return new JavaClassFieldProvider(clazz);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ReloadableTypeFieldProvider extends FieldProvider {
|
||||
private ReloadableType rtype;
|
||||
|
||||
public ReloadableTypeFieldProvider(ReloadableType rtype) {
|
||||
this.rtype = rtype;
|
||||
}
|
||||
|
||||
@Override
|
||||
List<FieldRef> getFields() {
|
||||
FieldMember[] fields = rtype.getLatestTypeDescriptor().getFields();
|
||||
List<FieldRef> refs = new ArrayList<FieldRef>(fields.length);
|
||||
for (FieldMember f : fields) {
|
||||
refs.add(fieldRef(rtype, f));
|
||||
}
|
||||
return refs;
|
||||
}
|
||||
|
||||
private FieldRef fieldRef(ReloadableType rtype, FieldMember f) {
|
||||
CurrentLiveVersion clv = rtype.getLiveVersion();
|
||||
if (clv == null) {
|
||||
//Not yet reloaded... use original field (with fixed mods)
|
||||
try {
|
||||
Field jf = rtype.getClazz().getDeclaredField(f.getName());
|
||||
ReflectiveInterceptor.fixModifier(rtype.getLatestTypeDescriptor(), jf);
|
||||
return new JavaFieldRef(jf);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
} else {
|
||||
//Already reloaded
|
||||
return new ReloadedTypeFieldRef(rtype, f);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInterface() {
|
||||
return rtype.getLatestTypeDescriptor().isInterface();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FieldProvider[] getInterfaces() {
|
||||
String[] superItfs = rtype.getLatestTypeDescriptor().getSuperinterfacesName();
|
||||
FieldProvider[] superProvs = new FieldProvider[superItfs.length];
|
||||
for (int i = 0; i < superItfs.length; i++) {
|
||||
superProvs[i] = FieldProvider.create(rtype.typeRegistry, superItfs[i]);
|
||||
}
|
||||
return superProvs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FieldProvider getSuper() {
|
||||
String supr = rtype.getLatestTypeDescriptor().getSupertypeName();
|
||||
if (supr != null) {
|
||||
return FieldProvider.create(rtype.typeRegistry, supr);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded.ri;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.List;
|
||||
|
||||
import org.springsource.loaded.ReloadableType;
|
||||
|
||||
|
||||
/**
|
||||
* Implementation of filed lookup algorithm for Class.getDeclaredField.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class GetDeclaredFieldLookup extends FieldLookup {
|
||||
|
||||
public static Field lookup(ReloadableType rtype, String name) {
|
||||
FieldRef ref = lookup(FieldProvider.create(rtype), name);
|
||||
if (ref == null) {
|
||||
return null;
|
||||
}
|
||||
return ref.getField();
|
||||
}
|
||||
|
||||
private static FieldRef lookup(FieldProvider provider, String name) {
|
||||
List<FieldRef> fields = provider.getFields();
|
||||
for (FieldRef f : fields) {
|
||||
if (f.getName().equals(name)) {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded.ri;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Provides an implementation for method lookup as suitable for 'Class.getDeclaredMethod'
|
||||
*
|
||||
* @author Kris De Volder
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class GetDeclaredMethodLookup {
|
||||
|
||||
private String name;
|
||||
private String paramsDescriptor;
|
||||
|
||||
/**
|
||||
* Create an object capable of performing the lookup in some MethodProvider
|
||||
*/
|
||||
public GetDeclaredMethodLookup(String name, String paramsDescriptor) {
|
||||
this.name = name;
|
||||
this.paramsDescriptor = paramsDescriptor;
|
||||
}
|
||||
|
||||
public Invoker lookup(MethodProvider methodProvider) {
|
||||
List<Invoker> methods = methodProvider.getDeclaredMethods();
|
||||
Invoker found = null;
|
||||
for (Invoker invoker : methods) {
|
||||
if (matches(invoker)) {
|
||||
if (found == null || isMoreSpecificReturnTypeThan(invoker, found)) {
|
||||
found = invoker;
|
||||
}
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if m2 has a more specific return type than m1
|
||||
*/
|
||||
private boolean isMoreSpecificReturnTypeThan(Invoker m1, Invoker m2) {
|
||||
//This uses 'Class.isAssigableFrom'. This is ok, assuming that inheritance hierarchy is not something that we are allowed
|
||||
// to change on reloads.
|
||||
Class<?> cls1 = m1.getReturnType();
|
||||
Class<?> cls2 = m2.getReturnType();
|
||||
return cls2.isAssignableFrom(cls1);
|
||||
}
|
||||
|
||||
protected boolean matches(Invoker invoker) {
|
||||
return name.equals(invoker.getName()) && paramsDescriptor.equals(invoker.getParamsDescriptor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "GetDeclaredMethod( " + name + "." + paramsDescriptor + " )";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded.ri;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.List;
|
||||
|
||||
import org.springsource.loaded.ReloadableType;
|
||||
|
||||
|
||||
/**
|
||||
* Implementation of FieldLookup algorithm for "Class.getField".
|
||||
*
|
||||
* @author Kris De Volder
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class GetFieldLookup extends FieldLookup {
|
||||
|
||||
public static Field lookup(ReloadableType rtype, String name) {
|
||||
FieldRef ref = lookup(FieldProvider.create(rtype), name);
|
||||
if (ref == null) {
|
||||
return null;
|
||||
}
|
||||
return ref.getField();
|
||||
}
|
||||
|
||||
private static FieldRef lookup(FieldProvider provider, String name) {
|
||||
List<FieldRef> fields = provider.getFields();
|
||||
for (FieldRef f : fields) {
|
||||
if (f.isPublic()) {
|
||||
if (f.getName().equals(name)) {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Didn't find in this type. Check interfaces.
|
||||
FieldProvider[] itfs = provider.getInterfaces();
|
||||
for (FieldProvider itf : itfs) {
|
||||
FieldRef f = lookup(itf, name);
|
||||
if (f != null) {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
// Still didn't find... Check superclass but only if we are not an interface
|
||||
if (!provider.isInterface()) {
|
||||
FieldProvider supr = provider.getSuper();
|
||||
if (supr != null) {
|
||||
FieldRef f = lookup(supr, name);
|
||||
if (f != null) {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
}
|
||||
//Not found
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded.ri;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
import org.springsource.loaded.Utils;
|
||||
|
||||
|
||||
/**
|
||||
* Implements a 'lookup' strategy that finds methods in the fashion required by java.lang.Class.getMethod
|
||||
*
|
||||
* @author Kris De Volder
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class GetMethodLookup {
|
||||
|
||||
private String name;
|
||||
private String paramsDescriptor;
|
||||
|
||||
/**
|
||||
* Create an object capable of performing the lookup
|
||||
*/
|
||||
public GetMethodLookup(String name, String paramsDescriptor) {
|
||||
this.name = name;
|
||||
this.paramsDescriptor = paramsDescriptor;
|
||||
}
|
||||
|
||||
public GetMethodLookup(String name, Class<?>[] params) {
|
||||
this(name, Utils.toParamDescriptor(params));
|
||||
}
|
||||
|
||||
public Invoker lookup(MethodProvider methodProvider) {
|
||||
Invoker method = methodProvider.getDeclaredMethod(name, paramsDescriptor);
|
||||
if (method != null && Modifier.isPublic(method.getModifiers())) {
|
||||
return method;
|
||||
}
|
||||
|
||||
// Try the superclass context (but not for interfaces, we aren't supposed to include Object's methods
|
||||
// in them!
|
||||
if (!methodProvider.isInterface()) {
|
||||
MethodProvider parent = methodProvider.getSuper();
|
||||
if (parent != null) {
|
||||
method = lookup(parent);
|
||||
if (method != null) {
|
||||
return method;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try the interfaces
|
||||
MethodProvider[] itfs = methodProvider.getInterfaces();
|
||||
for (MethodProvider itf : itfs) {
|
||||
Invoker itfMethod = lookup(itf);
|
||||
if (itfMethod != null) {
|
||||
return itfMethod;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded.ri;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Kris De Volder
|
||||
* @since 0.5.0
|
||||
*
|
||||
*/
|
||||
public class GetMethodsLookup {
|
||||
|
||||
public Collection<Invoker> lookup(MethodProvider methodProvider) {
|
||||
Map<String, Invoker> found = new HashMap<String, Invoker>();
|
||||
collectAll(methodProvider, found);
|
||||
return found.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect all public methods from methodProvider and its supertypes into the 'found' hasmap, indexed by "name+descriptor".
|
||||
*/
|
||||
private void collectAll(MethodProvider methodProvider, Map<String, Invoker> found) {
|
||||
//We do this in inverse order as in 'GetMethodLookup'. This is because GetMethodLookup
|
||||
//is lazy and wants to stop when a method is found, but here we instead collect
|
||||
//verything bottom up and 'overwrite' earlier results so the last one found is the
|
||||
//one kept.
|
||||
|
||||
//First interfaces in inverse order...
|
||||
MethodProvider[] itfs = methodProvider.getInterfaces();
|
||||
for (int i = itfs.length - 1; i >= 0; i--) { // inverse order
|
||||
collectAll(itfs[i], found);
|
||||
}
|
||||
|
||||
//Then the superclass(es), but only if we're not an interface (interfaces do not report
|
||||
// the methods of Object!
|
||||
MethodProvider supr = methodProvider.getSuper();
|
||||
if (supr != null && !methodProvider.isInterface()) {
|
||||
collectAll(supr, found);
|
||||
}
|
||||
|
||||
//Finally all our own public methods
|
||||
for (Invoker method : methodProvider.getDeclaredMethods()) {
|
||||
if (Modifier.isPublic(method.getModifiers())) {
|
||||
found.put(method.getName() + method.getMethodDescriptor(), method);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded.ri;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
/**
|
||||
* An invoker represents the result of a method lookup operation in the type hierarchy.
|
||||
* <p>
|
||||
* It encapsulates a reference to a resolved method implementation in a reloadable or non-reloadable type and provides an 'invoke'
|
||||
* method suitable for invoking that method implementation, and a 'createJavaMethod' to create a Java {@link Method} instance that
|
||||
* can be used to represent the method in the Java reflection API.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public abstract class Invoker {
|
||||
|
||||
private Method cachedMethod; //Cached for cases where we get call getJavaMethod multiple times.
|
||||
|
||||
public abstract Object invoke(Object target, Object... params) throws IllegalArgumentException, IllegalAccessException,
|
||||
InvocationTargetException;
|
||||
|
||||
public abstract int getModifiers();
|
||||
|
||||
public abstract String getName();
|
||||
|
||||
public abstract String getMethodDescriptor();
|
||||
|
||||
public String toString() {
|
||||
return "Invoker(" + Modifier.toString(getModifiers()) + " " + getClassName() + "." + getName() + getMethodDescriptor()
|
||||
+ ")";
|
||||
}
|
||||
|
||||
public abstract String getClassName();
|
||||
|
||||
protected abstract Method createJavaMethod();
|
||||
|
||||
public String getParamsDescriptor() {
|
||||
String methodDescriptor = getMethodDescriptor();
|
||||
return methodDescriptor.substring(0, methodDescriptor.lastIndexOf(')') + 1);
|
||||
}
|
||||
|
||||
public Class<?> getReturnType() {
|
||||
return getJavaMethod().getReturnType();
|
||||
}
|
||||
|
||||
public final Method getJavaMethod() {
|
||||
if (cachedMethod == null) {
|
||||
cachedMethod = createJavaMethod();
|
||||
}
|
||||
return cachedMethod;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded.ri;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* An implementation of {@link MethodProvider} that provides methods by using the Java reflection API.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class JavaClassMethodProvider extends MethodProvider {
|
||||
|
||||
private Class<?> clazz;
|
||||
|
||||
public JavaClassMethodProvider(Class<?> clazz) {
|
||||
this.clazz = clazz;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Invoker> getDeclaredMethods() {
|
||||
Method[] jMethods = clazz.getDeclaredMethods();
|
||||
List<Invoker> invokers = new ArrayList<Invoker>(jMethods.length);
|
||||
for (Method jMethod : jMethods) {
|
||||
invokers.add(new JavaMethodInvoker(this, jMethod));
|
||||
}
|
||||
return invokers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodProvider getSuper() {
|
||||
Class<?> supr = clazz.getSuperclass();
|
||||
if (supr == null) {
|
||||
return null;
|
||||
}
|
||||
return MethodProvider.create(supr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodProvider[] getInterfaces() {
|
||||
Class<?>[] jItfs = clazz.getInterfaces();
|
||||
MethodProvider[] itfs = new MethodProvider[jItfs.length];
|
||||
for (int i = 0; i < itfs.length; i++) {
|
||||
itfs[i] = MethodProvider.create(jItfs[i]);
|
||||
}
|
||||
return itfs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSlashedName() {
|
||||
return getDottedName().replace('.', '/');
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDottedName() {
|
||||
return clazz.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInterface() {
|
||||
return clazz.isInterface();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded.ri;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.objectweb.asm.Type;
|
||||
import org.springsource.loaded.MethodMember;
|
||||
|
||||
|
||||
/**
|
||||
* Creating Java Method objects for a given MethodMember is rather expensive because it typically involves getting. The declared
|
||||
* methods of a Class and searching for one that matches the method signature. This is most problematic when we are trying to get a
|
||||
* Method for an array of MethodMembers, because in this case we will end up repeating the process multiple times. A JavaMethodCache
|
||||
* instance can cache Method objects from the first time we iterate the declared methods of a class so subsequently we can just get
|
||||
* the other methods from the cache.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class JavaMethodCache {
|
||||
|
||||
//TODO: [...] This cache uses string+descriptor for key. It may be possible to cache method objects inside MethodMembers
|
||||
// themselves, which would make for much quicker 'lookup'.
|
||||
|
||||
/**
|
||||
* This class is used to initialise the cache in a thread safe manner. I.e. a fully filled map should be passed into the cache's
|
||||
* initialize method, so that the 'isInitialized' method will not return true unless initialisation is complete and all entries
|
||||
* are present.
|
||||
*/
|
||||
public static class Initializer {
|
||||
|
||||
//To build up initial map entries with 'put'
|
||||
private Map<String, Method> cache = new HashMap<String, Method>();
|
||||
|
||||
protected void put(Method method) {
|
||||
cache.put(method.getName() + Type.getMethodDescriptor(method), method);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Map indexed by name+descriptor.
|
||||
*/
|
||||
private Map<String, Method> cache = null;
|
||||
|
||||
public boolean isInitialized() {
|
||||
return cache != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should be called to put all entries into the map.
|
||||
*/
|
||||
public void initialize(Initializer init) {
|
||||
this.cache = init.cache;
|
||||
init.cache = null; // Not strictly necessary, but prevents reuse of the initializer.
|
||||
}
|
||||
|
||||
public Method get(MethodMember methodMember) {
|
||||
return cache.get(methodMember.getNameAndDescriptor());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded.ri;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.objectweb.asm.Type;
|
||||
import org.springsource.loaded.jvm.JVM;
|
||||
|
||||
|
||||
/**
|
||||
* Implementation of Invoker that wraps a {@link Method} object. It is assumed that this Method object is from a non-reloadable
|
||||
* Class so it shouldn't need any kind of special handling.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class JavaMethodInvoker extends Invoker {
|
||||
|
||||
private Method method;
|
||||
|
||||
public JavaMethodInvoker(@SuppressWarnings("unused") JavaClassMethodProvider provider, Method method) {
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(Object target, Object... params) throws IllegalArgumentException, IllegalAccessException,
|
||||
InvocationTargetException {
|
||||
return method.invoke(target, params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Method createJavaMethod() {
|
||||
return JVM.copyMethod(method);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getModifiers() {
|
||||
return method.getModifiers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return method.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMethodDescriptor() {
|
||||
return Type.getMethodDescriptor(method);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClassName() {
|
||||
return method.getDeclaringClass().getName();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded.ri;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.objectweb.asm.Type;
|
||||
import org.springsource.loaded.ReloadableType;
|
||||
import org.springsource.loaded.TypeDescriptor;
|
||||
import org.springsource.loaded.TypeRegistry;
|
||||
import org.springsource.loaded.Utils;
|
||||
|
||||
|
||||
/**
|
||||
* To manage the complexity of the different cases created by a variety of different types of contexts where we can do 'method
|
||||
* lookup' we need an abstraction to represent them all.
|
||||
* <p>
|
||||
* This class provides that abstraction.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public abstract class MethodProvider {
|
||||
|
||||
public static MethodProvider create(ReloadableType rtype) {
|
||||
return new ReloadableTypeMethodProvider(rtype);
|
||||
}
|
||||
|
||||
public static MethodProvider create(TypeRegistry registry, TypeDescriptor typeDescriptor) {
|
||||
if (typeDescriptor.isReloadable()) {
|
||||
ReloadableType rtype = registry.getReloadableType(typeDescriptor.getName(), false);
|
||||
if (rtype == null) {
|
||||
TypeRegistry tr = registry;
|
||||
while (rtype == null) {
|
||||
ClassLoader pcl = tr.getClassLoader().getParent();
|
||||
if (pcl == null) {
|
||||
break;
|
||||
} else {
|
||||
tr = TypeRegistry.getTypeRegistryFor(pcl);
|
||||
if (tr == null) {
|
||||
break;
|
||||
}
|
||||
rtype = tr.getReloadableType(typeDescriptor.getName(), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (rtype != null) {
|
||||
return new ReloadableTypeMethodProvider(rtype);
|
||||
}
|
||||
|
||||
// ReloadableType rtype = registry.getReloadableType(typeDescriptor.getName(), true);
|
||||
// // TODO rtype can be null if this type hasn't been loaded yet for the first time, is that true?
|
||||
// // e.g. CGLIB generated proxy for a service type in grails
|
||||
// if (rtype != null) {
|
||||
// return new ReloadableTypeMethodProvider(rtype);
|
||||
// }
|
||||
}
|
||||
try {
|
||||
try {
|
||||
Type objectType = Type.getObjectType(typeDescriptor.getName());
|
||||
|
||||
// TODO doing things this way would mean we aren't 'guessing' the delegation strategy, we
|
||||
// are instead allowing it to do its thing then looking for the right registry.
|
||||
// Above we are guessing regular parent delegation.
|
||||
Class<?> class1 = Utils.toClass(objectType, registry.getClassLoader());
|
||||
if (typeDescriptor.isReloadable()) {
|
||||
ClassLoader cl = class1.getClassLoader();
|
||||
TypeRegistry tr = TypeRegistry.getTypeRegistryFor(cl);
|
||||
ReloadableType rtype = tr.getReloadableType(typeDescriptor.getName(), true);
|
||||
if (rtype != null) {
|
||||
return new ReloadableTypeMethodProvider(rtype);
|
||||
}
|
||||
}
|
||||
return create(class1);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new IllegalStateException("We have a type descriptor for '" + typeDescriptor.getName()
|
||||
+ " but no corresponding Java class", e);
|
||||
}
|
||||
} catch (RuntimeException re) {
|
||||
re.printStackTrace();
|
||||
throw re;
|
||||
}
|
||||
}
|
||||
|
||||
public static MethodProvider create(Class<?> clazz) {
|
||||
return new JavaClassMethodProvider(clazz);
|
||||
}
|
||||
|
||||
public abstract List<Invoker> getDeclaredMethods();
|
||||
|
||||
public abstract MethodProvider getSuper();
|
||||
|
||||
public abstract MethodProvider[] getInterfaces();
|
||||
|
||||
public abstract boolean isInterface();
|
||||
|
||||
public abstract String getSlashedName();
|
||||
|
||||
/**
|
||||
* @return Full qualified name with "."
|
||||
*/
|
||||
public String getDottedName() {
|
||||
return getSlashedName().replace('/', '.');
|
||||
}
|
||||
|
||||
public Invoker dynamicLookup(int mods, String name, String methodDescriptor) {
|
||||
return new DynamicLookup(name, methodDescriptor).lookup(this);
|
||||
}
|
||||
|
||||
public Invoker staticLookup(int mods, String name, String methodDescriptor) {
|
||||
return new StaticLookup(name, methodDescriptor).lookup(this);
|
||||
}
|
||||
|
||||
public Invoker getMethod(String name, Class<?>[] params) {
|
||||
return new GetMethodLookup(name, params).lookup(this);
|
||||
}
|
||||
|
||||
public Invoker getDeclaredMethod(String name, String paramsDescriptor) {
|
||||
return new GetDeclaredMethodLookup(name, paramsDescriptor).lookup(this);
|
||||
}
|
||||
|
||||
public Invoker getDeclaredMethod(String name, Class<?>[] params) {
|
||||
return getDeclaredMethod(name, Utils.toParamDescriptor(params));
|
||||
}
|
||||
|
||||
public Collection<Invoker> getMethods() {
|
||||
return new GetMethodsLookup().lookup(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MethodProvider(" + getDottedName() + ")";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded.ri;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.springsource.loaded.MethodMember;
|
||||
import org.springsource.loaded.jvm.JVM;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Kris De Volder
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class OriginalClassInvoker extends Invoker {
|
||||
|
||||
private Class<?> clazz;
|
||||
private MethodMember method;
|
||||
private JavaMethodCache methodCache;
|
||||
|
||||
public OriginalClassInvoker(Class<?> clazz, MethodMember methodMember, JavaMethodCache methodCache) {
|
||||
this.clazz = clazz;
|
||||
this.method = methodMember;
|
||||
this.methodCache = methodCache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(Object target, Object... params) throws IllegalArgumentException, IllegalAccessException,
|
||||
InvocationTargetException {
|
||||
Method method = getJavaMethod();
|
||||
method.setAccessible(true); //Disable access checks, we do our own!
|
||||
return method.invoke(target, params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Method createJavaMethod() {
|
||||
Method retval = method.cachedMethod;
|
||||
if (retval == null) {
|
||||
if (!methodCache.isInitialized()) {
|
||||
JavaMethodCache.Initializer init = new JavaMethodCache.Initializer();
|
||||
Method[] methods = clazz.getDeclaredMethods();
|
||||
for (Method m : methods) {
|
||||
init.put(m);
|
||||
}
|
||||
methodCache.initialize(init);
|
||||
}
|
||||
retval = methodCache.get(method);
|
||||
method.cachedMethod = retval;
|
||||
if (retval.getModifiers() != method.getModifiers()) {
|
||||
JVM.setMethodModifiers(retval, method.getModifiers());
|
||||
}
|
||||
}
|
||||
return JVM.copyMethod(retval); // Since we got m from a cache we must copy to give it a fresh 'isAccessible' flag.
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getModifiers() {
|
||||
return method.getModifiers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return method.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMethodDescriptor() {
|
||||
return method.getDescriptor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClassName() {
|
||||
return clazz.getName();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded.ri;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springsource.loaded.GlobalConfiguration;
|
||||
import org.springsource.loaded.MethodMember;
|
||||
import org.springsource.loaded.ReloadableType;
|
||||
import org.springsource.loaded.TypeDescriptor;
|
||||
import org.springsource.loaded.TypeRegistry;
|
||||
import org.springsource.loaded.Utils;
|
||||
|
||||
|
||||
/**
|
||||
* Concrete implementation of MethodProvider that provides methods for a Reloadable Type, taking into account any changes made to
|
||||
* the type by reloading.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class ReloadableTypeMethodProvider extends TypeDescriptorMethodProvider {
|
||||
|
||||
ReloadableType rtype;
|
||||
|
||||
public ReloadableTypeMethodProvider(ReloadableType rtype) {
|
||||
if (GlobalConfiguration.assertsOn) {
|
||||
Utils.assertTrue(rtype != null, "ReloadableTypeMethodProvider rtype should not be null");
|
||||
}
|
||||
this.rtype = rtype;
|
||||
}
|
||||
|
||||
protected Invoker invokerFor(final MethodMember methodMember) {
|
||||
if (rtype.getLiveVersion() == null) {
|
||||
//Should be possible to call the original method
|
||||
return new OriginalClassInvoker(rtype.getClazz(), methodMember, rtype.getJavaMethodCache());
|
||||
} else {
|
||||
//Should be calling executor method
|
||||
return ReloadedTypeInvoker.create(this, methodMember);
|
||||
}
|
||||
}
|
||||
|
||||
public TypeDescriptor getTypeDescriptor() {
|
||||
return rtype.getLatestTypeDescriptor();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TypeRegistry getTypeRegistry() {
|
||||
return rtype.getTypeRegistry();
|
||||
}
|
||||
|
||||
public ReloadableType getRType() {
|
||||
return rtype;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Invoker> getDeclaredMethods() {
|
||||
if (TypeRegistry.nothingReloaded && rtype.invokersCache_getDeclaredMethods != null) {
|
||||
// use the cached version, it will not change if a reload hasn't occurred
|
||||
return rtype.invokersCache_getDeclaredMethods;
|
||||
}
|
||||
List<Invoker> invokers = super.getDeclaredMethods();
|
||||
rtype.invokersCache_getDeclaredMethods = invokers;
|
||||
return invokers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Invoker> getMethods() {
|
||||
if (TypeRegistry.nothingReloaded && rtype.invokersCache_getMethods != null) {
|
||||
// use the cached version, it will not change if a reload hasn't occurred
|
||||
return rtype.invokersCache_getMethods;
|
||||
}
|
||||
Collection<Invoker> invokers = super.getMethods();
|
||||
rtype.invokersCache_getMethods = invokers;
|
||||
return invokers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Invoker getMethod(String name, Class<?>[] params) {
|
||||
String paramsDescriptor = Utils.toParamDescriptor(params);
|
||||
if (TypeRegistry.nothingReloaded) {
|
||||
// use the cache
|
||||
// TODO manage memory for this cache
|
||||
Map<String, Map<String, Invoker>> m = rtype.invokerCache_getMethod;
|
||||
Map<String, Invoker> psToInvoker = m.get(name);
|
||||
if (psToInvoker != null) {
|
||||
if (psToInvoker.containsKey(paramsDescriptor)) {
|
||||
return psToInvoker.get(paramsDescriptor);
|
||||
}
|
||||
}
|
||||
}
|
||||
Invoker invoker = super.getMethod(name, params);
|
||||
if (TypeRegistry.nothingReloaded) {
|
||||
Map<String, Map<String, Invoker>> m = rtype.invokerCache_getMethod;
|
||||
Map<String, Invoker> psToInvoker = m.get(name);
|
||||
if (psToInvoker == null) {
|
||||
psToInvoker = new HashMap<String, Invoker>();
|
||||
m.put(name, psToInvoker);
|
||||
}
|
||||
psToInvoker.put(paramsDescriptor, invoker);
|
||||
}
|
||||
return invoker;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Invoker getDeclaredMethod(String name, String paramsDescriptor) {
|
||||
if (TypeRegistry.nothingReloaded) {
|
||||
// use the cache
|
||||
// TODO manage memory for this cache
|
||||
Map<String, Map<String, Invoker>> m = rtype.invokerCache_getDeclaredMethod;
|
||||
Map<String, Invoker> psToInvoker = m.get(name);
|
||||
if (psToInvoker != null) {
|
||||
if (psToInvoker.containsKey(paramsDescriptor)) {
|
||||
return psToInvoker.get(paramsDescriptor);
|
||||
}
|
||||
}
|
||||
}
|
||||
Invoker invoker = super.getDeclaredMethod(name, paramsDescriptor);
|
||||
if (TypeRegistry.nothingReloaded) {
|
||||
Map<String, Map<String, Invoker>> m = rtype.invokerCache_getDeclaredMethod;
|
||||
Map<String, Invoker> psToInvoker = m.get(name);
|
||||
if (psToInvoker == null) {
|
||||
psToInvoker = new HashMap<String, Invoker>();
|
||||
m.put(name, psToInvoker);
|
||||
}
|
||||
psToInvoker.put(paramsDescriptor, invoker);
|
||||
}
|
||||
return invoker;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded.ri;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
import org.objectweb.asm.Type;
|
||||
import org.springsource.loaded.CurrentLiveVersion;
|
||||
import org.springsource.loaded.GlobalConfiguration;
|
||||
import org.springsource.loaded.MethodMember;
|
||||
import org.springsource.loaded.ReloadableType;
|
||||
import org.springsource.loaded.Utils;
|
||||
import org.springsource.loaded.jvm.JVM;
|
||||
|
||||
|
||||
/**
|
||||
* Common super type for Invoker for a method on a reloaded type.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public abstract class ReloadedTypeInvoker extends Invoker {
|
||||
|
||||
ReloadableType rtype;
|
||||
private MethodMember methodMember;
|
||||
|
||||
private ReloadedTypeInvoker(ReloadableTypeMethodProvider declaringType, MethodMember methodMember) {
|
||||
this.methodMember = methodMember;
|
||||
rtype = declaringType.getRType();
|
||||
if (GlobalConfiguration.assertsOn) {
|
||||
Utils.assertTrue(rtype.hasBeenReloaded(),
|
||||
"This class is only equiped to provide invocation/method services for reloaded types");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract Object invoke(Object target, Object... params) throws IllegalArgumentException, IllegalAccessException,
|
||||
InvocationTargetException;
|
||||
|
||||
/**
|
||||
* Create a 'mock' Java Method which is dependent on ReflectiveInterceptor to catch calls to invoke.
|
||||
*/
|
||||
@Override
|
||||
public Method createJavaMethod() {
|
||||
Class<?> clazz = rtype.getClazz();
|
||||
String name = methodMember.getName();
|
||||
String methodDescriptor = methodMember.getDescriptor();
|
||||
|
||||
ClassLoader classLoader = rtype.getTypeRegistry().getClassLoader();
|
||||
try {
|
||||
Class<?>[] params = Utils.toParamClasses(methodDescriptor, classLoader);
|
||||
Class<?> returnType = Utils.toClass(Type.getReturnType(methodDescriptor), classLoader);
|
||||
Class<?>[] exceptions = Utils.slashedNamesToClasses(methodMember.getExceptions(), classLoader);
|
||||
return JVM.newMethod(clazz, name, params, returnType, exceptions, methodMember.getModifiers(),
|
||||
methodMember.getGenericSignature());
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new IllegalStateException("Couldn't create j.l.r.Method for " + clazz.getName() + "." + methodDescriptor, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getModifiers() {
|
||||
return methodMember.getModifiers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return methodMember.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMethodDescriptor() {
|
||||
return methodMember.getDescriptor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClassName() {
|
||||
return rtype.getName();
|
||||
}
|
||||
|
||||
public static Invoker create(ReloadableTypeMethodProvider declaringType, final MethodMember methodMember) {
|
||||
if (Modifier.isStatic(methodMember.getModifiers())) {
|
||||
// Since static methods don't change parameter lists, they just invoke the executor
|
||||
return new ReloadedTypeInvoker(declaringType, methodMember) {
|
||||
@Override
|
||||
public Object invoke(Object target, Object... params) throws IllegalArgumentException, IllegalAccessException,
|
||||
InvocationTargetException {
|
||||
CurrentLiveVersion clv = rtype.getLiveVersion();
|
||||
Method executor = clv.getExecutorMethod(methodMember);
|
||||
return executor.invoke(target, params);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
// Non static method invokers need to add target as a first param
|
||||
return new ReloadedTypeInvoker(declaringType, methodMember) {
|
||||
@Override
|
||||
public Object invoke(Object target, Object... params) throws IllegalArgumentException, IllegalAccessException,
|
||||
InvocationTargetException {
|
||||
CurrentLiveVersion clv = rtype.getLiveVersion();
|
||||
Method executor = clv.getExecutorMethod(methodMember);
|
||||
if (params == null) {
|
||||
return executor.invoke(null, target);
|
||||
} else {
|
||||
Object[] ps = new Object[params.length + 1];
|
||||
System.arraycopy(params, 0, ps, 1, params.length);
|
||||
ps[0] = target;
|
||||
return executor.invoke(null, ps);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded.ri;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Provides an implementation for dynamic method lookup in a given Method provider.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class StaticLookup {
|
||||
|
||||
private String name;
|
||||
private String methodDescriptor;
|
||||
|
||||
/**
|
||||
* Create an object capable of performing a dynamic method lookup in some MethodProvider
|
||||
*/
|
||||
public StaticLookup(String name, String methodDescriptor) {
|
||||
this.name = name;
|
||||
this.methodDescriptor = methodDescriptor;
|
||||
}
|
||||
|
||||
public Invoker lookup(MethodProvider methodProvider) {
|
||||
List<Invoker> methods = methodProvider.getDeclaredMethods();
|
||||
for (Invoker invoker : methods) {
|
||||
if (matches(invoker)) {
|
||||
return invoker;
|
||||
}
|
||||
}
|
||||
//Code below unreachable, because 'deleted' methods are checked for
|
||||
//before the method lookup.
|
||||
return null;
|
||||
}
|
||||
|
||||
protected boolean matches(Invoker invoker) {
|
||||
return name.equals(invoker.getName()) && methodDescriptor.equals(invoker.getMethodDescriptor());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded.ri;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springsource.loaded.MethodMember;
|
||||
import org.springsource.loaded.ReloadableType;
|
||||
import org.springsource.loaded.TypeDescriptor;
|
||||
import org.springsource.loaded.TypeRegistry;
|
||||
|
||||
|
||||
/**
|
||||
* Abstract base class for implementation of MethodProvider that are capable of producing a {@link TypeDescriptor}
|
||||
*
|
||||
* @author Kris De Volder
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public abstract class TypeDescriptorMethodProvider extends MethodProvider {
|
||||
|
||||
protected abstract TypeDescriptor getTypeDescriptor();
|
||||
|
||||
protected abstract TypeRegistry getTypeRegistry();
|
||||
|
||||
protected abstract Invoker invokerFor(MethodMember methodMember);
|
||||
|
||||
@Override
|
||||
public List<Invoker> getDeclaredMethods() {
|
||||
TypeDescriptor typeDescriptor = getTypeDescriptor();
|
||||
MethodMember[] methods = typeDescriptor.getMethods();
|
||||
List<Invoker> invokers = new ArrayList<Invoker>();
|
||||
for (MethodMember method : methods) {
|
||||
if (((MethodMember.BIT_CATCHER | MethodMember.WAS_DELETED) & method.bits) == 0) {
|
||||
invokers.add(invokerFor(method));
|
||||
}
|
||||
}
|
||||
return invokers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodProvider getSuper() {
|
||||
TypeRegistry registry = getTypeRegistry();
|
||||
TypeDescriptor typeDesc = getTypeDescriptor();
|
||||
String superName = typeDesc.getSupertypeName();
|
||||
if (superName == null) {
|
||||
//This happens only for type Object... Code unreachable unless Object is reloadable
|
||||
return null;
|
||||
} else {
|
||||
ReloadableType rsuper = registry.getReloadableType(superName);
|
||||
if (rsuper != null) {
|
||||
return MethodProvider.create(rsuper);
|
||||
} else {
|
||||
TypeDescriptor dsuper = registry.getDescriptorFor(superName);
|
||||
return MethodProvider.create(registry, dsuper);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSlashedName() {
|
||||
return getTypeDescriptor().getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodProvider[] getInterfaces() {
|
||||
TypeRegistry registry = getTypeRegistry();
|
||||
String[] itfNames = getTypeDescriptor().getSuperinterfacesName();
|
||||
MethodProvider[] itfs = new MethodProvider[itfNames.length];
|
||||
for (int i = 0; i < itfNames.length; i++) {
|
||||
itfs[i] = MethodProvider.create(registry, registry.getDescriptorFor(itfNames[i]));
|
||||
}
|
||||
return itfs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInterface() {
|
||||
return getTypeDescriptor().isInterface();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded.test.infra;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.PrintStream;
|
||||
|
||||
import org.objectweb.asm.AnnotationVisitor;
|
||||
import org.objectweb.asm.Attribute;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.ClassVisitor;
|
||||
import org.objectweb.asm.FieldVisitor;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.springsource.loaded.Utils;
|
||||
|
||||
|
||||
/**
|
||||
* @author Andy Clement
|
||||
*/
|
||||
public class ClassPrinter implements ClassVisitor, Opcodes {
|
||||
|
||||
private PrintStream destination;
|
||||
private boolean includeBytecode;
|
||||
|
||||
public static void main(String[] argv) throws Exception {
|
||||
ClassReader reader = new ClassReader(Utils.loadBytesFromStream(new FileInputStream(new File(argv[0]))));
|
||||
reader.accept(new ClassPrinter(System.out, true), 0);
|
||||
}
|
||||
|
||||
public ClassPrinter(PrintStream destination) {
|
||||
this(destination, true);
|
||||
}
|
||||
|
||||
public ClassPrinter(PrintStream destination, boolean includeBytecode) {
|
||||
this.destination = destination;
|
||||
this.includeBytecode = includeBytecode;
|
||||
}
|
||||
|
||||
public static void print(String message, byte[] bytes) {
|
||||
System.out.println(message);
|
||||
print(bytes, true);
|
||||
}
|
||||
|
||||
public static void print(byte[] bytes) {
|
||||
print(bytes, true);
|
||||
}
|
||||
|
||||
public static void print(byte[] bytes, boolean includeBytecode) {
|
||||
ClassReader reader = new ClassReader(bytes);
|
||||
reader.accept(new ClassPrinter(System.out, includeBytecode), 0);
|
||||
}
|
||||
|
||||
public static void print(PrintStream printStream, byte[] bytes, boolean includeBytecode) {
|
||||
ClassReader reader = new ClassReader(bytes);
|
||||
reader.accept(new ClassPrinter(printStream, includeBytecode), 0);
|
||||
}
|
||||
|
||||
public static void print(String message, byte[] bytes, boolean includeBytecode) {
|
||||
System.out.println(message);
|
||||
print(bytes, includeBytecode);
|
||||
}
|
||||
|
||||
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
|
||||
destination.println("CLASS: " + name + " v" + Integer.toString(version) + " " + toHex(access, 4) + "("
|
||||
+ toAccessForClass(access) + ") super " + superName
|
||||
+ (interfaces == null || interfaces.length == 0 ? "" : " interfaces" + toString(interfaces)));
|
||||
}
|
||||
|
||||
private String toString(Object[] os) {
|
||||
if (os == null) {
|
||||
return "";
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (Object o : os) {
|
||||
sb.append(o).append(" ");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private String toAccessForClass(int flags) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if ((flags & Opcodes.ACC_PUBLIC) != 0) {
|
||||
sb.append("public ");
|
||||
}
|
||||
if ((flags & Opcodes.ACC_PRIVATE) != 0) {
|
||||
sb.append("private ");
|
||||
}
|
||||
if ((flags & Opcodes.ACC_PROTECTED) != 0) {
|
||||
sb.append("protected ");
|
||||
}
|
||||
if ((flags & Opcodes.ACC_STATIC) != 0) {
|
||||
sb.append("static ");
|
||||
}
|
||||
if ((flags & Opcodes.ACC_FINAL) != 0) {
|
||||
sb.append("final ");
|
||||
}
|
||||
if ((flags & Opcodes.ACC_SYNCHRONIZED) != 0) {
|
||||
sb.append("synchronized ");
|
||||
}
|
||||
if ((flags & Opcodes.ACC_BRIDGE) != 0) {
|
||||
sb.append("bridge ");
|
||||
}
|
||||
if ((flags & Opcodes.ACC_VARARGS) != 0) {
|
||||
sb.append("varargs ");
|
||||
}
|
||||
if ((flags & Opcodes.ACC_NATIVE) != 0) {
|
||||
sb.append("native ");
|
||||
}
|
||||
if ((flags & Opcodes.ACC_ABSTRACT) != 0) {
|
||||
sb.append("abstract ");
|
||||
}
|
||||
if ((flags & Opcodes.ACC_SYNTHETIC) != 0) {
|
||||
sb.append("synthetic ");
|
||||
}
|
||||
if ((flags & Opcodes.ACC_DEPRECATED) != 0) {
|
||||
sb.append("deprecated ");
|
||||
}
|
||||
if ((flags & Opcodes.ACC_INTERFACE) != 0) {
|
||||
sb.append("interface ");
|
||||
}
|
||||
return sb.toString().trim();
|
||||
}
|
||||
|
||||
public static String toAccessForMember(int flags) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if ((flags & Opcodes.ACC_PUBLIC) != 0) {
|
||||
sb.append("public ");
|
||||
}
|
||||
if ((flags & Opcodes.ACC_PRIVATE) != 0) {
|
||||
sb.append("private ");
|
||||
}
|
||||
if ((flags & Opcodes.ACC_STATIC) != 0) {
|
||||
sb.append("static ");
|
||||
}
|
||||
if ((flags & Opcodes.ACC_PROTECTED) != 0) {
|
||||
sb.append("protected ");
|
||||
}
|
||||
if ((flags & Opcodes.ACC_FINAL) != 0) {
|
||||
sb.append("final ");
|
||||
}
|
||||
if ((flags & Opcodes.ACC_SUPER) != 0) {
|
||||
sb.append("super ");
|
||||
}
|
||||
if ((flags & Opcodes.ACC_INTERFACE) != 0) {
|
||||
sb.append("interface ");
|
||||
}
|
||||
if ((flags & Opcodes.ACC_ABSTRACT) != 0) {
|
||||
sb.append("abstract ");
|
||||
}
|
||||
if ((flags & Opcodes.ACC_SYNTHETIC) != 0) {
|
||||
sb.append("synthetic ");
|
||||
}
|
||||
if ((flags & Opcodes.ACC_ANNOTATION) != 0) {
|
||||
sb.append("annotation ");
|
||||
}
|
||||
if ((flags & Opcodes.ACC_ENUM) != 0) {
|
||||
sb.append("enum ");
|
||||
}
|
||||
if ((flags & Opcodes.ACC_DEPRECATED) != 0) {
|
||||
sb.append("deprecated ");
|
||||
}
|
||||
return sb.toString().trim();
|
||||
}
|
||||
|
||||
private String toHex(int i, int len) {
|
||||
StringBuilder sb = new StringBuilder("00000000");
|
||||
sb.append(Integer.toHexString(i));
|
||||
return "0x" + sb.substring(sb.length() - len);
|
||||
}
|
||||
|
||||
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
|
||||
destination.print("ANNOTATION " + desc + " vis?" + visible + " VALUE ");
|
||||
return new AnnotationVisitorPrinter();
|
||||
}
|
||||
|
||||
class AnnotationVisitorPrinter implements AnnotationVisitor {
|
||||
|
||||
public void visit(String name, Object value) {
|
||||
destination.print(name + "=" + value + " ");
|
||||
}
|
||||
|
||||
public void visitEnum(String name, String desc, String value) {
|
||||
destination.print(name + "=" + desc + "." + value + " ");
|
||||
}
|
||||
|
||||
public AnnotationVisitor visitAnnotation(String name, String desc) {
|
||||
destination.print(name + "=" + desc + " ");
|
||||
return new AnnotationVisitorPrinter();
|
||||
}
|
||||
|
||||
public AnnotationVisitor visitArray(String name) {
|
||||
destination.print(name + " ");
|
||||
return new AnnotationVisitorPrinter();
|
||||
}
|
||||
|
||||
public void visitEnd() {
|
||||
destination.println();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void visitAttribute(Attribute attr) {
|
||||
}
|
||||
|
||||
public void visitEnd() {
|
||||
destination.println();
|
||||
}
|
||||
|
||||
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("FIELD " + toHex(access, 4) + "(" + toAccessForMember(access) + ") " + name + " " + desc
|
||||
+ (signature != null ? " " + signature : ""));
|
||||
destination.println(sb.toString().trim());
|
||||
return null;
|
||||
}
|
||||
|
||||
public void visitInnerClass(String name, String outerName, String innerName, int access) {
|
||||
destination.println("INNERCLASS: " + name + " " + outerName + " " + innerName + " " + access);
|
||||
}
|
||||
|
||||
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
|
||||
StringBuilder s = new StringBuilder();
|
||||
s.append("METHOD: " + toHex(access, 4) + "(" + toAccessForMember(access) + ") " + name + desc + " " + fromArray(exceptions));
|
||||
destination.println(s.toString().trim());
|
||||
return includeBytecode ? new MethodPrinter(destination) : null;
|
||||
}
|
||||
|
||||
private String fromArray(Object[] os) {
|
||||
if (os == null) {
|
||||
return "";
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (Object o : os) {
|
||||
sb.append(o).append(" ");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public void visitOuterClass(String owner, String name, String desc) {
|
||||
destination.println("OUTERCLASS: " + owner + " " + name + " " + desc);
|
||||
}
|
||||
|
||||
public void visitSource(String source, String debug) {
|
||||
destination.println("SOURCE: " + source + " " + debug);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* Copyright 2010-2012 VMware and contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springsource.loaded.test.infra;
|
||||
|
||||
import java.io.PrintStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.objectweb.asm.AnnotationVisitor;
|
||||
import org.objectweb.asm.Attribute;
|
||||
import org.objectweb.asm.Label;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.springsource.loaded.Utils;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Andy Clement
|
||||
*/
|
||||
public class MethodPrinter implements MethodVisitor, Opcodes {
|
||||
|
||||
PrintStream to;
|
||||
|
||||
List<Label> labels = new ArrayList<Label>();
|
||||
|
||||
private String toString(Label label) {
|
||||
int idx = labels.indexOf(label);
|
||||
if (idx != -1) {
|
||||
return "L" + idx;
|
||||
}
|
||||
labels.add(label);
|
||||
return "L" + labels.indexOf(label);
|
||||
}
|
||||
|
||||
public MethodPrinter(PrintStream destination) {
|
||||
this.to = destination;
|
||||
}
|
||||
|
||||
public void visitCode() {
|
||||
to.print(" CODE\n");
|
||||
}
|
||||
|
||||
public void visitMethodInsn(int opcode, String owner, String name, String desc) {
|
||||
if (opcode == Opcodes.INVOKESTATIC) {
|
||||
to.println(" INVOKESTATIC " + owner + "." + name + desc);
|
||||
} else if (opcode == Opcodes.INVOKESPECIAL) {
|
||||
to.println(" INVOKESPECIAL " + owner + "." + name + desc);
|
||||
} else if (opcode == Opcodes.INVOKEVIRTUAL) {
|
||||
to.println(" INVOKEVIRTUAL " + owner + "." + name + desc);
|
||||
} else if (opcode == Opcodes.INVOKEINTERFACE) {
|
||||
to.println(" INVOKEINTERFACE " + owner + "." + name + desc);
|
||||
} else {
|
||||
throw new IllegalStateException(":" + opcode);
|
||||
}
|
||||
}
|
||||
|
||||
// -- pas de implemented
|
||||
|
||||
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
|
||||
to.print("ANNOTATION " + desc + " vis?" + visible + " VALUE ");
|
||||
return new AnnotationVisitorPrinter();
|
||||
}
|
||||
|
||||
class AnnotationVisitorPrinter implements AnnotationVisitor {
|
||||
|
||||
public void visit(String name, Object value) {
|
||||
to.print(name + "=" + value + " ");
|
||||
}
|
||||
|
||||
public void visitEnum(String name, String desc, String value) {
|
||||
to.print(name + "=" + desc + "." + value + " ");
|
||||
}
|
||||
|
||||
public AnnotationVisitor visitAnnotation(String name, String desc) {
|
||||
to.print(name + "=" + desc + " ");
|
||||
return new AnnotationVisitorPrinter();
|
||||
}
|
||||
|
||||
public AnnotationVisitor visitArray(String name) {
|
||||
to.print(name + " ");
|
||||
return new AnnotationVisitorPrinter();
|
||||
}
|
||||
|
||||
public void visitEnd() {
|
||||
to.println();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public AnnotationVisitor visitAnnotationDefault() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void visitAttribute(Attribute attr) {
|
||||
}
|
||||
|
||||
public void visitEnd() {
|
||||
}
|
||||
|
||||
public void visitFieldInsn(int opcode, String owner, String name, String desc) {
|
||||
if (opcode == Opcodes.GETSTATIC) {
|
||||
to.println(" GETSTATIC " + owner + "." + name + " " + desc);
|
||||
} else if (opcode == Opcodes.PUTSTATIC) {
|
||||
to.println(" PUTSTATIC " + owner + "." + name + " " + desc);
|
||||
} else if (opcode == Opcodes.GETFIELD) {
|
||||
to.println(" GETFIELD " + owner + "." + name + " " + desc);
|
||||
} else if (opcode == Opcodes.PUTFIELD) {
|
||||
to.println(" PUTFIELD " + owner + "." + name + " " + desc);
|
||||
} else {
|
||||
throw new IllegalStateException(":" + opcode);
|
||||
}
|
||||
}
|
||||
|
||||
public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) {
|
||||
}
|
||||
|
||||
public void visitIincInsn(int var, int increment) {
|
||||
}
|
||||
|
||||
public void visitInsn(int opcode) {
|
||||
to.println(" " + Utils.toOpcodeString(opcode));
|
||||
}
|
||||
|
||||
public void visitIntInsn(int opcode, int operand) {
|
||||
to.println(" " + Utils.toOpcodeString(opcode) + " " + operand);
|
||||
}
|
||||
|
||||
public void visitJumpInsn(int opcode, Label label) {
|
||||
to.println(" " + Utils.toOpcodeString(opcode) + " " + toString(label));
|
||||
}
|
||||
|
||||
public void visitLabel(Label label) {
|
||||
to.println(" " + toString(label));
|
||||
}
|
||||
|
||||
public void visitLdcInsn(Object cst) {
|
||||
to.println(" LDC " + cst);
|
||||
}
|
||||
|
||||
public void visitLineNumber(int line, Label start) {
|
||||
}
|
||||
|
||||
public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {
|
||||
}
|
||||
|
||||
public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
|
||||
}
|
||||
|
||||
public void visitMaxs(int maxStack, int maxLocals) {
|
||||
}
|
||||
|
||||
public void visitMultiANewArrayInsn(String desc, int dims) {
|
||||
}
|
||||
|
||||
public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) {
|
||||
}
|
||||
|
||||
public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
|
||||
}
|
||||
|
||||
public void visitTypeInsn(int opcode, String type) {
|
||||
if (opcode == Opcodes.NEW) { // 187
|
||||
to.println(" NEW " + type);
|
||||
} else if (opcode == Opcodes.ANEWARRAY) { // 189
|
||||
to.println(" ANEWARRAY " + type);
|
||||
} else if (opcode == Opcodes.CHECKCAST) { // 192
|
||||
to.println(" CHECKCAST " + type);
|
||||
} else if (opcode == Opcodes.INSTANCEOF) { // 193
|
||||
to.println(" INSTANCEOF " + type);
|
||||
} else {
|
||||
throw new IllegalStateException(":" + opcode);
|
||||
}
|
||||
}
|
||||
|
||||
public void visitVarInsn(int opcode, int var) {
|
||||
if (opcode == Opcodes.ALOAD) {
|
||||
to.println(" ALOAD " + var);
|
||||
} else if (opcode == Opcodes.ASTORE) {
|
||||
to.println(" ASTORE " + var);
|
||||
} else if (opcode == Opcodes.ILOAD) {
|
||||
to.println(" ILOAD " + var);
|
||||
} else if (opcode == FLOAD) {
|
||||
to.println(" FLOAD " + var);
|
||||
} else if (opcode == LLOAD) {
|
||||
to.println(" LLOAD " + var);
|
||||
} else if (opcode == DLOAD) {
|
||||
to.println(" DLOAD " + var);
|
||||
} else if (opcode == ISTORE) {
|
||||
to.println(" ISTORE " + var);
|
||||
} else if (opcode == LSTORE) {
|
||||
to.println(" LSTORE " + var);
|
||||
} else {
|
||||
throw new IllegalStateException(":" + opcode);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user