xapi.util.X_GC Maven / Gradle / Ivy
package xapi.util;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashSet;
import xapi.annotation.gc.Indestructible;
import xapi.annotation.gc.OnGC;
import xapi.collect.impl.AbstractMultiInitMap;
import xapi.log.X_Log;
import xapi.util.api.Destroyable;
import xapi.util.api.Destroyer;
/**
* In order to encourage and facilitate object de-referencing,
* the X_GC class, combined with the {@link OnGC} annotation
* allow objects to destroy each other without necessarily knowing anything about them.
*
* It also allows forming "destruction chains", whereby when a root object is destroyed,
* we can check if it's fields are destroyable and gc them too, either by annotating with OnGC,
* or implementing the {@link Destroyable} interface.
*
* Not that this may NOT be what you want, in case a field is shared among multiple children.
* Thus, GC chaining is disabled by default.
* Default behavior is to null out fields without inspecting them.
*
* @author "James X. Nelson ([email protected])"
*
*/
public class X_GC {
private X_GC() {
}
private static final class StaticGCMethods extends AbstractMultiInitMap> {
public StaticGCMethods() {
super(PASS_THRU);
}
@Override
protected Destroyer initialize(String method, Class> cls) {
try{
String[] bits = method.split("#");
assert bits.length == 2 :
"[ERROR] Malformed static GC method "+method+"; \n" +
"Proper syntax is com.package.Clazz$StaticInner#methodName";
Class> toLoad;
if (bits[0].equals(cls.getName()))
toLoad = cls;
else {
toLoad = Class.forName(bits[0], true, cls.getClassLoader());
}
final Method m = toLoad.getMethod(bits[1], Object.class);
if (m == null){
X_Log.warn("Could not find static gc method ",method);
return Destroyer.NO_OP;
}
else {
assert m.getTypeParameters().length == 0 :
"Only zero-arg instance methods allowed";
if (!m.isAccessible())
m.setAccessible(true);
return new Destroyer() {
@Override
public void destroyObject(Object toDestroy) {
try {
m.invoke(null, toDestroy);
} catch (Throwable e) {
X_Debug.maybeRethrow(e);
}
}
};
}
} catch (Throwable e) {
throw X_Util.rethrow(e);
}
}
}
private static final class GCMap extends AbstractMultiInitMap, Destroyer, OnGC>
{
private static final StaticGCMethods staticDestroyers = new StaticGCMethods();
public GCMap() {
super(CLASS_NAME);
}
@Override
protected Destroyer initialize(Class> cls, OnGC options) {
final ArrayList zeroArg = new ArrayList();
for (String method : options.instanceGCMethods()) {
try{
Method m = cls.getMethod(method);
if (m == null)
m = cls.getDeclaredMethod(method);
if (m == null)
X_Log.warn("Could not find instance level gc method ",method);
else {
if (!m.isAccessible())
m.setAccessible(true);
assert m.getTypeParameters().length == 0 :
"Only zero-arg instance methods allowed on instance gc methods";
// could test for static, but we'll let that slide, as it will work...
zeroArg.add(m);
}
} catch (Throwable e) {
X_Debug.maybeRethrow(e);
}
}
final ArrayList fieldDestroyers = new ArrayList();
if (options.chainDeleteFields()||options.deleteInstanceFields()) {
for (final Field f : cls.getDeclaredFields()) {
if (shouldSkip(f))
continue;
try {
if (!f.isAccessible()) {
f.setAccessible(true);
}
final Destroyer setToNull = new Destroyer() {
@Override
public void destroyObject(Object toDestroy) {
try {
f.set(toDestroy, null);
} catch (Throwable e) {
X_Debug.maybeRethrow(e);
}
}
};
// if the client wants us to chain gc to child fields:
if (options.chainDeleteFields()) {
fieldDestroyers.add(new Destroyer() {
@SuppressWarnings({"rawtypes","unchecked"})
@Override
public void destroyObject(Object toDestroy) {
Object obj;
try {
obj = f.get(toDestroy);
if (obj != null) {
if (obj instanceof Destroyable) {
((Destroyable)obj).destroy();
}
Class cls = obj.getClass();
OnGC anno = (OnGC)cls.getAnnotation(OnGC.class);
if (anno != null)
// This object ref won't give generator details,
// However, it can still be made to work, by scanning
// for all OnGC annotations and generating code for any of them
destroy(cls, obj);
}
} catch (Throwable e) {
e.printStackTrace();
}
// also call our setToNull destroyer
setToNull.destroyObject(toDestroy);
}
});
} else {
fieldDestroyers.add(setToNull);
}
} catch (Throwable e) {
X_Debug.maybeRethrow(e);
}
}
}
final Destroyer instanceLevel = new Destroyer() {
@Override
public void destroyObject(Object toDestroy) {
// destroy methods called
for (Method m : zeroArg) {
try{
m.invoke(toDestroy);
} catch (Throwable e){
X_Debug.maybeRethrow(e);
}
}
// fields cleared / destroy event chained
for (Destroyer destroyer : fieldDestroyers) {
destroyer.destroyObject(toDestroy);
}
}
};
// avoid extra work, if we can;
// the static methods are more overhead up front
if (options.staticGCMethods().length == 0)
return instanceLevel;
// We have to do reflection
final ArrayList staticLevel = new ArrayList();
for (String method : options.staticGCMethods()) {
assert !method.contains("(") : "Do not send parantheses with method descriptions: "+method+" on "+cls;
// share instances of a single destroyer per method, to cut down on memory duplication
staticLevel.add(staticDestroyers.get(method, cls));
}
// Initialize a composite destroyer, which notifies static methods first,
// then instance methods and finally instance fields, if enabled.
return new Destroyer() {
@Override
public void destroyObject(Object toDestroy) {
// Give static methods a crack at the object first,
// as they are the most likely to be doing housekeeping
// that needs to see the objects before they are destroyed.
for (Destroyer m : staticLevel) {
m.destroyObject(toDestroy);
}
instanceLevel.destroyObject(toDestroy);
}
};
}
}
private static final GCMap destroyers = new GCMap();
/**
* In gwt, we can use this as a magic method to compute a destroy function,
* but do so without bloating runtime code. This is why you must send the class.
*
* If you put OnGC on a superclass, and do not wish to repeat the annotation,
* you can send the superclass's class literal, and it will be applied instead
* (though it will not use any overridden methods; we literally use the super classes Method objects).
*
* Note that you must send a the class LITERAL and not a class reference,
* as that is what the gwt generator will use to define a GC strategy.
*
* In any case, the code needed to destroy the fields of the object will just be plain javascript,
* for (key in _)if (_.hasOwnProperty(key)) delete _[key]
*
* Sending the class along simply means we can check for the @GC annotation, and use it to call some
* zero-arg destroy methods for this object, and maybe even on any particular fields.
*
* @param cls
* @param inst
*/
public static void destroy(Class super T> classLiteral, T inst) {
if (inst == null)return;
OnGC gc = classLiteral.getAnnotation(OnGC.class);
if (gc == null) {
assert "true".equals(System.getProperty(
"xapi.gcignore."+classLiteral.getName(), "false")) : "Trying to call X_GC on a class without an OnGC annotation.\n" +
"To suppress this assertion, set system property "+"xapi.gcignore."+classLiteral.getName()+" to true";
return;
}
destroyers.get(classLiteral, gc).destroyObject(inst);
// TODO have a flag to disable supertype gc as well.
// Also, move this into the pre-compiled destroyer commands, instead of checking every time.
while (classLiteral != Object.class) {
classLiteral = classLiteral.getSuperclass();
gc = classLiteral.getAnnotation(OnGC.class);
if (gc != null){
destroy(classLiteral, inst);
return; // the above call will recurse
}
}
}
private static boolean shouldSkip(Field f) {
return
f.getType().isPrimitive()
|| Modifier.isFinal(f.getModifiers())
|| Modifier.isStatic(f.getModifiers())
|| f.getAnnotation(Indestructible.class) != null
|| f.isSynthetic()
;
}
public static void deepDestroy(Class super T> classLiteral, T inst) {
deepDestroy(classLiteral, inst, new HashSet
© 2015 - 2024 Weber Informatics LLC | Privacy Policy