package org.jruby;
import java.util.ArrayList;
import static org.jruby.RubyEnumerator.enumeratorize;
import java.util.Iterator;
import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyModule;
import org.jruby.runtime.Block;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import static org.jruby.runtime.Visibility.*;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.collections.WeakValuedIdentityMap;
@JRubyModule(name="ObjectSpace")
public class RubyObjectSpace {
public static RubyModule createObjectSpaceModule(Ruby runtime) {
RubyModule objectSpaceModule = runtime.defineModule("ObjectSpace");
runtime.setObjectSpaceModule(objectSpaceModule);
objectSpaceModule.defineAnnotatedMethods(RubyObjectSpace.class);
WeakMap.createWeakMap(runtime);
return objectSpaceModule;
}
@JRubyMethod(required = 1, optional = 1, module = true, visibility = PRIVATE)
public static IRubyObject define_finalizer(IRubyObject recv, IRubyObject[] args, Block block) {
Ruby runtime = recv.getRuntime();
IRubyObject finalizer;
if (args.length == 2) {
finalizer = args[1];
if (!finalizer.respondsTo("call")) {
throw runtime.newArgumentError("wrong type argument " + finalizer.getType() + " (should be callable)");
}
} else {
finalizer = runtime.newProc(Block.Type.PROC, block);
}
IRubyObject obj = args[0];
runtime.getObjectSpace().addFinalizer(obj, finalizer);
return runtime.newArray(RubyFixnum.zero(runtime), finalizer);
}
@JRubyMethod(required = 1, module = true, visibility = PRIVATE)
public static IRubyObject undefine_finalizer(IRubyObject recv, IRubyObject obj, Block block) {
recv.getRuntime().getObjectSpace().removeFinalizers(RubyNumeric.fix2long(obj.id()));
return recv;
}
@JRubyMethod(name = "_id2ref", required = 1, module = true, visibility = PRIVATE)
public static IRubyObject id2ref(IRubyObject recv, IRubyObject id) {
final Ruby runtime = id.getRuntime();
if (!(id instanceof RubyFixnum)) {
throw runtime.newTypeError(id, runtime.getFixnum());
}
long longId = ((RubyFixnum) id).getLongValue();
if (longId == 0) {
return runtime.getFalse();
} else if (longId == 20) {
return runtime.getTrue();
} else if (longId == 8) {
return runtime.getNil();
} else if (longId % 2 != 0) {
return runtime.newFixnum((longId - 1) / 2);
} else {
if (runtime.isObjectSpaceEnabled()) {
IRubyObject object = runtime.getObjectSpace().id2ref(longId);
if (object == null) {
return runtime.getNil();
}
return object;
} else {
runtime.getWarnings().warn("ObjectSpace is disabled; _id2ref only supports immediates, pass -X+O to enable");
throw runtime.newRangeError(String.format("0x%016x is not id value", longId));
}
}
}
public static IRubyObject each_objectInternal(final ThreadContext context, IRubyObject recv, IRubyObject[] args, final Block block) {
final Ruby runtime = context.runtime;
final RubyModule rubyClass;
if (args.length == 0) {
rubyClass = runtime.getObject();
} else {
if (!(args[0] instanceof RubyModule)) throw runtime.newTypeError("class or module required");
rubyClass = (RubyModule) args[0];
}
if (rubyClass == runtime.getClassClass() || rubyClass == runtime.getModule()) {
final ArrayList<IRubyObject> modules = new ArrayList<>(96);
runtime.eachModule((module) -> {
if (rubyClass.isInstance(module)) {
if (!(module instanceof IncludedModule)) {
modules.add(module);
}
}
});
final int count = modules.size();
for (int i = 0; i<count; i++) {
block.yield(context, modules.get(i));
}
return runtime.newFixnum(count);
}
if (rubyClass.getClass() == MetaClass.class) {
IRubyObject attached = ((MetaClass) args[0]).getAttached();
block.yield(context, attached); int count = 1;
if (attached instanceof RubyClass) {
for (RubyClass child : ((RubyClass) attached).subclasses(true)) {
if (!(child instanceof IncludedModule)) {
count++; block.yield(context, child);
}
}
}
return runtime.newFixnum(count);
}
if ( ! runtime.isObjectSpaceEnabled() ) {
throw runtime.newRuntimeError("ObjectSpace is disabled; each_object will only work with Class, pass -X+O to enable");
}
final Iterator iter = runtime.getObjectSpace().iterator(rubyClass);
IRubyObject obj; int count = 0;
while ((obj = (IRubyObject) iter.next()) != null) {
count++; block.yield(context, obj);
}
return runtime.newFixnum(count);
}
@JRubyMethod(name = "each_object", optional = 1, module = true, visibility = PRIVATE)
public static IRubyObject each_object(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) {
return block.isGiven() ? each_objectInternal(context, recv, args, block) : enumeratorize(context.runtime, recv, "each_object", args);
}
@JRubyMethod(name = "garbage_collect", module = true, visibility = PRIVATE)
public static IRubyObject garbage_collect(ThreadContext context, IRubyObject recv) {
return RubyGC.start(context, recv);
}
public static class WeakMap extends RubyObject {
static void createWeakMap(Ruby runtime) {
RubyClass weakMap = runtime.getObjectSpaceModule().defineClassUnder("WeakMap", runtime.getObject(), new ObjectAllocator() {
public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
return new WeakMap(runtime, klazz);
}
});
weakMap.defineAnnotatedMethods(WeakMap.class);
}
public WeakMap(Ruby runtime, RubyClass cls) {
super(runtime, cls);
}
@JRubyMethod(name = "[]")
public IRubyObject op_aref(ThreadContext context, IRubyObject key) {
IRubyObject value = map.get(key);
if (value != null) return value;
return context.nil;
}
@JRubyMethod(name = "[]=")
public IRubyObject op_aref(ThreadContext context, IRubyObject key, IRubyObject value) {
map.put(key, value);
return context.runtime.newFixnum(System.identityHashCode(value));
}
@JRubyMethod(name = "key?")
public IRubyObject key_p(ThreadContext context, IRubyObject key) {
return context.runtime.newBoolean(map.get(key) != null);
}
private final WeakValuedIdentityMap<IRubyObject, IRubyObject> map = new WeakValuedIdentityMap<IRubyObject, IRubyObject>();
}
}