package org.jruby.javasupport;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import org.jruby.Ruby;
import org.jruby.RubyBoolean;
import org.jruby.RubyClass;
import org.jruby.RubyFixnum;
import org.jruby.RubyModule;
import org.jruby.RubyObject;
import org.jruby.RubyString;
import org.jruby.runtime.ivars.VariableAccessor;
import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyClass;
import org.jruby.java.proxies.JavaProxy;
import org.jruby.runtime.Block;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.ByteList;
import org.jruby.util.JRubyObjectInputStream;
@JRubyClass(name="Java::JavaObject")
public class JavaObject extends RubyObject {
private static final Object NULL_LOCK = new Object();
private final VariableAccessor objectAccessor;
protected JavaObject(Ruby runtime, RubyClass rubyClass, Object value) {
super(runtime, rubyClass);
objectAccessor = rubyClass.getVariableAccessorForWrite("__wrap_struct__");
dataWrapStruct(value);
}
@Override
public final Object dataGetStruct() {
return objectAccessor.get(this);
}
@Override
public final void dataWrapStruct(Object object) {
objectAccessor.set(this, object);
}
protected JavaObject(Ruby runtime, Object value) {
this(runtime, runtime.getJavaSupport().getJavaObjectClass(), value);
}
public static JavaObject wrap(final Ruby runtime, final Object value) {
if ( value != null ) {
if ( value instanceof Class ) {
return JavaClass.get(runtime, (Class<?>) value);
}
if ( value.getClass().isArray() ) {
return new JavaArray(runtime, value);
}
}
return new JavaObject(runtime, value);
}
@JRubyMethod(meta = true)
public static IRubyObject wrap(final ThreadContext context,
final IRubyObject self, final IRubyObject object) {
final Object objectValue = unwrapObject(object, NEVER);
if ( objectValue == NEVER ) return context.nil;
return wrap(context.runtime, objectValue);
}
@Override
public final Class<?> getJavaClass() {
Object dataStruct = dataGetStruct();
return dataStruct != null ? dataStruct.getClass() : Void.TYPE;
}
public final Object getValue() {
return dataGetStruct();
}
public static RubyClass createJavaObjectClass(Ruby runtime, RubyModule javaModule) {
RubyClass JavaObject = javaModule.defineClassUnder("JavaObject", runtime.getObject(), JAVA_OBJECT_ALLOCATOR);
registerRubyMethods(runtime, JavaObject);
JavaObject.getMetaClass().undefineMethod("new");
JavaObject.getMetaClass().undefineMethod("allocate");
return JavaObject;
}
protected static void registerRubyMethods(Ruby runtime, RubyClass JavaObject) {
JavaObject.defineAnnotatedMethods(JavaObject.class);
}
@Override
public boolean equals(final Object other) {
final Object otherValue;
if ( other instanceof IRubyObject ) {
otherValue = unwrapObject((IRubyObject) other, NEVER);
}
else {
otherValue = other;
}
if ( otherValue == NEVER ) return false;
return getValue() == otherValue;
}
@Override
public int hashCode() {
final Object value = dataGetStruct();
return value == null ? 0 : value.hashCode();
}
@JRubyMethod
@Override
public RubyFixnum hash() {
return getRuntime().newFixnum(hashCode());
}
@JRubyMethod
@Override
public IRubyObject to_s() {
return to_s(getRuntime(), dataGetStruct());
}
public static IRubyObject to_s(Ruby runtime, Object dataStruct) {
if (dataStruct != null) {
final String stringValue = dataStruct.toString();
if ( stringValue == null ) return runtime.getNil();
return RubyString.newUnicodeString(runtime, stringValue);
}
return RubyString.newEmptyString(runtime);
}
@JRubyMethod(name = {"==", "eql?"}, required = 1)
public IRubyObject op_equal(final IRubyObject other) {
return equals(getRuntime(), getValue(), other);
}
public static RubyBoolean op_equal(JavaProxy self, IRubyObject other) {
return equals(self.getRuntime(), self.getObject(), other);
}
private static RubyBoolean equals(final Ruby runtime,
final Object thisValue, final IRubyObject other) {
final Object otherValue = unwrapObject(other, NEVER);
if ( otherValue == NEVER ) {
return runtime.getFalse();
}
if ( thisValue == null ) {
return runtime.newBoolean(otherValue == null);
}
return runtime.newBoolean(thisValue.equals(otherValue));
}
@JRubyMethod(name = "equal?", required = 1)
public IRubyObject same(final IRubyObject other) {
final Ruby runtime = getRuntime();
final Object thisValue = getValue();
final Object otherValue = unwrapObject(other, NEVER);
if ( otherValue == NEVER ) {
return runtime.getFalse();
}
if ( ! (other instanceof JavaObject) ) return runtime.getFalse();
return runtime.newBoolean(thisValue == otherValue);
}
private static Object unwrapObject(
final IRubyObject wrapped, final Object defaultValue) {
if ( wrapped instanceof JavaObject ) {
return ((JavaObject) wrapped).getValue();
}
if ( wrapped instanceof JavaProxy ) {
return ((JavaProxy) wrapped).getObject();
}
return defaultValue;
}
@JRubyMethod
public RubyString java_type() {
return getRuntime().newString(getJavaClass().getName());
}
@JRubyMethod
public JavaClass java_class() {
return JavaClass.get(getRuntime(), getJavaClass());
}
@JRubyMethod
public RubyFixnum length() {
throw getRuntime().newTypeError("not a java array");
}
@JRubyMethod(name = "java_proxy?")
public IRubyObject is_java_proxy() {
return getRuntime().getTrue();
}
@JRubyMethod(name = "synchronized")
public final IRubyObject ruby_synchronized(ThreadContext context, Block block) {
final Object lock = getValue();
synchronized (lock != null ? lock : NULL_LOCK) {
return block.yield(context, null);
}
}
public static IRubyObject ruby_synchronized(ThreadContext context, final Object lock, Block block) {
synchronized (lock != null ? lock : NULL_LOCK) {
return block.yield(context, null);
}
}
@JRubyMethod
public IRubyObject marshal_dump(ThreadContext context) {
if (Serializable.class.isAssignableFrom(getJavaClass())) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
new ObjectOutputStream(baos).writeObject(getValue());
return context.runtime.newString(new ByteList(baos.toByteArray(), false));
}
catch (IOException ex) {
throw context.runtime.newIOErrorFromException(ex);
}
}
throw context.runtime.newTypeError("no marshal_dump is defined for class " + getJavaClass());
}
@JRubyMethod
public IRubyObject marshal_load(ThreadContext context, IRubyObject str) {
try {
ByteList byteList = str.convertToString().getByteList();
ByteArrayInputStream bais = new ByteArrayInputStream(byteList.getUnsafeBytes(), byteList.getBegin(), byteList.getRealSize());
dataWrapStruct(new JRubyObjectInputStream(context.runtime, bais).readObject());
return this;
}
catch (IOException ex) {
throw context.runtime.newIOErrorFromException(ex);
}
catch (ClassNotFoundException ex) {
throw context.runtime.newTypeError("Class not found unmarshaling Java type: " + ex.getLocalizedMessage());
}
}
@Override
@SuppressWarnings("unchecked")
public <T> T toJava(Class<T> target) {
final Object value = getValue();
if ( value == null ) return null;
if ( target.isAssignableFrom( value.getClass() ) ) {
return target.cast(value);
}
return super.toJava(target);
}
private static final ObjectAllocator JAVA_OBJECT_ALLOCATOR = new ObjectAllocator() {
public JavaObject allocate(Ruby runtime, RubyClass klazz) {
return new JavaObject(runtime, klazz, null);
}
};
}