Rudimentary jar watching
New options watchJars takes a colon separated list of jars (just the jar names, not paths). For example: -Dspringloaded=watchJars=foo.jar:bar.jar Committed some very basic tests that are passing, they are in the SpringLoadedTestsInSeparateJVM test class, see the tests with 'jar' in the name.
This commit is contained in:
@@ -30,7 +30,7 @@ import org.springsource.loaded.agent.SpringPlugin;
|
||||
* Encapsulates configurable elements - these are set (to values other than the defaults) in TypeRegistry when the
|
||||
* system property springloaded configuration is processed. Some of the options are only used by testcases to make the
|
||||
* testcases easier to write and more straightforward.
|
||||
*
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
@@ -104,6 +104,13 @@ public class GlobalConfiguration {
|
||||
|
||||
public final static boolean logNonInterceptedReflectiveCalls = false;
|
||||
|
||||
/**
|
||||
* Holds a list of fully qualified paths to jars that should be 'watched' for changes. Types
|
||||
* within these jars will be made reloadable. Set via option 'watchJars' which
|
||||
* takes a colon separated list of jars.
|
||||
*/
|
||||
public static String[] jarsToWatch = null;
|
||||
|
||||
/**
|
||||
* Global control for checking assertions
|
||||
*/
|
||||
@@ -255,6 +262,12 @@ public class GlobalConfiguration {
|
||||
log.info("configuration: dumpFolder = " + dumpFolder);
|
||||
}
|
||||
}
|
||||
else if (key.equals("watchJars")) {
|
||||
if (isRuntimeLogging && log.isLoggable(Level.INFO)) {
|
||||
log.info("Watching jars: " + kv.substring(equals + 1));
|
||||
}
|
||||
jarsToWatch = kv.substring(equals + 1).split(":");
|
||||
}
|
||||
else if (key.equals("maxClassDefinitions")) {
|
||||
try {
|
||||
maxClassDefinitions = Integer.parseInt(kv.substring(equals + 1));
|
||||
|
||||
@@ -58,7 +58,7 @@ import org.springsource.loaded.support.Java8;
|
||||
* The type registry tracks all reloadable types loaded by a specific class loader. It is configurable via a
|
||||
* springloaded.properties file (which it will discover as resources through the classloader) or directly via a
|
||||
* configure(Properties) method call.
|
||||
*
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
@@ -97,13 +97,13 @@ public class TypeRegistry {
|
||||
"javax.management.remote.rmi.NoCallStackClassLoader",
|
||||
"org.springsource.loaded.ChildClassLoader",
|
||||
// "groovy.lang.GroovyClassLoader$InnerLoader",
|
||||
// not excluding GCL$InnerLoader because we want the reflection stuff rewritten - think we need to separate out
|
||||
// not excluding GCL$InnerLoader because we want the reflection stuff rewritten - think we need to separate out
|
||||
// reflection rewriting from the rest of callside rewriting. Although do we still need to rewrite call sites anyway, although the code there may not change (i.e. TypeRewriter not
|
||||
// required), the targets for some calls may come and go (may not have been in the original loaded version)
|
||||
"org.apache.jasper.servlet.JasperLoader",
|
||||
|
||||
// tc server configuration...
|
||||
// "org.apache.catalina.loader.StandardClassLoader"
|
||||
// "org.apache.catalina.loader.StandardClassLoader"
|
||||
};
|
||||
|
||||
// @formatter:on
|
||||
@@ -231,7 +231,7 @@ public class TypeRegistry {
|
||||
/**
|
||||
* Check if a type registry exists for a specific type registry ID. Enables parts of the system (for example the
|
||||
* FileSystemWatcher) to check if a type registry is still alive/active.
|
||||
*
|
||||
*
|
||||
* @param typeRegistryId the ID of a type registry
|
||||
* @return true if that type registry is still around, false otherwise
|
||||
*/
|
||||
@@ -246,7 +246,7 @@ public class TypeRegistry {
|
||||
|
||||
/**
|
||||
* Factory access method for obtaining TypeRegistry instances. Returns a TypeRegistry for the specified classloader.
|
||||
*
|
||||
*
|
||||
* @param classloader The classloader to create/retrieve the type registry for
|
||||
* @return the TypeRegistry for the classloader
|
||||
*/
|
||||
@@ -308,7 +308,7 @@ public class TypeRegistry {
|
||||
|
||||
/**
|
||||
* Only checks the reloadable types this registry knows about, it doesn't search beyond that.
|
||||
*
|
||||
*
|
||||
* @param slashedClassname the slashed classname (e.g. java/lang/String)
|
||||
* @return the TypeDescriptor or null if that classname is unknown
|
||||
*/
|
||||
@@ -432,7 +432,7 @@ public class TypeRegistry {
|
||||
/**
|
||||
* Configure this TypeRegistry using a specific set of properties - this will override any previous configuration.
|
||||
* It is mainly provided for testing purposes.
|
||||
*
|
||||
*
|
||||
* @param properties the properties to use to configure this type registry
|
||||
*/
|
||||
public void configure(Properties properties) {
|
||||
@@ -605,7 +605,7 @@ public class TypeRegistry {
|
||||
* Determine if the named type could be reloadable. This method is invoked if the user has not setup any inclusions.
|
||||
* With no inclusions specified, something is considered reloadable if it is accessible by the classloader for this
|
||||
* registry and is not in a jar
|
||||
*
|
||||
*
|
||||
* @param slashedName the typename of interest (e.g. com/foo/Bar)
|
||||
* @return true if the type should be considered reloadable
|
||||
*/
|
||||
@@ -693,6 +693,26 @@ public class TypeRegistry {
|
||||
// new RuntimeException().printStackTrace();
|
||||
// }
|
||||
reloadable = protocol.equals("file");
|
||||
// Check if it is from a jar that is being watched
|
||||
if (!reloadable && protocol.equals("jar")) {
|
||||
if (GlobalConfiguration.jarsToWatch != null) {
|
||||
// 1.3 feature, can watch jars!
|
||||
String urlstring = url.toString();
|
||||
// example path: jar:file:/var/folders/cn/p3n4rh_n6z7gm6zwk53mtfc80000gp/T/_sl308369570226517394/foo.jar!/Foo.class
|
||||
int bangSlash = urlstring.lastIndexOf("!/");
|
||||
if (bangSlash != -1) {
|
||||
String pathInJar = urlstring.substring(bangSlash + 2);
|
||||
String remainingPrefix = urlstring.substring(0, bangSlash);
|
||||
int lastSlash = remainingPrefix.lastIndexOf("/"); // TODO windoze?
|
||||
String jarname = remainingPrefix.substring(lastSlash + 1);
|
||||
for (String jarToWatch : GlobalConfiguration.jarsToWatch) {
|
||||
if (jarname.equals(jarToWatch)) {
|
||||
reloadable = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (packageName != null) {
|
||||
if (reloadable) {
|
||||
@@ -719,7 +739,7 @@ public class TypeRegistry {
|
||||
/**
|
||||
* Determine if the type specified is a reloadable type. This method works purely by name, it does not load
|
||||
* anything.
|
||||
*
|
||||
*
|
||||
* @param slashedName the type name, eg. a/b/c/D
|
||||
* @param protectionDomain the protection domain this class is being loaded under
|
||||
* @param bytes the class bytes for the class being loaded
|
||||
@@ -851,7 +871,7 @@ public class TypeRegistry {
|
||||
/**
|
||||
* Lookup the type ID for a string. First checks those allocated but not yet registered, then those that are already
|
||||
* registered. If not found then a new one is allocated and recorded.
|
||||
*
|
||||
*
|
||||
* @param slashname the slashed type name, eg. a/b/c/D
|
||||
* @param allocateIfNotFound determines whether an id should be allocated for the type if it cannot be found
|
||||
* @return the unique ID number
|
||||
@@ -900,6 +920,13 @@ public class TypeRegistry {
|
||||
rtype.loadNewVersion(versionstamp, newBytes);
|
||||
}
|
||||
|
||||
public void loadNewVersion(ReloadableType rtype, long lastModTime, InputStream is) {
|
||||
String versionstamp = Utils.encode(lastModTime);
|
||||
// load bytes for new version
|
||||
byte[] newBytes = Utils.loadFromStream(is);
|
||||
rtype.loadNewVersion(versionstamp, newBytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map from a registry ID number to a registry instance. ID numbers are used in the rewritten code. WeakReferences
|
||||
* so that we aren't preventing collection of TypeRegistry objects when their classloaders are GC'd.
|
||||
@@ -952,7 +979,7 @@ public class TypeRegistry {
|
||||
|
||||
/**
|
||||
* Add a type to the registry. The name should have already passed the isReloadableTypeName() test.
|
||||
*
|
||||
*
|
||||
* @param dottedname type name of the form a.b.c.D
|
||||
* @param initialbytes the first version of the bytes as loaded
|
||||
* @return the ReloadableType or null if it cannot be made reloadable
|
||||
@@ -1053,7 +1080,7 @@ public class TypeRegistry {
|
||||
/**
|
||||
* Sometimes we discover the reloadabletype during program execution, for example A calls B and we haven't yet seen
|
||||
* B. We find B has been loaded by a parent classloader, let's remember B here so we can do fast lookups for it.
|
||||
*
|
||||
*
|
||||
* @param typeId the id for the type
|
||||
* @param rtype the ReloadableType to associate with the id
|
||||
*/
|
||||
@@ -1070,7 +1097,7 @@ public class TypeRegistry {
|
||||
/**
|
||||
* Determine the reloadabletype object representation for a specified class. If the caller already knows the ID for
|
||||
* the type, that would be a quicker way to locate the reloadable type object.
|
||||
*
|
||||
*
|
||||
* @param slashedClassName the slashed (e.g. java/lang/String) class name
|
||||
* @return the ReloadableType
|
||||
*/
|
||||
@@ -1095,7 +1122,7 @@ public class TypeRegistry {
|
||||
* For a specific classname, this method will search in the current type registry and any parent type registries
|
||||
* (similar to a regular classloader delegation strategy). Returns null if the type is not found. It does not
|
||||
* attempt to load anything in.
|
||||
*
|
||||
*
|
||||
* @param classname the type being searched for, e.g. com/foo/Bar
|
||||
* @return the ReloadableType if found, otherwise null
|
||||
*/
|
||||
@@ -1125,7 +1152,7 @@ public class TypeRegistry {
|
||||
* Find the ReloadableType object for a given classname. If the allocateIdIfNotYetLoaded option is set then a new id
|
||||
* will be allocated for this classname if it hasn't previously been seen before. This method does not create new
|
||||
* ReloadableType objects, they are expected to come into existence when defined by the classloader.
|
||||
*
|
||||
*
|
||||
* @param slashedClassname the slashed class name (e.g. java/lang/String)
|
||||
* @param allocateIdIfNotYetLoaded if true an id will be allocated because sometime later the type will be loaded
|
||||
* (and made reloadable)
|
||||
@@ -1206,7 +1233,7 @@ public class TypeRegistry {
|
||||
|
||||
/**
|
||||
* Used to determine if the invokedynamic needs to be intercepted.
|
||||
*
|
||||
*
|
||||
* @return null if nothing has been reloaded
|
||||
*/
|
||||
@UsedByGeneratedCode
|
||||
@@ -1223,7 +1250,7 @@ public class TypeRegistry {
|
||||
* Determine if something has changed in a particular type related to a particular descriptor and so the dispatcher
|
||||
* interface should be used. The type registry ID and class ID are merged in the 'ids' parameter. This method is for
|
||||
* INVOKESTATIC rewrites and so performs additional checks because it assumes the target is static.
|
||||
*
|
||||
*
|
||||
* @param ids packed representation of the registryId (top 16bits) and typeId (bottom 16bits)
|
||||
* @param nameAndDescriptor the name and descriptor of the method about to be INVOKESTATIC'd
|
||||
* @return null if the original code can run otherwise return the dispatcher to use
|
||||
@@ -1364,7 +1391,7 @@ public class TypeRegistry {
|
||||
|
||||
/**
|
||||
* See notes.md#001
|
||||
*
|
||||
*
|
||||
* @param instance the object instance on which the INVOKEINTERFACE is being called
|
||||
* @param params the parameters to the INVOKEINTERFACE call
|
||||
* @param instance2 the object instance on which the INVOKEINTERFACE is being called
|
||||
@@ -1470,7 +1497,7 @@ public class TypeRegistry {
|
||||
* changes about the descriptor it is considered an entirely different method (and the old form is a deleted
|
||||
* method). For this reason there is no need to consider 'changed' methods, because the static-ness nor visibility
|
||||
* cannot change.
|
||||
*
|
||||
*
|
||||
* @param ids packed representation of the registryId (top 16bits) and typeId (bottom 16bits)
|
||||
* @param nameAndDescriptor the name and descriptor of the method about to be INVOKEINTERFACE'd
|
||||
* @return true if the original method operation must be intercepted
|
||||
@@ -1523,19 +1550,19 @@ public class TypeRegistry {
|
||||
* "can i call what I was going to call, or not?"
|
||||
* The answer to that question primarily depends on whether the method was previously defined in the target hierarchy. If it was then
|
||||
* yes, make the call and let catchers sort it out. If not then we need to jump through firey hoops.
|
||||
*
|
||||
*
|
||||
* For example, this code:
|
||||
* public int run1() {
|
||||
XX zz = new ZZ();
|
||||
return zz.foo();
|
||||
}
|
||||
*
|
||||
*
|
||||
* results in this invokevirtual:
|
||||
*
|
||||
*
|
||||
INVOKEVIRTUAL invokevirtual/XX.foo()I
|
||||
*
|
||||
*
|
||||
* Notice the static type of the variable is used in the method descriptor for the invoke.
|
||||
*
|
||||
*
|
||||
* The rewriter then turns it into this:
|
||||
LDC 65537
|
||||
LDC foo()I
|
||||
@@ -1554,28 +1581,28 @@ public class TypeRegistry {
|
||||
L5
|
||||
*
|
||||
* What that says is: call ivicheck for 65537,foo()I (65537 embodies the type registry id and the class ID, XX in our case, as per the descriptor).
|
||||
*
|
||||
*
|
||||
* vcheck should return true for methods that do not exist - since we can't run the invokevirtual
|
||||
*
|
||||
*
|
||||
* If vcheck returns false, do what you were going to do anyway:
|
||||
* this will actually cause us to jump into a catcher method.
|
||||
* If vcheck returns true, call the __execute() method on the type XX - however, due to virtual dispatch and all the types implementing __execute() we
|
||||
* will end up in the one for the dynamic type (ZZ.__execute())
|
||||
*
|
||||
*
|
||||
* These two paths proceed as follows.
|
||||
*
|
||||
*
|
||||
* 1) If we jumped into a catcher method, we actually hit the catcher ZZ.foo()
|
||||
* The catcher works as follows - grab the latest version of this type (if it has been reloaded) and call foo() on the dispatcher, otherwise call super.foo().
|
||||
* The catcher exists because the type did not originally implement the method. It exists to enable the type to implement the method later. The same sequence
|
||||
* will continue (through catchers) until a type is hit that provides an implementation which did not used to, or an original implementation is hit, or we run out
|
||||
* of options and an NSME is created. The catcher code is below.
|
||||
*
|
||||
*
|
||||
* 2) In the ZZ.__execute() method we actually ask the type registry to tell us what to call - we call determineDispatcher which uses the runtime type for the call
|
||||
* and discovers which dispatcher should be used. it is a bit naughty in that if it finds an reloadabletype that is the right answer but that hasn't been reloaded,
|
||||
* it forces a reload of the original code to create a dispatcher instance that can be returned.
|
||||
*
|
||||
*
|
||||
* __execute is for methods that were never there at all
|
||||
*
|
||||
*
|
||||
METHOD: 0x0001(public) foo()I
|
||||
CODE
|
||||
GETSTATIC invokevirtual/ZZ.r$type Lorg/springsource/loaded/ReloadableType;
|
||||
@@ -1592,14 +1619,14 @@ public class TypeRegistry {
|
||||
ALOAD 0
|
||||
INVOKESPECIAL invokevirtual/YY.foo()I
|
||||
IRETURN
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Called for a field operation - trying to determine whether a particular field needs special handling.
|
||||
*
|
||||
*
|
||||
* @param ids packed representation of the registryId (top 16bits) and typeId (bottom 16bits)
|
||||
* @param name the name of the instance field about to be accessed
|
||||
* @return true if the field operation must be intercepted
|
||||
@@ -1664,7 +1691,7 @@ public class TypeRegistry {
|
||||
/**
|
||||
* Used in code the generated code replaces invokevirtual calls. Determine if the code can run as it was originally
|
||||
* compiled.
|
||||
*
|
||||
*
|
||||
* This method will return FALSE if nothing has changed to interfere with the invocation and it should proceed. This
|
||||
* method will return TRUE if something has changed and the caller needs to do something different.
|
||||
*
|
||||
@@ -1758,7 +1785,7 @@ public class TypeRegistry {
|
||||
|
||||
/**
|
||||
* This method discovers the reloadable type instance for the registry and type id specified.
|
||||
*
|
||||
*
|
||||
* @param typeRegistryId the type registry id
|
||||
* @param typeId the type id
|
||||
* @return the ReloadableType (if there is no ReloadableType an exception will be thrown)
|
||||
@@ -1787,6 +1814,7 @@ public class TypeRegistry {
|
||||
return reloadableType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder s = new StringBuilder();
|
||||
s.append("TypeRegistry(id=");
|
||||
@@ -1828,7 +1856,8 @@ public class TypeRegistry {
|
||||
log.info("Called to monitor " + rtype.dottedtypename + " from " + externalForm);
|
||||
}
|
||||
|
||||
if (!watching.contains(externalForm)) {
|
||||
boolean watchingContainsIt = watching.contains(externalForm);
|
||||
if (!watchingContainsIt || externalForm.endsWith(".jar")) {
|
||||
// classFileToType.put(externalForm, rtype.slashedtypename);
|
||||
File f = new File(externalForm);
|
||||
if (fileChangeListener == null) {
|
||||
@@ -1838,8 +1867,10 @@ public class TypeRegistry {
|
||||
fsWatcher = new FileSystemWatcher(fileChangeListener, id, getClassLoaderName());
|
||||
}
|
||||
fileChangeListener.register(rtype, f);
|
||||
fsWatcher.register(f);
|
||||
watching.add(externalForm);
|
||||
if (!watchingContainsIt) {
|
||||
fsWatcher.register(f);
|
||||
watching.add(externalForm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1916,7 +1947,7 @@ public class TypeRegistry {
|
||||
/**
|
||||
* Process some type pattern objects from the supplied value. For example the value might be
|
||||
* 'com.foo.Bar,!com.foo.Goo'
|
||||
*
|
||||
*
|
||||
* @param value string defining a comma separated list of type patterns
|
||||
* @return list of TypePatterns
|
||||
*/
|
||||
@@ -2010,7 +2041,7 @@ public class TypeRegistry {
|
||||
/**
|
||||
* To avoid leaking permgen we want to periodically discard the child classloader and recreate a new one. We will
|
||||
* need to then redefine types again over time as they are used (the most recent variants of them).
|
||||
*
|
||||
*
|
||||
* @param currentlyDefining the reloadable type currently being defined reloaded
|
||||
*/
|
||||
public void checkChildClassLoader(ReloadableType currentlyDefining) {
|
||||
@@ -2101,7 +2132,7 @@ public class TypeRegistry {
|
||||
/**
|
||||
* When an invokedynamic instruction is reached, we allocate an id that recognizes that bsm and the parameters to
|
||||
* that bsm. The index can be used when rewriting that invokedynamic
|
||||
*
|
||||
*
|
||||
* @param slashedClassName the slashed class name containing the bootstrap method
|
||||
* @param bsm the bootstrap methods
|
||||
* @param bsmArgs the bootstrap method arguments (asm types)
|
||||
@@ -2148,7 +2179,7 @@ public class TypeRegistry {
|
||||
/**
|
||||
* Called from the static initializer of a reloadabletype, allowing it to connect itself to the parent type, such
|
||||
* that when reloading occurs we can mark all relevant types in the hierarchy as being impacted by the reload.
|
||||
*
|
||||
*
|
||||
* @param child the ReloadableType actively being initialized
|
||||
* @param parent the superclass of the reloadable type (may or may not be reloadable!)
|
||||
*/
|
||||
|
||||
@@ -27,7 +27,6 @@ import org.objectweb.asm.FieldVisitor;
|
||||
import org.objectweb.asm.Label;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.Type;
|
||||
import org.springsource.loaded.Utils.ReturnType;
|
||||
|
||||
|
||||
@@ -41,7 +40,7 @@ import org.springsource.loaded.Utils.ReturnType;
|
||||
* <li>Creates catchers for inherited methods. Catchers are simply passed through unless a new version of the class
|
||||
* provides an implementation
|
||||
* </ul>
|
||||
*
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
@@ -311,7 +310,7 @@ public class TypeRewriter implements Constants {
|
||||
mv.visitJumpInsn(IFNONNULL, l2);
|
||||
mv.visitTypeInsn(NEW, tStaticStateManager);
|
||||
mv.visitInsn(DUP);
|
||||
mv.visitMethodInsn(INVOKESPECIAL, tStaticStateManager, "<init>", "()V");
|
||||
mv.visitMethodInsn(INVOKESPECIAL, tStaticStateManager, "<init>", "()V", false);
|
||||
mv.visitFieldInsn(PUTSTATIC, slashedname, fStaticFieldsName, lStaticStateManager);
|
||||
mv.visitLabel(l2);
|
||||
mv.visitFieldInsn(GETSTATIC, slashedname, fStaticFieldsName, lStaticStateManager);
|
||||
@@ -319,7 +318,7 @@ public class TypeRewriter implements Constants {
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitVarInsn(ALOAD, 1);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, tStaticStateManager, "setValue", "(" + lReloadableType
|
||||
+ "Ljava/lang/Object;Ljava/lang/String;)V");
|
||||
+ "Ljava/lang/Object;Ljava/lang/String;)V", false);
|
||||
mv.visitInsn(RETURN);
|
||||
mv.visitMaxs(4, 2);
|
||||
mv.visitEnd();
|
||||
@@ -406,7 +405,7 @@ public class TypeRewriter implements Constants {
|
||||
mv.visitVarInsn(ALOAD, lvInstance);
|
||||
mv.visitVarInsn(ALOAD, lvNewValue);
|
||||
mv.visitVarInsn(ALOAD, lvName);
|
||||
// setValue(ReloadableType rtype, Object instance, Object value, String name)
|
||||
// setValue(ReloadableType rtype, Object instance, Object value, String name)
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, tInstanceStateManager, "setValue", "(" + lReloadableType
|
||||
+ "Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/String;)V");
|
||||
mv.visitInsn(RETURN);
|
||||
@@ -714,13 +713,13 @@ public class TypeRewriter implements Constants {
|
||||
if (MethodMember.isCatcherForInterfaceMethod(method)) {
|
||||
mv.visitTypeInsn(NEW, "java/lang/IllegalStateException");
|
||||
mv.visitInsn(DUP);
|
||||
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/AbstractMethodError", "<init>", "()V");
|
||||
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/AbstractMethodError", "<init>", "()V", false);
|
||||
mv.visitInsn(ATHROW);
|
||||
}
|
||||
else {
|
||||
mv.visitVarInsn(ALOAD, 0); // load this
|
||||
Utils.createLoadsBasedOnDescriptor(mv, method.getDescriptor(), 1);
|
||||
mv.visitMethodInsn(INVOKESPECIAL, supertypeName, method.getName(), method.getDescriptor());
|
||||
mv.visitMethodInsn(INVOKESPECIAL, supertypeName, method.getName(), method.getDescriptor(), false);
|
||||
Utils.addCorrectReturnInstruction(mv, methodReturnType, false);
|
||||
}
|
||||
|
||||
@@ -765,7 +764,7 @@ public class TypeRewriter implements Constants {
|
||||
/**
|
||||
* For the fields that need it (protected fields from a non-reloadable supertype), create the getters and
|
||||
* setters so that the executor can read/write them.
|
||||
*
|
||||
*
|
||||
*/
|
||||
private void createProtectedFieldGetterSetter(FieldMember field) {
|
||||
String descriptor = field.descriptor;
|
||||
@@ -847,7 +846,7 @@ public class TypeRewriter implements Constants {
|
||||
mv.visitJumpInsn(IFNONNULL, l1);
|
||||
mv.visitTypeInsn(NEW, tStaticStateManager);
|
||||
mv.visitInsn(DUP);
|
||||
mv.visitMethodInsn(INVOKESPECIAL, tStaticStateManager, "<init>", "()V");
|
||||
mv.visitMethodInsn(INVOKESPECIAL, tStaticStateManager, "<init>", "()V", false);
|
||||
mv.visitFieldInsn(PUTSTATIC, slashedname, fStaticFieldsName, lStaticStateManager);
|
||||
mv.visitLabel(l1);
|
||||
}
|
||||
|
||||
@@ -45,12 +45,12 @@ import org.objectweb.asm.tree.FieldNode;
|
||||
import org.springsource.loaded.Utils.ReturnType.Kind;
|
||||
|
||||
|
||||
// TODO debugging tests - how is the experience? rewriting of field accesses will really
|
||||
// TODO debugging tests - how is the experience? rewriting of field accesses will really
|
||||
// affect field navigation in the debugger
|
||||
|
||||
/**
|
||||
* Utility functions for use throughout SpringLoaded
|
||||
*
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
@@ -72,7 +72,7 @@ public class Utils implements Opcodes, Constants {
|
||||
|
||||
/**
|
||||
* Convert a number (base10) to base62 encoded string
|
||||
*
|
||||
*
|
||||
* @param number the number to convert
|
||||
* @return the base 62 encoded string
|
||||
*/
|
||||
@@ -90,7 +90,7 @@ public class Utils implements Opcodes, Constants {
|
||||
|
||||
/**
|
||||
* Decode a base62 encoded string into a number (base10). (More expensive than encoding)
|
||||
*
|
||||
*
|
||||
* @param s the string to decode
|
||||
* @return the number
|
||||
*/
|
||||
@@ -104,7 +104,7 @@ public class Utils implements Opcodes, Constants {
|
||||
|
||||
/**
|
||||
* Depending on the signature of the return type, add the appropriate instructions to the method visitor.
|
||||
*
|
||||
*
|
||||
* @param mv where to visit to append the instructions
|
||||
* @param returnType return type descriptor
|
||||
* @param createCast whether to include CHECKCAST instructions for return type values
|
||||
@@ -153,7 +153,7 @@ public class Utils implements Opcodes, Constants {
|
||||
|
||||
/**
|
||||
* Return the number of parameters in the descriptor. Copes with primitives and arrays and reference types.
|
||||
*
|
||||
*
|
||||
* @param methodDescriptor a method descriptor of the form (Ljava/lang/String;I[[Z)I
|
||||
* @return number of parameters in the descriptor
|
||||
*/
|
||||
@@ -183,7 +183,7 @@ public class Utils implements Opcodes, Constants {
|
||||
|
||||
/**
|
||||
* Create the set of LOAD instructions to load the method parameters. Take into account the size and type.
|
||||
*
|
||||
*
|
||||
* @param mv the method visitor to recieve the load instructions
|
||||
* @param descriptor the complete method descriptor (eg. "(ILjava/lang/String;)V") - params and return type are
|
||||
* skipped
|
||||
@@ -310,7 +310,7 @@ public class Utils implements Opcodes, Constants {
|
||||
/**
|
||||
* Return a simple sequence for the descriptor where type references are collapsed to 'O', so
|
||||
* (IILjava/lang/String;Z) will return IIOZ.
|
||||
*
|
||||
*
|
||||
* @param descriptor method descriptor, for example (IILjava/lang/String;Z)V
|
||||
* @return sequence where all parameters are represented by a single character - or null if no parameters
|
||||
*/
|
||||
@@ -595,7 +595,7 @@ public class Utils implements Opcodes, Constants {
|
||||
|
||||
/**
|
||||
* Create a descriptor for some set of parameter types. The descriptor will be of the form "([Ljava/lang/String;)"
|
||||
*
|
||||
*
|
||||
* @param params the (possibly null) list of parameters for which to create the descriptor
|
||||
* @return a descriptor or "()" for no parameters
|
||||
*/
|
||||
@@ -614,7 +614,7 @@ public class Utils implements Opcodes, Constants {
|
||||
/**
|
||||
* Given a method descriptor, extract the parameter descriptor and convert into corresponding Class objects. This
|
||||
* requires a reference to a class loader to convert type names into Class objects.
|
||||
*
|
||||
*
|
||||
* @param methodDescriptor a method descriptor (e.g (Ljava/lang/String;)I)
|
||||
* @param classLoader a class loader that can be used to lookup types
|
||||
* @return an array for classes representing the types in the method descriptor
|
||||
@@ -633,7 +633,7 @@ public class Utils implements Opcodes, Constants {
|
||||
/**
|
||||
* Convert an asm Type into a corresponding Class object, requires a reference to a ClassLoader to be able to
|
||||
* convert classnames to class objects.
|
||||
*
|
||||
*
|
||||
* @param type the asm Type
|
||||
* @param classLoader a class loader that can be used to find types
|
||||
* @return the JVM Class for the type
|
||||
@@ -672,7 +672,7 @@ public class Utils implements Opcodes, Constants {
|
||||
* Construct the method descriptor for a method. For example 'String bar(int)' would return '(I)Ljava/lang/String;'.
|
||||
* If the first parameter is skipped, the leading '(' is also skipped (the caller is expect to build the right
|
||||
* prefix).
|
||||
*
|
||||
*
|
||||
* @param method method for which the descriptor should be created
|
||||
* @param ignoreFirstParameter whether to include the first parameter in the output descriptor
|
||||
* @return a method descriptor
|
||||
@@ -744,7 +744,7 @@ public class Utils implements Opcodes, Constants {
|
||||
|
||||
/**
|
||||
* Create the string representation of an integer and pad it to a particular width using leading zeroes.
|
||||
*
|
||||
*
|
||||
* @param value the value to convert to a string
|
||||
* @param width the width (in chars) that the resultant string should be
|
||||
* @return the padded string
|
||||
@@ -761,7 +761,7 @@ public class Utils implements Opcodes, Constants {
|
||||
/**
|
||||
* Access the specified class as a resource accessible through the specified loader and return the bytes. The
|
||||
* classname should be 'dot' separated (eg. com.foo.Bar) and not suffixed .class
|
||||
*
|
||||
*
|
||||
* @param loader the classloader against which getResourceAsStream() will be invoked
|
||||
* @param dottedclassname the dot separated classname without .class suffix
|
||||
* @return the byte data defining that class
|
||||
@@ -785,7 +785,7 @@ public class Utils implements Opcodes, Constants {
|
||||
/**
|
||||
* Access the specified class as a resource accessible through the specified loader and return the bytes. The
|
||||
* classname should be 'dot' separated (eg. com.foo.Bar) and not suffixed .class
|
||||
*
|
||||
*
|
||||
* @param loader the classloader against which getResourceAsStream() will be invoked
|
||||
* @param slashedclassname the dot separated classname without .class suffix
|
||||
* @return the byte data defining that class
|
||||
@@ -833,8 +833,8 @@ public class Utils implements Opcodes, Constants {
|
||||
|
||||
/**
|
||||
* Load all the byte data from an input stream.
|
||||
*
|
||||
* @param stream thr input stream from which to read
|
||||
*
|
||||
* @param stream the input stream from which to read
|
||||
* @return a byte array containing all the data from the stream
|
||||
*/
|
||||
public static byte[] loadBytesFromStream(InputStream stream) {
|
||||
@@ -876,7 +876,7 @@ public class Utils implements Opcodes, Constants {
|
||||
* Generate the name for the executor class. Must use '$' so that it is considered by some code (eclipse debugger
|
||||
* for example) to be an inner type of the original class (thus able to consider itself as being from the same
|
||||
* source file).
|
||||
*
|
||||
*
|
||||
* @param name the name prefix for the executor class
|
||||
* @param versionstamp the suffix string for the executor class name
|
||||
* @return an executor class name
|
||||
@@ -892,7 +892,7 @@ public class Utils implements Opcodes, Constants {
|
||||
* Strip the first parameter out of a method descriptor and return the shortened method descriptor. Since primitive
|
||||
* types cannot be reloadable, there is no need to handle that case - it should always be true that the first
|
||||
* parameter will exist and will end with a semi-colon. For example: (Ljava/lang/String;II)V becomes (IIV)
|
||||
*
|
||||
*
|
||||
* @param descriptor method descriptor to be shortened
|
||||
* @return new version of input descriptor with first parameter taken out
|
||||
*/
|
||||
@@ -911,7 +911,7 @@ public class Utils implements Opcodes, Constants {
|
||||
/**
|
||||
* Discover the descriptor for the return type. It may be a primitive (so one char) or a reference type (so a/b/c,
|
||||
* with no 'L' or ';') or it may be an array descriptor ([Ljava/lang/String;).
|
||||
*
|
||||
*
|
||||
* @param methodDescriptor method descriptor
|
||||
* @return return type descriptor (with any 'L' or ';' trimmed off)
|
||||
*/
|
||||
@@ -952,7 +952,7 @@ public class Utils implements Opcodes, Constants {
|
||||
|
||||
/**
|
||||
* Descriptor for a reference type has already been stripped of L and ;
|
||||
*
|
||||
*
|
||||
* @param descriptor descriptor, either one char for a primitive or slashed name for a reference type or
|
||||
* [La/b/c; for array type
|
||||
* @param kind one of primitive, array or reference
|
||||
@@ -1052,7 +1052,7 @@ public class Utils implements Opcodes, Constants {
|
||||
* Generate the instructions in the specified method visitor to unpack an assumed array (on top of the stack)
|
||||
* according to the specified descriptor. For example, if the descriptor is (I)V then the array contains a single
|
||||
* Integer that must be unloaded from the array then unboxed to an int.
|
||||
*
|
||||
*
|
||||
* @param mv the method visitor to receive the unpack instructions
|
||||
* @param toCallDescriptor the descriptor for the method whose parameters describe the array contents
|
||||
* @param arrayVariableIndex index of the array variable
|
||||
@@ -1127,7 +1127,7 @@ public class Utils implements Opcodes, Constants {
|
||||
/**
|
||||
* Dump the specified bytes under the specified name in the filesystem. If the location hasn't been configured then
|
||||
* File.createTempFile() is used to determine where the file will be put.
|
||||
*
|
||||
*
|
||||
* @param slashname the slashed class name (e.g. java/lang/String)
|
||||
* @param bytes the bytes to dump
|
||||
* @return the path to the file
|
||||
@@ -1172,7 +1172,7 @@ public class Utils implements Opcodes, Constants {
|
||||
/**
|
||||
* Return the size of a type. The size is usually 1 except for double and long which are of size 2. The descriptor
|
||||
* passed in is the full descriptor, including any leading 'L' and trailing ';'.
|
||||
*
|
||||
*
|
||||
* @param typeDescriptor the descriptor for a single type, may be primitive. For example: I, J, Z,
|
||||
* Ljava/lang/String;
|
||||
* @return the size of the descriptor (number of slots it will consume), either 1 or 2
|
||||
@@ -1192,7 +1192,7 @@ public class Utils implements Opcodes, Constants {
|
||||
|
||||
/**
|
||||
* Dump some bytes into the specified file.
|
||||
*
|
||||
*
|
||||
* @param file full filename for where to dump the stuff (e.g. c:/temp/Foo.class)
|
||||
* @param bytes the bytes to write to the file
|
||||
*/
|
||||
@@ -1226,7 +1226,7 @@ public class Utils implements Opcodes, Constants {
|
||||
|
||||
/**
|
||||
* Compute the size required for a specific method descriptor.
|
||||
*
|
||||
*
|
||||
* @param descriptor a method descriptor, for example (Ljava/lang/String;ZZ)V
|
||||
* @return number of stack/var entries necessary for that descriptor
|
||||
*/
|
||||
@@ -1391,7 +1391,7 @@ public class Utils implements Opcodes, Constants {
|
||||
|
||||
/**
|
||||
* Load the contents of an input stream.
|
||||
*
|
||||
*
|
||||
* @param stream input stream that contains the bytes to load
|
||||
* @return the byte array loaded from the input stream
|
||||
*/
|
||||
@@ -1427,10 +1427,10 @@ public class Utils implements Opcodes, Constants {
|
||||
|
||||
/**
|
||||
* If the flags indicate it is not public, private or protected, then it is default and make it public.
|
||||
*
|
||||
*
|
||||
* Default visibility needs promoting because package visibility is determined by classloader+package, not just
|
||||
* package.
|
||||
*
|
||||
*
|
||||
* @param access incoming access modifiers
|
||||
* @return adjusted modifiers
|
||||
*/
|
||||
@@ -1439,9 +1439,9 @@ public class Utils implements Opcodes, Constants {
|
||||
// is default
|
||||
return (access | Modifier.PUBLIC);
|
||||
}
|
||||
if ((access & Constants.ACC_PROTECTED) != 0) {
|
||||
if ((access & Opcodes.ACC_PROTECTED) != 0) {
|
||||
// was protected, need to 'publicize' it
|
||||
return access - Constants.ACC_PROTECTED + Constants.ACC_PUBLIC;
|
||||
return access - Opcodes.ACC_PROTECTED + Opcodes.ACC_PUBLIC;
|
||||
}
|
||||
// if ((access & Constants.ACC_PRIVATE) != 0) {
|
||||
// // was private, need to 'publicize' it
|
||||
@@ -1455,18 +1455,18 @@ public class Utils implements Opcodes, Constants {
|
||||
// is default
|
||||
return (access | Modifier.PUBLIC);
|
||||
}
|
||||
if ((access & Constants.ACC_PROTECTED) != 0) {
|
||||
if ((access & Opcodes.ACC_PROTECTED) != 0) {
|
||||
// was protected, need to 'publicize' it
|
||||
return access - Constants.ACC_PROTECTED + Constants.ACC_PUBLIC;
|
||||
return access - Opcodes.ACC_PROTECTED + Opcodes.ACC_PUBLIC;
|
||||
}
|
||||
if (isEnum && (access & Constants.ACC_PRIVATE) != 0) {
|
||||
if (isEnum && (access & Opcodes.ACC_PRIVATE) != 0) {
|
||||
// was private, need to 'publicize' it
|
||||
return access - Constants.ACC_PRIVATE + Constants.ACC_PUBLIC;
|
||||
return access - Opcodes.ACC_PRIVATE + Opcodes.ACC_PUBLIC;
|
||||
}
|
||||
if ((access & Constants.ACC_PRIVATE_STATIC_SYNTHETIC) == ACC_PRIVATE_STATIC_SYNTHETIC
|
||||
&& name.startsWith("lambda")) {
|
||||
// Special case for lambda, may need to generalize for general invokedynamic support
|
||||
return access - Constants.ACC_PRIVATE + Constants.ACC_PUBLIC;
|
||||
return access - Opcodes.ACC_PRIVATE + Opcodes.ACC_PUBLIC;
|
||||
}
|
||||
return access;
|
||||
}
|
||||
@@ -1476,9 +1476,9 @@ public class Utils implements Opcodes, Constants {
|
||||
// is default
|
||||
return (access | Modifier.PUBLIC);
|
||||
}
|
||||
if ((access & Constants.ACC_PROTECTED) != 0) {
|
||||
if ((access & Opcodes.ACC_PROTECTED) != 0) {
|
||||
// was protected, need to 'publicize' it
|
||||
return access - Constants.ACC_PROTECTED + Constants.ACC_PUBLIC;
|
||||
return access - Opcodes.ACC_PROTECTED + Opcodes.ACC_PUBLIC;
|
||||
}
|
||||
// if ((access & Constants.ACC_PRIVATE) != 0) {
|
||||
// // was private, need to 'publicize' it
|
||||
@@ -1489,7 +1489,7 @@ public class Utils implements Opcodes, Constants {
|
||||
|
||||
/**
|
||||
* Utility method similar to Java 1.6 Arrays.copyOf, used instead of that method to stick to Java 1.5 only API.
|
||||
*
|
||||
*
|
||||
* @param <T> the type of the array entries
|
||||
* @param array the array to copy
|
||||
* @param newSize the size of the new array
|
||||
@@ -1504,7 +1504,7 @@ public class Utils implements Opcodes, Constants {
|
||||
|
||||
/**
|
||||
* Modify visibility to be public.
|
||||
*
|
||||
*
|
||||
* @param access existing access
|
||||
* @return modified access, adjusted to public non-final
|
||||
*/
|
||||
@@ -1556,7 +1556,7 @@ public class Utils implements Opcodes, Constants {
|
||||
* Convert a value to the requested descriptor. For null values where the caller needs a primitive, this returns the
|
||||
* appropriate (boxed) default. This method will not attempt conversion, it is basically checking what to do if the
|
||||
* result is null - and ensuring the caller gets back what they expect (the appropriate primitive default).
|
||||
*
|
||||
*
|
||||
* @param value the value
|
||||
* @param desc the type the caller would like it to be
|
||||
* @return the converted value or possibly a default value for the type if the incoming value is null
|
||||
@@ -1598,7 +1598,7 @@ public class Utils implements Opcodes, Constants {
|
||||
* Check that the value we have discovered is of the right type. It may not be if the field has changed type during
|
||||
* a reload. When this happens we will default the value for the new field and forget the one we were holding onto.
|
||||
* note: array forms are not compatible (e.g. int[] and Integer[])
|
||||
*
|
||||
*
|
||||
* @param registry the type registry that can be quizzed for type information
|
||||
* @param result the result we have discovered and are about to return - this is never null
|
||||
* @param expectedTypeDescriptor the type we are looking for (will be primitive or Ljava/lang/String style)
|
||||
@@ -1654,7 +1654,7 @@ public class Utils implements Opcodes, Constants {
|
||||
/*
|
||||
* Determine if the type specified in lookingFor is a supertype (class/interface) of the specified typedescriptor, i.e. can an
|
||||
* object of type 'candidate' be assigned to a variable of typ 'lookingFor'.
|
||||
*
|
||||
*
|
||||
* @return true if it is a supertype
|
||||
*/
|
||||
public static boolean isAssignableFrom(String lookingFor, TypeDescriptor candidate) {
|
||||
@@ -1680,7 +1680,7 @@ public class Utils implements Opcodes, Constants {
|
||||
|
||||
/*
|
||||
* Produce the bytecode that will collapse the stack entries into an array - the descriptor describes what is being packed.
|
||||
*
|
||||
*
|
||||
* @param mv the method visitor to receive the instructions to package the data
|
||||
* @param desc the descriptor for the method that shows (through its parameters) the contents of the stack
|
||||
*/
|
||||
@@ -1750,7 +1750,7 @@ public class Utils implements Opcodes, Constants {
|
||||
/**
|
||||
* Looks at the supplied descriptor and inserts enough pops to remove all parameters. Should be used when about to
|
||||
* avoid a method call.
|
||||
*
|
||||
*
|
||||
* @param mv the method visitor to append instructions to
|
||||
* @param desc the method descriptor for the parameter sequence (e.g. (Ljava/lang/String;IZZ)V)
|
||||
* @return number of parameters popped
|
||||
@@ -1834,7 +1834,7 @@ public class Utils implements Opcodes, Constants {
|
||||
|
||||
/**
|
||||
* Determine the interfaces implemented by a given class (supplied as bytes)
|
||||
*
|
||||
*
|
||||
* @param classbytes the classfile bytes
|
||||
* @return array of interface names (slashed descriptors)
|
||||
*/
|
||||
@@ -1855,34 +1855,43 @@ public class Utils implements Opcodes, Constants {
|
||||
|
||||
public String[] interfaces;
|
||||
|
||||
@Override
|
||||
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
|
||||
this.interfaces = interfaces;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitSource(String source, String debug) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitOuterClass(String owner, String name, String desc) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitAttribute(Attribute attr) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitInnerClass(String name, String outerName, String innerName, int access) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitEnd() {
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ import org.springsource.loaded.TypeRegistry;
|
||||
|
||||
/**
|
||||
* Class pre-processor.
|
||||
*
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
|
||||
@@ -19,13 +19,11 @@ package org.springsource.loaded.agent;
|
||||
import java.beans.BeanInfo;
|
||||
import java.beans.Introspector;
|
||||
import java.lang.ref.Reference;
|
||||
import java.lang.ref.ReferenceQueue;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.security.ProtectionDomain;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import org.springsource.loaded.GlobalConfiguration;
|
||||
@@ -37,7 +35,7 @@ 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
|
||||
*/
|
||||
@@ -109,7 +107,7 @@ public class JVMPlugin implements ReloadEventProcessorPlugin, LoadtimeInstrument
|
||||
// /** queue for WeakReferences to field reflectors keys */
|
||||
// private static final ReferenceQueue<Class<?>> reflectorsQueue =
|
||||
// new ReferenceQueue<>();
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
@@ -129,7 +127,7 @@ public class JVMPlugin implements ReloadEventProcessorPlugin, LoadtimeInstrument
|
||||
beanInfoCacheCleared = clearThreadGroupContext(clazz);
|
||||
}
|
||||
|
||||
// GRAILS-9505 - had to introduce the flushFromCaches(). The appcontext we seem to be able to
|
||||
// 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
|
||||
@@ -157,7 +155,7 @@ public class JVMPlugin implements ReloadEventProcessorPlugin, LoadtimeInstrument
|
||||
// if (map != null) {
|
||||
// if (GlobalConfiguration.debugplugins) {
|
||||
// System.err.println("JVMPlugin: clearing out BeanInfo for " + clazz.getName());
|
||||
// }
|
||||
// }
|
||||
// map.remove(clazz);
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -17,10 +17,16 @@
|
||||
package org.springsource.loaded.agent;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
import org.springsource.loaded.FileChangeListener;
|
||||
import org.springsource.loaded.GlobalConfiguration;
|
||||
@@ -29,7 +35,7 @@ import org.springsource.loaded.TypeRegistry;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
*/
|
||||
@@ -41,6 +47,23 @@ public class ReloadableFileChangeListener implements FileChangeListener {
|
||||
|
||||
private Map<File, ReloadableType> correspondingReloadableTypes = new HashMap<File, ReloadableType>();
|
||||
|
||||
Map<File, Set<JarEntry>> watchedJarContents = new HashMap<File, Set<JarEntry>>();
|
||||
|
||||
static class JarEntry {
|
||||
|
||||
final ReloadableType rtype;
|
||||
|
||||
final String slashname;
|
||||
|
||||
long lmt;
|
||||
|
||||
public JarEntry(ReloadableType rtype, String slashname, long lmt) {
|
||||
this.rtype = rtype;
|
||||
this.slashname = slashname;
|
||||
this.lmt = lmt;
|
||||
}
|
||||
}
|
||||
|
||||
public ReloadableFileChangeListener(TypeRegistry typeRegistry) {
|
||||
this.typeRegistry = typeRegistry;
|
||||
}
|
||||
@@ -50,11 +73,65 @@ public class ReloadableFileChangeListener implements FileChangeListener {
|
||||
log.info(" processing change for " + file);
|
||||
}
|
||||
ReloadableType rtype = correspondingReloadableTypes.get(file);
|
||||
typeRegistry.loadNewVersion(rtype, file);
|
||||
if (file.getName().endsWith(".jar")) {
|
||||
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) {
|
||||
log.info(" processing change for JAR " + file);
|
||||
}
|
||||
try {
|
||||
ZipFile zf = new ZipFile(file);
|
||||
Set<JarEntry> entriesBeingWatched = watchedJarContents.get(file);
|
||||
for (JarEntry entryBeingWatched : entriesBeingWatched) {
|
||||
ZipEntry ze = zf.getEntry(entryBeingWatched.slashname);
|
||||
long lmt = ze.getLastModifiedTime().toMillis();
|
||||
if (lmt > entryBeingWatched.lmt) {
|
||||
// entry in jar has been updated
|
||||
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) {
|
||||
log.info(" detected update to jar entry. jar=" + file.getName() + " class="
|
||||
+ entryBeingWatched.slashname + " OLD LMT=" + new Date(entryBeingWatched.lmt)
|
||||
+ " NEW LMT=" + new Date(lmt));
|
||||
}
|
||||
typeRegistry.loadNewVersion(entryBeingWatched.rtype, lmt, zf.getInputStream(ze));
|
||||
entryBeingWatched.lmt = lmt;
|
||||
}
|
||||
}
|
||||
zf.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
else {
|
||||
typeRegistry.loadNewVersion(rtype, file);
|
||||
}
|
||||
}
|
||||
|
||||
public void register(ReloadableType rtype, File file) {
|
||||
correspondingReloadableTypes.put(file, rtype);
|
||||
if (file.getName().endsWith(".jar")) {
|
||||
// Compute the last mod time of the entry in the jar
|
||||
try {
|
||||
ZipFile zf = new ZipFile(file);
|
||||
String slashname = rtype.getSlashedName() + ".class";
|
||||
ZipEntry ze = zf.getEntry(slashname);
|
||||
long lmt = ze.getLastModifiedTime().toMillis();
|
||||
JarEntry je = new JarEntry(rtype, slashname, lmt);
|
||||
zf.close();
|
||||
Set<JarEntry> jarEntries = watchedJarContents.get(file);
|
||||
if (jarEntries == null) {
|
||||
jarEntries = new HashSet<JarEntry>();
|
||||
watchedJarContents.put(file, jarEntries);
|
||||
}
|
||||
jarEntries.add(je);
|
||||
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.INFO)) {
|
||||
log.info(" watching jar file entry. Jar=" + file + " file=" + rtype.getSlashedName() + " lmt="
|
||||
+ lmt);
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
else {
|
||||
correspondingReloadableTypes.put(file, rtype);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -606,11 +606,23 @@ public class SpringLoadedPreProcessor implements Constants {
|
||||
// great! nothing to do
|
||||
}
|
||||
else if (file.getName().endsWith(".jar")) {
|
||||
if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.WARNING)) {
|
||||
boolean found = false;
|
||||
if (GlobalConfiguration.jarsToWatch != null) {
|
||||
// Check if it is one to watch
|
||||
String candidate = file.getName();
|
||||
for (String jarToWatch : GlobalConfiguration.jarsToWatch) {
|
||||
if (candidate.equals(jarToWatch)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!found && GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.WARNING)) {
|
||||
log.warning("unable to watch this jar file entry: " + slashedClassName.replace('/', '.')
|
||||
+ ". Computed location=" + file.toString());
|
||||
}
|
||||
return null;
|
||||
if (!found)
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user