Various changes:
- Fix for GRAILS-10411 (super dispatchers) - Better forked JVM test harness - Work in progress on improved logging/explain mode
This commit is contained in:
@@ -150,4 +150,5 @@ public interface Constants extends Opcodes {
|
||||
static final String jlcgms = "__sljlcgms";
|
||||
static final String jlcgmsDescriptor = "(Ljava/lang/Class;)[Ljava/lang/reflect/Method;";
|
||||
|
||||
static final String methodSuffixSuperDispatcher = "_$superdispatcher$";
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ public class DispatcherBuilder {
|
||||
}
|
||||
|
||||
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
|
||||
if (MethodMember.isCatcher(method) || MethodMember.isSuperDispatcher(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?
|
||||
}
|
||||
|
||||
@@ -25,11 +25,10 @@ import java.util.logging.Logger;
|
||||
|
||||
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. 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.
|
||||
* 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
|
||||
@@ -59,6 +58,12 @@ public class GlobalConfiguration {
|
||||
* verbose mode can trigger extra messages. Enable with 'verbose=true'
|
||||
*/
|
||||
public static boolean verboseMode = false;
|
||||
|
||||
/**
|
||||
* Can be turned on to enable users to determine the decision process around why
|
||||
* something is not reloadable.
|
||||
*/
|
||||
public static boolean explainMode = false;
|
||||
|
||||
/**
|
||||
* Global control for runtime logging
|
||||
@@ -263,9 +268,13 @@ public class GlobalConfiguration {
|
||||
printUsage();
|
||||
}
|
||||
else if (kv.equals("verbose")) {
|
||||
Log.log("verbose mode on, configuration is:"+value);
|
||||
Log.log("[verbose mode on] Full configuration is:"+value);
|
||||
verboseMode = true;
|
||||
}
|
||||
else if (kv.equals("explain")) {
|
||||
Log.log("[explain mode on] Reporting on the decision making process within SpringLoaded");
|
||||
explainMode = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,6 +120,12 @@ public class IncrementalTypeDescriptor implements Constants {
|
||||
latest.bits |= MethodMember.IS_NEW;
|
||||
newOrChangedMethods.add(latest);
|
||||
}
|
||||
// TODO [perf] not convinced this can occur? Think it through
|
||||
if (MethodMember.isSuperDispatcher(original) && !MethodMember.isSuperDispatcher(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;
|
||||
|
||||
@@ -95,6 +95,11 @@ class MethodCopier extends MethodAdapter implements Constants {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private TypeDescriptor getType(String type) {
|
||||
TypeDescriptor typeDescriptor = this.typeDescriptor.getTypeRegistry().getDescriptorFor(type);
|
||||
return typeDescriptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitFieldInsn(final int opcode, final String owner, final String name, final String desc) {
|
||||
@@ -125,24 +130,42 @@ class MethodCopier extends MethodAdapter implements Constants {
|
||||
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 (opcode == INVOKESPECIAL && name.charAt(0) != '<' && !name.startsWith("r$")) {
|
||||
if (owner.equals(classname)) {
|
||||
// private method call
|
||||
// leaving the invokespecial alone will cause a verify error
|
||||
String descriptor = Utils.insertExtraParameter(owner, desc);
|
||||
super.visitMethodInsn(INVOKESTATIC, Utils.getExecutorName(classname, suffix), name, descriptor);
|
||||
return;
|
||||
} else {
|
||||
// super call
|
||||
// TODO Check if this is true: we can just call the catcher directly if there was one, there is no need
|
||||
// for a superdispatcher
|
||||
|
||||
// Only need to redirect to the superdispatcher if it was a protected method
|
||||
TypeDescriptor supertypeDescriptor = getType(owner);
|
||||
MethodMember target = supertypeDescriptor.getByNameAndDescriptor(name+desc);
|
||||
if (target!=null && target.isProtected()) {
|
||||
// A null target means that method is not in the supertype, so didn't get a superdispatcher
|
||||
super.visitMethodInsn(INVOKESPECIAL,classname,name+methodSuffixSuperDispatcher,desc);
|
||||
} else {
|
||||
super.visitMethodInsn(opcode, owner, name, desc);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!done) {
|
||||
super.visitMethodInsn(opcode, owner, name, desc);
|
||||
}
|
||||
// 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
|
||||
|
||||
@@ -36,9 +36,9 @@ public class MethodMember extends AbstractMember {
|
||||
// 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;
|
||||
public final static int BIT_SUPERDISPATCHER = 0x0008;
|
||||
|
||||
// computed on incremental members to indicate what changed:
|
||||
public final static int MADE_STATIC = 0x0010;
|
||||
@@ -170,6 +170,26 @@ public class MethodMember extends AbstractMember {
|
||||
copy.bits |= MethodMember.BIT_CATCHER;
|
||||
return copy;
|
||||
}
|
||||
|
||||
public MethodMember superDispatcherFor() {
|
||||
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+"_$superdispatcher$", descriptor, signature, exceptions);
|
||||
copy.bits |= MethodMember.BIT_SUPERDISPATCHER;
|
||||
return copy;
|
||||
}
|
||||
|
||||
public MethodMember catcherCopyOfWithAbstractRemoved() {
|
||||
int newModifiers = modifiers & ~(Modifier.NATIVE | Modifier.ABSTRACT);
|
||||
@@ -221,6 +241,10 @@ public class MethodMember extends AbstractMember {
|
||||
public static boolean isClash(MethodMember method) {
|
||||
return (method.bits & MethodMember.BIT_CLASH) != 0;
|
||||
}
|
||||
|
||||
public static boolean isSuperDispatcher(MethodMember method) {
|
||||
return (method.bits & BIT_SUPERDISPATCHER) != 0;
|
||||
}
|
||||
|
||||
public static boolean isCatcher(MethodMember method) {
|
||||
return (method.bits & BIT_CATCHER) != 0;
|
||||
@@ -242,6 +266,9 @@ public class MethodMember extends AbstractMember {
|
||||
if ((bits & BIT_CLASH) != 0) {
|
||||
s.append("clash ");
|
||||
}
|
||||
if ((bits & BIT_SUPERDISPATCHER) != 0) {
|
||||
s.append("superdispatcher ");
|
||||
}
|
||||
if ((bits & MADE_STATIC) != 0) {
|
||||
s.append("made_static ");
|
||||
}
|
||||
|
||||
@@ -41,9 +41,8 @@ import org.springsource.loaded.infra.UsedByGeneratedCode;
|
||||
import org.springsource.loaded.ri.Invoker;
|
||||
import org.springsource.loaded.ri.JavaMethodCache;
|
||||
|
||||
|
||||
/**
|
||||
* Represents a type that is reloadable.
|
||||
* Represents a type that has been processed such that it can be reloaded at runtime.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.5.0
|
||||
@@ -51,7 +50,9 @@ import org.springsource.loaded.ri.JavaMethodCache;
|
||||
public class ReloadableType {
|
||||
|
||||
// TODO when a field is shadowed or renamed and the old one never accessed again, it may be holding onto something and prevent it from GC.
|
||||
// Thinking about a solution that involves a tag in the FieldAccessor object so that we can check whether a 'repair' is needed on a field accessor (because the type has been reloaded and the map in the accessor hasnt been repaired yet)
|
||||
// Thinking about a solution that involves a tag in the FieldAccessor object so that we can
|
||||
// check whether a 'repair' is needed on a field accessor (because the type has been reloaded and
|
||||
// the map in the accessor hasnt been repaired yet)
|
||||
private static Logger log = Logger.getLogger(ReloadableType.class.getName());
|
||||
|
||||
/** The registry maintaining this reloadable type */
|
||||
@@ -142,6 +143,9 @@ public class ReloadableType {
|
||||
Utils.assertDotted(dottedtypename);
|
||||
}
|
||||
this.id = id;
|
||||
if (GlobalConfiguration.verboseMode && log.isLoggable(Level.INFO)) {
|
||||
log.info("New reloadable type: "+dottedtypename+ " (allocatedId="+id+") "+typeRegistry.toString());
|
||||
}
|
||||
this.typeRegistry = typeRegistry;
|
||||
this.dottedtypename = dottedtypename;
|
||||
this.slashedtypename = dottedtypename.replace('.', '/');
|
||||
@@ -269,12 +273,9 @@ public class ReloadableType {
|
||||
*/
|
||||
public boolean loadNewVersion(String versionsuffix, byte[] newbytedata) {
|
||||
javaMethodCache = null;
|
||||
// int size = newbytedata.length;
|
||||
// InputStream is = typeRegistry.getClassLoader().getResourceAsStream(this.slashedtypename + ".class");
|
||||
// byte[] bs = Utils.loadFromStream(is);
|
||||
//
|
||||
// System.out.println(">> loadNewVersion " + versionsuffix + " bytesin=" + size
|
||||
// + " bytesdiscovered through getResourceAsStream" + bs.length);
|
||||
if (log.isLoggable(Level.INFO)) {
|
||||
log.info("Loading new version of "+slashedtypename+", identifying suffix "+versionsuffix+", new data length is "+newbytedata.length+"bytes");
|
||||
}
|
||||
|
||||
// If we find our parent classloader has a weavingTransformer
|
||||
newbytedata = retransform(newbytedata);
|
||||
@@ -1023,7 +1024,8 @@ public class ReloadableType {
|
||||
// Did the type originally define it:
|
||||
MethodMember[] mms = rtype.getTypeDescriptor().getMethods();
|
||||
for (MethodMember mm : mms) {
|
||||
if (mm.getNameAndDescriptor().equals(nameAndDescriptor) && !MethodMember.isCatcher(mm)) {
|
||||
// TODO don't need superdispatcher check, name won't match will it...
|
||||
if (mm.getNameAndDescriptor().equals(nameAndDescriptor) && !MethodMember.isCatcher(mm) && !MethodMember.isSuperDispatcher(mm)) {
|
||||
// the original version does implement it
|
||||
found = true;
|
||||
break;
|
||||
|
||||
@@ -18,9 +18,6 @@ package org.springsource.loaded;
|
||||
/**
|
||||
* API for directly interacting with SpringLoaded.
|
||||
*
|
||||
* <p>
|
||||
* tag: API
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 0.8.0
|
||||
*/
|
||||
@@ -30,12 +27,12 @@ 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
|
||||
* @param newbytes 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);
|
||||
public static int loadNewVersionOfType(Class<?> clazz, byte[] newbytes) {
|
||||
return loadNewVersionOfType(clazz.getClassLoader(), clazz.getName(), newbytes);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -43,11 +40,11 @@ public class SpringLoaded {
|
||||
*
|
||||
* @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
|
||||
* @param newbytes 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) {
|
||||
public static int loadNewVersionOfType(ClassLoader classLoader, String dottedClassname, byte[] newbytes) {
|
||||
try {
|
||||
// Obtain the type registry of interest
|
||||
TypeRegistry typeRegistry = TypeRegistry.getTypeRegistryFor(classLoader);
|
||||
@@ -61,7 +58,7 @@ public class SpringLoaded {
|
||||
}
|
||||
// Create a unique version tag for this reload attempt
|
||||
String tag = Utils.encode(System.currentTimeMillis());
|
||||
boolean reloaded = reloadableType.loadNewVersion(tag, newbytedata);
|
||||
boolean reloaded = reloadableType.loadNewVersion(tag, newbytes);
|
||||
return reloaded ? 0 : 3;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
|
||||
@@ -74,7 +74,7 @@ public class TypeDescriptorExtractor {
|
||||
|
||||
public TypeDescriptor getTypeDescriptor() {
|
||||
if (isReloadableType) {
|
||||
computeCatchers();
|
||||
computeCatchersAndSuperdispatchers();
|
||||
}
|
||||
computeFieldsRequiringAccessors();
|
||||
computeClashes();
|
||||
@@ -141,7 +141,7 @@ public class TypeDescriptorExtractor {
|
||||
* 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() {
|
||||
private void computeCatchersAndSuperdispatchers() {
|
||||
// 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>();
|
||||
@@ -151,6 +151,7 @@ public class TypeDescriptorExtractor {
|
||||
if (Modifier.isInterface(this.flags)) {
|
||||
return;
|
||||
}
|
||||
List<String> superDispatcherAddedFor = new ArrayList<String>();
|
||||
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
|
||||
@@ -158,6 +159,14 @@ public class TypeDescriptorExtractor {
|
||||
// permgen, and simplification of stack traces
|
||||
// if (!supertypeDescriptor.isReloadable()) {
|
||||
for (MethodMember method : supertypeDescriptor.getMethods()) {
|
||||
if (shouldCreateSuperDispatcherFor(method) && !superDispatcherAddedFor.contains(method.nameAndDescriptor)) {
|
||||
// need a public super dispatcher - so that we can reach that super method
|
||||
// from a reloaded instance of this type
|
||||
MethodMember superdispatcher = method.superDispatcherFor();
|
||||
methods.add(superdispatcher);
|
||||
superDispatcherAddedFor.add(method.nameAndDescriptor);
|
||||
}
|
||||
|
||||
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
|
||||
@@ -202,6 +211,14 @@ public class TypeDescriptorExtractor {
|
||||
finalInHierarchy.addAll(shouldNotCatch);
|
||||
}
|
||||
|
||||
// TODO should clone and finalize be in here?
|
||||
private boolean shouldCreateSuperDispatcherFor(MethodMember method) {
|
||||
return method.isProtected() && !(
|
||||
(method.getName().equals("finalize") && method.getDescriptor().equals("()V")) ||
|
||||
(method.getName().equals("clone") && method.getDescriptor().equals("()Ljava/lang/Object;")));
|
||||
}
|
||||
|
||||
|
||||
private void addCatchersForNonImplementedMethodsFrom(String interfacename) {
|
||||
TypeDescriptor interfaceDescriptor = findTypeDescriptor(registry, interfacename);
|
||||
for (MethodMember method : interfaceDescriptor.getMethods()) {
|
||||
@@ -257,7 +274,7 @@ public class TypeDescriptorExtractor {
|
||||
* @return true if it should be caught
|
||||
*/
|
||||
private boolean shouldCatchMethod(MethodMember method) {
|
||||
return !(method.isPrivateStaticFinal() || (method.getName().equals("finalize") && method.getDescriptor().equals("()V")));
|
||||
return !(method.isPrivateStaticFinal() || method.getName().endsWith(Constants.methodSuffixSuperDispatcher) || (method.getName().equals("finalize") && method.getDescriptor().equals("()V")));
|
||||
}
|
||||
|
||||
public void visit(int version, int flags, String name, String signature, String superclassName, String[] interfaceNames) {
|
||||
|
||||
@@ -58,16 +58,16 @@ import org.springsource.loaded.infra.UsedByGeneratedCode;
|
||||
* @since 0.5.0
|
||||
*/
|
||||
public class TypeRegistry {
|
||||
|
||||
public static boolean nothingReloaded = true;
|
||||
|
||||
private static Logger log = Logger.getLogger(TypeRegistry.class.getName());
|
||||
|
||||
/**
|
||||
* Types in these packages are not reloadable by default ('inclusions' must be specified to override this default).
|
||||
*/
|
||||
private final static String[][] ignorablePackagePrefixes;
|
||||
|
||||
private static Logger log = Logger.getLogger(TypeRegistry.class.getName());
|
||||
|
||||
// The first time something gets reloaded this is flipped
|
||||
public static boolean nothingReloaded = true;
|
||||
|
||||
static {
|
||||
ignorablePackagePrefixes = new String[26][];
|
||||
ignorablePackagePrefixes['a' - 'a'] = new String[] { "antlr/" };
|
||||
@@ -80,6 +80,7 @@ public class TypeRegistry {
|
||||
}
|
||||
|
||||
// @formatter:off
|
||||
// These classloaders do not get a type registry (do not load reloadable types!)
|
||||
private final static String[] STANDARD_EXCLUDED_LOADERS = new String[] {
|
||||
// TODO DIFF rules for excluding this loader? is it necessary to usually exclude under tcserver?
|
||||
// sun.misc.Launcher$AppClassLoader
|
||||
@@ -109,9 +110,8 @@ public class TypeRegistry {
|
||||
private int maxClassDefinitions;
|
||||
|
||||
/**
|
||||
* Map from a classloader to the type registry created to process reloadable types loaded by it.
|
||||
*
|
||||
* <p>
|
||||
* Map from each classloader to the type registry responsible for that loader.
|
||||
* <p><b>Note:</b>
|
||||
* Notice that this is a WeakHashMap - the keys are 'weak'. That means a reference in the map doesn't prevent GC of the
|
||||
* ClassLoader. Once the ClassLoader is gone we don't need that TypeRegistry any more. It isn't WeakReference<TypeRegistry>
|
||||
* because we do need those things around whilst the ClassLoader is around. Although there is a reference from a ReloadableType
|
||||
@@ -130,6 +130,7 @@ public class TypeRegistry {
|
||||
private Map<String, String> rebasePaths = new HashMap<String, String>();
|
||||
|
||||
private List<String> pluginClassNames = new ArrayList<String>();
|
||||
|
||||
List<Plugin> localPlugins = new ArrayList<Plugin>();
|
||||
|
||||
/**
|
||||
@@ -183,7 +184,7 @@ public class TypeRegistry {
|
||||
* Create a TypeRegistry for a specified classloader. On creation an id number is allocated for the registry which can then be
|
||||
* used as shorthand reference to the registry in rewritten code. A sub-classloader is created to handle loading generated
|
||||
* artifacts - by using a child classloader it can be discarded after a number of reloadings have occurred to recover memory.
|
||||
* This constructor is only used by the factory method getTypeRegistryFor.
|
||||
* This constructor is only used by the factory method getTypeRegistryFor().
|
||||
*/
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
private TypeRegistry(ClassLoader classloader) {
|
||||
@@ -296,15 +297,16 @@ public class TypeRegistry {
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
// TODO cheaper/faster to go up the typeregistry hierarchy?
|
||||
|
||||
// This will not work for a generated class, what should we do in that case?
|
||||
byte[] data = Utils.loadClassAsBytes2(classLoader.get(), slashedname);
|
||||
byte[] data = Utils.loadSlashedClassAsBytes(classLoader.get(), slashedname);
|
||||
// As the caller did not say, we need to work it out:
|
||||
boolean isReloadableType = isReloadableTypeName(slashedname);
|
||||
TypeDescriptor td = extractor.extract(data, isReloadableType);
|
||||
if (isReloadableType) {
|
||||
if (!slashedname.endsWith("Top"))
|
||||
reloadableTypeDescriptorCache.put(slashedname, td);
|
||||
reloadableTypeDescriptorCache.put(slashedname, td);
|
||||
} else {
|
||||
typeDescriptorCache.put(slashedname, td);
|
||||
}
|
||||
@@ -316,7 +318,7 @@ public class TypeRegistry {
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
byte[] data = Utils.loadClassAsBytes2(classLoader.get(), slashedname);
|
||||
byte[] data = Utils.loadSlashedClassAsBytes(classLoader.get(), slashedname);
|
||||
// As the caller did not say, we need to work it out:
|
||||
boolean isReloadableType = isReloadableTypeName(slashedname);
|
||||
TypeDescriptor td = extractor.extract(data, isReloadableType);
|
||||
@@ -574,6 +576,9 @@ public class TypeRegistry {
|
||||
if (candidates != null) {
|
||||
for (String ignorablePackagePrefix : candidates) {
|
||||
if (slashedName.startsWith(ignorablePackagePrefix)) {
|
||||
if (GlobalConfiguration.explainMode && log.isLoggable(Level.INFO)) {
|
||||
log.info("WhyNotReloadable? The type "+slashedName+" is using a package name '"+ignorablePackagePrefix+"' which is considered infrastructure and types within it are not made reloadable");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -664,14 +669,17 @@ public class TypeRegistry {
|
||||
* @return true if the type is reloadable, false otherwise
|
||||
*/
|
||||
public boolean isReloadableTypeName(String slashedName, ProtectionDomain protectionDomain, byte[] bytes) {
|
||||
// if (GlobalConfiguration.isRuntimeLogging && log.isLoggable(Level.FINEST)) {
|
||||
// log.log(Level.FINEST, "> isReloadableTypeName(" + slashedName + ")");
|
||||
// }
|
||||
if (GlobalConfiguration.verboseMode && log.isLoggable(Level.FINER)) {
|
||||
log.finer("entering TypeRegistry.isReloadableTypeName(" + slashedName + ")");
|
||||
}
|
||||
if (GlobalConfiguration.assertsOn) {
|
||||
Utils.assertSlashed(slashedName);
|
||||
}
|
||||
if (GlobalConfiguration.isProfiling) {
|
||||
if (slashedName.startsWith("com/yourkit")) {
|
||||
if (GlobalConfiguration.explainMode && log.isLoggable(Level.FINER)) {
|
||||
log.finer("[explanation] The type "+slashedName+" is considered part of yourkit and is not being made reloadable");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -698,8 +706,14 @@ public class TypeRegistry {
|
||||
for (IsReloadableTypePlugin plugin : SpringLoadedPreProcessor.getIsReloadableTypePlugins()) {
|
||||
ReloadDecision decision = plugin.shouldBeMadeReloadable(this,slashedName, protectionDomain, bytes);
|
||||
if (decision == ReloadDecision.YES) {
|
||||
if (GlobalConfiguration.explainMode && log.isLoggable(Level.FINER)) {
|
||||
log.finer("[explanation] The plugin "+plugin.getClass().getName()+" determined type "+slashedName+" is reloadable");
|
||||
}
|
||||
return true;
|
||||
} else if (decision == ReloadDecision.NO) {
|
||||
if (GlobalConfiguration.explainMode && log.isLoggable(Level.FINER)) {
|
||||
log.finer("[explanation] The plugin "+plugin.getClass().getName()+" determined type "+slashedName+" is not reloadable");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -708,8 +722,14 @@ public class TypeRegistry {
|
||||
// No inclusions, so unless it matches an exclusion, it will be included
|
||||
if (exclusionPatterns.isEmpty()) {
|
||||
if (couldBeReloadable(slashedName)) {
|
||||
if (GlobalConfiguration.explainMode && log.isLoggable(Level.FINER)) {
|
||||
log.finer("[explanation] The class "+slashedName+" is currently considered reloadable. It matches no exclusions, is accessible from this classloader and is not in a jar/zip.");
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
if (GlobalConfiguration.explainMode && log.isLoggable(Level.FINER)) {
|
||||
log.finer("[explanation] The class "+slashedName+" is not going to be treated as reloadable.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
@@ -1146,7 +1166,7 @@ public class TypeRegistry {
|
||||
}
|
||||
// ignore catchers because the dynamic __execute method wont have an implementation of them, we should
|
||||
// just keep looking for the real thing
|
||||
if (method != null && MethodMember.isCatcher(method)) {
|
||||
if (method != null && (MethodMember.isCatcher(method) || MethodMember.isSuperDispatcher(method))) {
|
||||
method = null;
|
||||
}
|
||||
} else {
|
||||
@@ -1204,7 +1224,7 @@ public class TypeRegistry {
|
||||
}
|
||||
// ignore catchers because the dynamic __execute method wont have an implementation of them, we should
|
||||
// just keep looking for the real thing
|
||||
if (m != null && MethodMember.isCatcher(m)) {
|
||||
if (m != null && (MethodMember.isCatcher(m) || MethodMember.isSuperDispatcher(m))) {
|
||||
m = null;
|
||||
}
|
||||
} else {
|
||||
@@ -1526,8 +1546,8 @@ public class TypeRegistry {
|
||||
*/
|
||||
@UsedByGeneratedCode
|
||||
public static ReloadableType getReloadableType(int typeRegistryId, int typeId) {
|
||||
if (GlobalConfiguration.logging && log.isLoggable(Level.INFO)) {
|
||||
log.info("> TypeRegistry.getReloadableType(" + typeRegistryId + "," + typeId + ")");
|
||||
if (GlobalConfiguration.verboseMode && log.isLoggable(Level.INFO)) {
|
||||
log.info(">TypeRegistry.getReloadableType(typeRegistryId=" + typeRegistryId + ",typeId=" + typeId + ")");
|
||||
}
|
||||
TypeRegistry typeRegistry = registryInstances[typeRegistryId].get();
|
||||
if (typeRegistry == null) {
|
||||
@@ -1536,20 +1556,21 @@ public class TypeRegistry {
|
||||
}
|
||||
ReloadableType reloadableType = typeRegistry.getReloadableType(typeId);
|
||||
if (reloadableType == null) {
|
||||
throw new IllegalStateException("Type registry does not know about type id " + typeId);
|
||||
throw new IllegalStateException("The type registry "+typeRegistry+" does not know about type id " + typeId);
|
||||
}
|
||||
reloadableType.setResolved();
|
||||
if (GlobalConfiguration.logging && log.isLoggable(Level.INFO)) {
|
||||
log.info("< TypeRegistry.getReloadableType(" + typeRegistryId + "," + typeId + ") returning " + reloadableType);
|
||||
if (GlobalConfiguration.verboseMode && log.isLoggable(Level.INFO)) {
|
||||
log.info("<TypeRegistry.getReloadableType(typeRegistryId=" + typeRegistryId + ",typeId=" + typeId + ") returning " + reloadableType);
|
||||
}
|
||||
return reloadableType;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuilder s = new StringBuilder();
|
||||
s.append("TypeReg id=");
|
||||
s.append("TypeRegistry(id=");
|
||||
s.append(System.identityHashCode(this));
|
||||
s.append(" loader=" + classLoader.get().getClass().getName());
|
||||
s.append(",loader=" + classLoader.get().getClass().getName());
|
||||
s.append(")");
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
|
||||
@@ -590,7 +590,37 @@ public class TypeRewriter implements Constants {
|
||||
for (FieldMember field : fms) {
|
||||
createProtectedFieldGetterSetter(field);
|
||||
}
|
||||
|
||||
MethodMember[] methods = typeDescriptor.getMethods();
|
||||
|
||||
for (MethodMember method: methods) {
|
||||
if (!MethodMember.isSuperDispatcher(method)) {
|
||||
continue;
|
||||
}
|
||||
// TODO topmost test?
|
||||
String name = method.getName();
|
||||
String descriptor = method.getDescriptor();
|
||||
if (GlobalConfiguration.verboseMode && log.isLoggable(Level.FINEST)) {
|
||||
log.finest("Creating super dispatcher for method "+name+descriptor+" in type "+slashedname);
|
||||
}
|
||||
// Create a superdispatcher for this method
|
||||
MethodVisitor mv = cw.visitMethod(Modifier.PUBLIC, method.getName(), method.getDescriptor(), null, method.getExceptions());
|
||||
int ps = Utils.getParameterCount(method.getDescriptor());
|
||||
ReturnType methodReturnType = Utils.getReturnTypeDescriptor(method.getDescriptor());
|
||||
int lvarIndex = 0;
|
||||
mv.visitVarInsn(ALOAD, lvarIndex++); // load this
|
||||
Utils.createLoadsBasedOnDescriptor(mv, descriptor, lvarIndex);
|
||||
String targetMethod = method.getName().substring(0,method.getName().lastIndexOf("_$"));
|
||||
mv.visitMethodInsn(Opcodes.INVOKESPECIAL,typeDescriptor.getSupertypeName(),targetMethod,method.getDescriptor());
|
||||
Utils.addCorrectReturnInstruction(mv, methodReturnType, false);
|
||||
int maxs = ps + 1;
|
||||
if (methodReturnType.isDoubleSlot()) {
|
||||
maxs++;
|
||||
}
|
||||
mv.visitMaxs(maxs, maxs);
|
||||
mv.visitEnd();
|
||||
}
|
||||
|
||||
for (MethodMember method : methods) {
|
||||
if (!MethodMember.isCatcher(method)) {
|
||||
continue;
|
||||
|
||||
@@ -16,7 +16,10 @@
|
||||
package org.springsource.loaded;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -731,7 +734,7 @@ public class Utils implements Opcodes, Constants {
|
||||
* @param dottedclassname the dot separated classname without .class suffix
|
||||
* @return the byte data defining that class
|
||||
*/
|
||||
public static byte[] loadClassAsBytes(ClassLoader loader, String dottedclassname) {
|
||||
public static byte[] loadDottedClassAsBytes(ClassLoader loader, String dottedclassname) {
|
||||
if (GlobalConfiguration.assertsOn) {
|
||||
if (dottedclassname.endsWith(".class")) {
|
||||
throw new IllegalStateException(".class suffixed name should not be passed:" + dottedclassname);
|
||||
@@ -755,7 +758,7 @@ public class Utils implements Opcodes, Constants {
|
||||
* @param slashedclassname the dot separated classname without .class suffix
|
||||
* @return the byte data defining that class
|
||||
*/
|
||||
public static byte[] loadClassAsBytes2(ClassLoader loader, String slashedclassname) {
|
||||
public static byte[] loadSlashedClassAsBytes(ClassLoader loader, String slashedclassname) {
|
||||
if (GlobalConfiguration.assertsOn) {
|
||||
if (slashedclassname.endsWith(".class")) {
|
||||
throw new IllegalStateException(".class suffixed name should not be passed:" + slashedclassname);
|
||||
@@ -770,6 +773,29 @@ public class Utils implements Opcodes, Constants {
|
||||
}
|
||||
return Utils.loadBytesFromStream(is);
|
||||
}
|
||||
|
||||
public static byte[] load(File file) {
|
||||
try {
|
||||
FileInputStream fis = new FileInputStream(file);
|
||||
byte[] data = loadBytesFromStream(fis);
|
||||
fis.close();
|
||||
return data;
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void write(File file, byte[] data) {
|
||||
try {
|
||||
FileOutputStream fos = new FileOutputStream(file);
|
||||
DataOutputStream dos = new DataOutputStream(fos);
|
||||
dos.write(data);
|
||||
dos.close();
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all the byte data from an input stream.
|
||||
|
||||
@@ -19,11 +19,13 @@ import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.springsource.loaded.FileChangeListener;
|
||||
import org.springsource.loaded.GlobalConfiguration;
|
||||
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
|
||||
@@ -95,6 +97,8 @@ public class FileSystemWatcher {
|
||||
|
||||
class Watcher implements Runnable {
|
||||
|
||||
private static Logger log = Logger.getLogger(Watcher.class.getName());
|
||||
|
||||
long lastScanTime;
|
||||
|
||||
// TODO configurable scan interval?
|
||||
@@ -222,6 +226,9 @@ class Watcher implements Runnable {
|
||||
if (f.isDirectory()) {
|
||||
determineChangesSince(f, lastScanTime);
|
||||
} else {
|
||||
if (GlobalConfiguration.verboseMode && log.isLoggable(Level.INFO)) {
|
||||
log.info("file change observed: "+f);
|
||||
}
|
||||
listener.fileChanged(f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,14 +42,13 @@ 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:
|
||||
* one of these 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
|
||||
* <li>reloadable types need their bytecode rewriting so that they can be modified later
|
||||
* <li>'framework' types (not loaded by the system classloader) need their reflection calls rewritten
|
||||
* <li>system classes also need their reflection calls modified but in a different way (they cannot have dependencies on types they cannot see)
|
||||
* </ul>
|
||||
*
|
||||
* @author Andy Clement
|
||||
@@ -63,10 +62,6 @@ public class SpringLoadedPreProcessor implements Constants {
|
||||
// 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;
|
||||
|
||||
@@ -74,9 +69,13 @@ public class SpringLoadedPreProcessor implements Constants {
|
||||
// to the VM. This records the list of those that have not yet been initialized.
|
||||
private Map<String, Integer> systemClassesRequiringInitialization = new HashMap<String, Integer>();
|
||||
|
||||
// Once the first reloadabletype is hit, we can start initializing the system classes with reflective interceptors.
|
||||
// Doing it early can lead to hangs
|
||||
private static boolean firstReloadableTypeHit = false;
|
||||
|
||||
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)
|
||||
// the generated suuport types)
|
||||
GlobalConfiguration.directlyDefineTypes = false;
|
||||
GlobalConfiguration.fileSystemMonitoring = true;
|
||||
systemClassesContainingReflection = new ArrayList<String>();
|
||||
@@ -98,14 +97,12 @@ public class SpringLoadedPreProcessor implements Constants {
|
||||
* order to determine whether the type should be made reloadable. Non-reloadable types will at least get their call sites
|
||||
* rewritten.
|
||||
*
|
||||
* @return modified bytes
|
||||
* @return potentially 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()) {
|
||||
@@ -120,44 +117,45 @@ public class SpringLoadedPreProcessor implements Constants {
|
||||
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 (GlobalConfiguration.verboseMode && log.isLoggable(Level.INFO)) {
|
||||
logPreProcess(classLoader, slashedClassName, typeRegistry);
|
||||
}
|
||||
|
||||
if (typeRegistry == null) { // A null type registry indicates nothing is being made reloadable for the classloader
|
||||
if (classLoader == null) { // Indicates loading of a system class
|
||||
if (systemClassesContainingReflection.contains(slashedClassName)) {
|
||||
try {
|
||||
// TODO [perf] why are we not using the cache here, is it because the list is so short?
|
||||
RewriteResult rr = SystemClassReflectionRewriter.rewrite(slashedClassName, bytes);
|
||||
// System.err.println("Type " + slashedClassName + " rewrite summary: " + rr.summarize());
|
||||
if (GlobalConfiguration.verboseMode && log.isLoggable(Level.FINER)) {
|
||||
log.finer("System class rewritten: name="+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;
|
||||
// }
|
||||
// This block can help when you suspect there is a system class using reflection and that
|
||||
// class isn't on the 'shortlist' (in systemClassesContainingReflection). Currently we skip
|
||||
// this for performance, we could make it optional baed on a configuration option
|
||||
// } 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)
|
||||
// What happens here? The aim is to determine if the type should be made reloadable.
|
||||
// 1. If NO, but something in this classloader might be, then rewrite the call sites.
|
||||
// 2. If NO, and nothing in this classloader might be, return the original bytes.
|
||||
// 3. If YES, make the type reloadable (including rewriting call sites)
|
||||
|
||||
boolean isReloadableTypeName = typeRegistry.isReloadableTypeName(slashedClassName, protectionDomain, bytes);
|
||||
|
||||
@@ -542,34 +540,12 @@ public class SpringLoadedPreProcessor implements Constants {
|
||||
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) {
|
||||
private void logPreProcess(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);
|
||||
}
|
||||
log.info("SpringLoaded preprocessing: classname="+slashedClassName+" classloader="+clname+" typeRegistry="+typeRegistry);
|
||||
}
|
||||
|
||||
public static List<Plugin> getGlobalPlugins() {
|
||||
|
||||
@@ -26,17 +26,23 @@ 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(record.getLevel());
|
||||
String message = super.formatMessage(record);
|
||||
|
||||
if (!(message.startsWith(">") || message.startsWith("<"))) {
|
||||
s.append(":");
|
||||
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(".");
|
||||
s.append(record.getSourceMethodName());
|
||||
s.append(":");
|
||||
s.append(super.formatMessage(record));
|
||||
s.append(message);
|
||||
s.append("\n");
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
@@ -44,7 +44,8 @@ public abstract class TypeDescriptorMethodProvider extends MethodProvider {
|
||||
MethodMember[] methods = typeDescriptor.getMethods();
|
||||
List<Invoker> invokers = new ArrayList<Invoker>();
|
||||
for (MethodMember method : methods) {
|
||||
if (((MethodMember.BIT_CATCHER | MethodMember.WAS_DELETED) & method.bits) == 0) {
|
||||
// TODO [perf] create constant for this check?
|
||||
if (((MethodMember.BIT_CATCHER | MethodMember.BIT_SUPERDISPATCHER | MethodMember.WAS_DELETED) & method.bits) == 0) {
|
||||
invokers.add(invokerFor(method));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user