JavaObject.java

  1. /***** BEGIN LICENSE BLOCK *****
  2.  * Version: EPL 1.0/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Eclipse Public
  5.  * License Version 1.0 (the "License"); you may not use this file
  6.  * except in compliance with the License. You may obtain a copy of
  7.  * the License at http://www.eclipse.org/legal/epl-v10.html
  8.  *
  9.  * Software distributed under the License is distributed on an "AS
  10.  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
  11.  * implied. See the License for the specific language governing
  12.  * rights and limitations under the License.
  13.  *
  14.  * Copyright (C) 2001 Alan Moore <alan_moore@gmx.net>
  15.  * Copyright (C) 2001-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
  16.  * Copyright (C) 2002 Benoit Cerrina <b.cerrina@wanadoo.fr>
  17.  * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
  18.  * Copyright (C) 2004 Thomas E Enebo <enebo@acm.org>
  19.  * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
  20.  * Copyright (C) 2004 David Corbin <dcorbin@users.sourceforge.net>
  21.  *
  22.  * Alternatively, the contents of this file may be used under the terms of
  23.  * either of the GNU General Public License Version 2 or later (the "GPL"),
  24.  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  25.  * in which case the provisions of the GPL or the LGPL are applicable instead
  26.  * of those above. If you wish to allow use of your version of this file only
  27.  * under the terms of either the GPL or the LGPL, and not to allow others to
  28.  * use your version of this file under the terms of the EPL, indicate your
  29.  * decision by deleting the provisions above and replace them with the notice
  30.  * and other provisions required by the GPL or the LGPL. If you do not delete
  31.  * the provisions above, a recipient may use your version of this file under
  32.  * the terms of any one of the EPL, the GPL or the LGPL.
  33.  ***** END LICENSE BLOCK *****/
  34. package org.jruby.javasupport;

  35. import java.io.ByteArrayInputStream;
  36. import java.io.ByteArrayOutputStream;
  37. import java.io.IOException;
  38. import java.io.ObjectInputStream;
  39. import java.io.ObjectOutputStream;
  40. import java.io.Serializable;
  41. import org.jruby.Ruby;
  42. import org.jruby.RubyClass;
  43. import org.jruby.RubyFixnum;
  44. import org.jruby.RubyModule;
  45. import org.jruby.RubyObject;
  46. import org.jruby.RubyString;
  47. import org.jruby.runtime.ivars.VariableAccessor;
  48. import org.jruby.anno.JRubyMethod;
  49. import org.jruby.anno.JRubyClass;
  50. import org.jruby.java.proxies.JavaProxy;
  51. import org.jruby.runtime.Block;
  52. import org.jruby.runtime.ObjectAllocator;
  53. import org.jruby.runtime.ThreadContext;
  54. import org.jruby.runtime.builtin.IRubyObject;
  55. import org.jruby.util.ByteList;
  56. import org.jruby.util.JRubyObjectInputStream;

  57. /**
  58.  *
  59.  * @author  jpetersen
  60.  */
  61. @JRubyClass(name="Java::JavaObject")
  62. public class JavaObject extends RubyObject {

  63.     private static Object NULL_LOCK = new Object();
  64.     private final VariableAccessor objectAccessor;

  65.     protected JavaObject(Ruby runtime, RubyClass rubyClass, Object value) {
  66.         super(runtime, rubyClass);
  67.         objectAccessor = rubyClass.getVariableAccessorForWrite("__wrap_struct__");
  68.         dataWrapStruct(value);
  69.     }

  70.     @Override
  71.     public Object dataGetStruct() {
  72.         return objectAccessor.get(this);
  73.     }

  74.     @Override
  75.     public void dataWrapStruct(Object object) {
  76.         objectAccessor.set(this, object);
  77.     }

  78.     protected JavaObject(Ruby runtime, Object value) {
  79.         this(runtime, runtime.getJavaSupport().getJavaObjectClass(), value);
  80.     }

  81.     public static JavaObject wrap(Ruby runtime, Object value) {
  82.         if (value != null) {
  83.             if (value instanceof Class) {
  84.                 return JavaClass.get(runtime, (Class<?>) value);
  85.             } else if (value.getClass().isArray()) {
  86.                 return new JavaArray(runtime, value);
  87.             }
  88.         }
  89.         return new JavaObject(runtime, value);
  90.     }

  91.     @JRubyMethod(meta = true)
  92.     public static IRubyObject wrap(ThreadContext context, IRubyObject self, IRubyObject object) {
  93.         Ruby runtime = context.runtime;
  94.         Object obj = getWrappedObject(object, NEVER);

  95.         if (obj == NEVER) return runtime.getNil();

  96.         return wrap(runtime, obj);
  97.     }

  98.     @Override
  99.     public Class<?> getJavaClass() {
  100.         Object dataStruct = dataGetStruct();
  101.         return dataStruct != null ? dataStruct.getClass() : Void.TYPE;
  102.     }

  103.     public Object getValue() {
  104.         return dataGetStruct();
  105.     }

  106.     public static RubyClass createJavaObjectClass(Ruby runtime, RubyModule javaModule) {
  107.         // FIXME: Ideally JavaObject instances should be marshallable, which means that
  108.         // the JavaObject metaclass should have an appropriate allocator. JRUBY-414
  109.         RubyClass result = javaModule.defineClassUnder("JavaObject", runtime.getObject(), JAVA_OBJECT_ALLOCATOR);

  110.         registerRubyMethods(runtime, result);

  111.         result.getMetaClass().undefineMethod("new");
  112.         result.getMetaClass().undefineMethod("allocate");

  113.         return result;
  114.     }

  115.     protected static void registerRubyMethods(Ruby runtime, RubyClass result) {
  116.         result.defineAnnotatedMethods(JavaObject.class);
  117.     }

  118.     @Override
  119.     public boolean equals(Object other) {
  120.         Object myValue = getValue();
  121.         Object otherValue = other;
  122.         if (other instanceof IRubyObject) {
  123.             otherValue = getWrappedObject((IRubyObject)other, NEVER);
  124.         }

  125.         if (otherValue == NEVER) {
  126.             // not a wrapped object
  127.             return false;
  128.         }
  129.         return myValue == otherValue;
  130.     }

  131.     @Override
  132.     public int hashCode() {
  133.         Object dataStruct = dataGetStruct();
  134.         if (dataStruct != null) {
  135.             return dataStruct.hashCode();
  136.         }
  137.         return 0;
  138.     }

  139.     @JRubyMethod
  140.     @Override
  141.     public RubyFixnum hash() {
  142.         return getRuntime().newFixnum(hashCode());
  143.     }

  144.     @JRubyMethod
  145.     @Override
  146.     public IRubyObject to_s() {
  147.         return to_s(getRuntime(), dataGetStruct());
  148.     }

  149.     public static IRubyObject to_s(Ruby runtime, Object dataStruct) {
  150.         if (dataStruct != null) {
  151.             String stringValue = dataStruct.toString();
  152.             if (stringValue != null) {
  153.                 return RubyString.newUnicodeString(runtime, dataStruct.toString());
  154.             }

  155.             return runtime.getNil();
  156.         }
  157.         return RubyString.newEmptyString(runtime);
  158.     }

  159.     @JRubyMethod(name = {"==", "eql?"}, required = 1)
  160.     public IRubyObject op_equal(IRubyObject other) {
  161.         Object myValue = getValue();
  162.         return opEqualShared(myValue, other);
  163.     }

  164.     public static IRubyObject op_equal(JavaProxy self, IRubyObject other) {
  165.         Object myValue = self.getObject();
  166.         return opEqualShared(myValue, other);
  167.     }

  168.     private static IRubyObject opEqualShared(Object myValue, IRubyObject other) {
  169.         Ruby runtime = other.getRuntime();
  170.         Object otherValue = getWrappedObject(other, NEVER);

  171.         if (other == NEVER) {
  172.             // not a wrapped object
  173.             return runtime.getFalse();
  174.         }

  175.         if (myValue == null && otherValue == null) {
  176.             return runtime.getTrue();
  177.         }

  178.         return runtime.newBoolean(myValue.equals(otherValue));
  179.     }

  180.     @JRubyMethod(name = "equal?", required = 1)
  181.     public IRubyObject same(IRubyObject other) {
  182.         Ruby runtime = getRuntime();
  183.         Object myValue = getValue();
  184.         Object otherValue = getWrappedObject(other, NEVER);

  185.         if (other == NEVER) {
  186.             // not a wrapped object
  187.             return runtime.getFalse();
  188.         }

  189.         if (myValue == null && otherValue == null) {
  190.             return getRuntime().getTrue();
  191.         }

  192.         if (!(other instanceof JavaObject)) return runtime.getFalse();

  193.         boolean isSame = getValue() == ((JavaObject) other).getValue();
  194.         return isSame ? getRuntime().getTrue() : getRuntime().getFalse();
  195.     }

  196.     private static Object getWrappedObject(IRubyObject other, Object def) {
  197.         if (other instanceof JavaObject) {
  198.             return ((JavaObject)other).getValue();
  199.         } else if (other instanceof JavaProxy) {
  200.             return ((JavaProxy)other).getObject();
  201.         } else {
  202.             return def;
  203.         }
  204.     }

  205.     @JRubyMethod
  206.     public RubyString java_type() {
  207.         return getRuntime().newString(getJavaClass().getName());
  208.     }

  209.     @JRubyMethod
  210.     public IRubyObject java_class() {
  211.         return JavaClass.get(getRuntime(), getJavaClass());
  212.     }

  213.     @JRubyMethod
  214.     public RubyFixnum length() {
  215.         throw getRuntime().newTypeError("not a java array");
  216.     }

  217.     @JRubyMethod(name = "java_proxy?")
  218.     public IRubyObject is_java_proxy() {
  219.         return getRuntime().getTrue();
  220.     }

  221.     @JRubyMethod(name = "synchronized")
  222.     public IRubyObject ruby_synchronized(ThreadContext context, Block block) {
  223.         Object lock = getValue();
  224.         synchronized (lock != null ? lock : NULL_LOCK) {
  225.             return block.yield(context, null);
  226.         }
  227.     }
  228.    
  229.     public static IRubyObject ruby_synchronized(ThreadContext context, Object lock, Block block) {
  230.         synchronized (lock != null ? lock : NULL_LOCK) {
  231.             return block.yield(context, null);
  232.         }
  233.     }
  234.    
  235.     @JRubyMethod
  236.     public IRubyObject marshal_dump() {
  237.         if (Serializable.class.isAssignableFrom(getJavaClass())) {
  238.             try {
  239.                 ByteArrayOutputStream baos = new ByteArrayOutputStream();
  240.                 ObjectOutputStream oos = new ObjectOutputStream(baos);

  241.                 oos.writeObject(getValue());

  242.                 return getRuntime().newString(new ByteList(baos.toByteArray()));
  243.             } catch (IOException ioe) {
  244.                 throw getRuntime().newIOErrorFromException(ioe);
  245.             }
  246.         } else {
  247.             throw getRuntime().newTypeError("no marshal_dump is defined for class " + getJavaClass());
  248.         }
  249.     }

  250.     @JRubyMethod
  251.     public IRubyObject marshal_load(ThreadContext context, IRubyObject str) {
  252.         try {
  253.             ByteList byteList = str.convertToString().getByteList();
  254.             ByteArrayInputStream bais = new ByteArrayInputStream(byteList.getUnsafeBytes(), byteList.getBegin(), byteList.getRealSize());
  255.             ObjectInputStream ois = new JRubyObjectInputStream(context.runtime, bais);

  256.             dataWrapStruct(ois.readObject());

  257.             return this;
  258.         } catch (IOException ioe) {
  259.             throw context.runtime.newIOErrorFromException(ioe);
  260.         } catch (ClassNotFoundException cnfe) {
  261.             throw context.runtime.newTypeError("Class not found unmarshaling Java type: " + cnfe.getLocalizedMessage());
  262.         }
  263.     }

  264.     @Override
  265.     public Object toJava(Class cls) {
  266.         if (getValue() == null) {
  267.             // THIS SHOULD NEVER HAPPEN, but it DOES
  268.             return getValue();
  269.         }
  270.        
  271.         if (cls.isAssignableFrom(getValue().getClass())) {
  272.             return getValue();
  273.         }
  274.        
  275.         return super.toJava(cls);
  276.     }

  277.     private static final ObjectAllocator JAVA_OBJECT_ALLOCATOR = new ObjectAllocator() {
  278.         public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
  279.             return new JavaObject(runtime, klazz, null);
  280.         }
  281.     };

  282. }