From 21f62eb245cb109b4849ed033add8df0ab2768b7 Mon Sep 17 00:00:00 2001 From: kares Date: Mon, 12 Dec 2011 22:17:42 +0100 Subject: [PATCH] add a ruby class wrapper - acts as a JS contructor, reimplement RubyObject#get to treat accessors as properties but otherwise arbitrary methods with arity == 0 should rather act as functions, move all ruby wrappers to a single unit --- lib/rhino.rb | 3 +- lib/rhino/ruby.rb | 173 +++++++++++++++++++++++++++++++ lib/rhino/ruby_function.rb | 39 ------- lib/rhino/ruby_object.rb | 109 ------------------- lib/rhino/wormhole.rb | 5 +- spec/rhino/ruby_function_spec.rb | 8 +- spec/rhino/ruby_object_spec.rb | 35 ++++--- 7 files changed, 199 insertions(+), 173 deletions(-) create mode 100644 lib/rhino/ruby.rb delete mode 100644 lib/rhino/ruby_function.rb delete mode 100644 lib/rhino/ruby_object.rb diff --git a/lib/rhino.rb b/lib/rhino.rb index 783a29a..991dc9a 100644 --- a/lib/rhino.rb +++ b/lib/rhino.rb @@ -24,6 +24,5 @@ require 'rhino/object' require 'rhino/context' require 'rhino/error' require 'rhino/rhino_ext' -require 'rhino/ruby_object' -require 'rhino/ruby_function' +require 'rhino/ruby' require 'rhino/deprecations' diff --git a/lib/rhino/ruby.rb b/lib/rhino/ruby.rb new file mode 100644 index 0000000..678dc05 --- /dev/null +++ b/lib/rhino/ruby.rb @@ -0,0 +1,173 @@ + +module Rhino + + class RubyObject < JS::ScriptableObject + include JS::Wrapper + + # wrap an arbitrary (ruby) object + def self.wrap(object) + new(object) + end + + def initialize(object) + super() + @ruby = object + end + + # abstract Object Wrapper#unwrap(); + def unwrap + @ruby + end + + # abstract String Scriptable#getClassName(); + def getClassName + @ruby.class.name + end + + def toString + "[ruby #{getClassName}]" # [object User] + end + + # override Object Scriptable#get(String name, Scriptable start); + # override Object Scriptable#get(int index, Scriptable start); + def get(name, start) + if name.is_a?(String) + if @ruby.respond_to?(name) + method = @ruby.method(name) + if method.arity == 0 && # check if it is an attr_reader + ( @ruby.respond_to?("#{name}=") || @ruby.instance_variables.include?("@#{name}") ) + begin + return Rhino.to_javascript(method.call, self) + rescue => e + raise RubyFunction.wrap_error(e) + end + else + return RubyFunction.new(@ruby.method(name)) + end + end + end + super + end + + # override boolean Scriptable#has(String name, Scriptable start); + # override boolean Scriptable#has(int index, Scriptable start); + def has(name, start) + if name.is_a?(String) + if @ruby.respond_to?(name) || + @ruby.respond_to?("#{name}=") # might have a writer but no reader + return true + end + end + super + end + + # override void Scriptable#put(String name, Scriptable start, Object value); + # override void Scriptable#put(int index, Scriptable start, Object value); + def put(name, start, value) + if name.is_a?(String) + if @ruby.respond_to?(set_name = "#{name}=") + return @ruby.send(set_name, Rhino.to_ruby(value)) + end + end + super + end + + # override Object[] Scriptable#getIds(); + def getIds + ids = [] + @ruby.public_methods(false).each do |name| + name = name[0...-1] if name[-1, 1] == '=' # 'foo=' ... 'foo' + name = name.to_java # java.lang.String + ids << name unless ids.include?(name) + end + super.each { |id| ids.unshift(id) } + ids.to_java + end + + # protected Object ScriptableObject#equivalentValues(Object value) + def equivalentValues(other) # JS == operator + other.is_a?(RubyObject) && unwrap.eql?(other.unwrap) + end + + end + + class RubyFunction < JS::BaseFunction + + # wrap a callable (Method/Proc) + def self.wrap(callable) + new(callable) + end + + def self.wrap_error(e) + JS::WrappedException.new(org.jruby.exceptions.RaiseException.new(e)) + end + + def initialize(callable) + super() + @callable = callable + end + + def unwrap + @callable + end + + # protected Object ScriptableObject#equivalentValues(Object value) + def equivalentValues(other) # JS == operator + return false unless other.is_a?(RubyFunction) + return true if unwrap == other.unwrap + # Method.== does check if their bind to the same object + # JS == means they might be bind to different objects : + unwrap.to_s == other.unwrap.to_s # "#" + end + + # override Object ScriptableObject#getPrototype() + def getPrototype + unless proto = super + #proto = ScriptableObject.getFunctionPrototype(getParentScope) + #setPrototype(proto) + end + proto + end + + # override Object BaseFunction#call(Context context, Scriptable scope, + # Scriptable thisObj, Object[] args) + def call(context, scope, this, args) + rb_args = Rhino.args_to_ruby(args.to_a) + begin + result = @callable.call(*rb_args) + rescue => e + # ... correct wrapping thus it's try { } catch (e) works in JS : + raise self.class.wrap_error(e) + end + Rhino.to_javascript(result, scope) + end + + end + + class RubyConstructor < RubyFunction + include JS::Wrapper + + # wrap a ruby class as as constructor function + def self.wrap(klass) + new(klass) + end + + def initialize(klass) + super(klass.method(:new)) + @klass = klass + end + + def unwrap + @klass + end + + # override boolean Scriptable#hasInstance(Scriptable instance); + def hasInstance(instance) + return false unless instance + return true if instance.is_a?(@klass) + instance.is_a?(RubyObject) && instance.unwrap.is_a?(@klass) + end + + end + +end diff --git a/lib/rhino/ruby_function.rb b/lib/rhino/ruby_function.rb deleted file mode 100644 index a63a987..0000000 --- a/lib/rhino/ruby_function.rb +++ /dev/null @@ -1,39 +0,0 @@ - -module Rhino - class RubyFunction < JS::BaseFunction - - def initialize(callable) - super() - @callable = callable - end - - def unwrap - @callable - end - - # override Object BaseFunction#call(Context context, Scriptable scope, - # Scriptable thisObj, Object[] args) - def call(context, scope, this, args) - rb_args = Rhino.args_to_ruby(args.to_a) - begin - result = @callable.call(*rb_args) - rescue => e - # ... correct wrapping thus it's try { } catch (e) works in JS : - - raise JS::WrappedException.new(org.jruby.exceptions.RaiseException.new(e)) - - end - Rhino.to_javascript(result, scope) - end - - # protected Object ScriptableObject#equivalentValues(Object value) - def equivalentValues(other) # JS == operator - return false unless other.is_a?(RubyFunction) - return true if unwrap == other.unwrap - # Method.== does check if their bind to the same object - # JS == means they might be bind to different objects : - unwrap.to_s == other.unwrap.to_s # "#" - end - - end -end \ No newline at end of file diff --git a/lib/rhino/ruby_object.rb b/lib/rhino/ruby_object.rb deleted file mode 100644 index 592928f..0000000 --- a/lib/rhino/ruby_object.rb +++ /dev/null @@ -1,109 +0,0 @@ - -module Rhino - class RubyObject < JS::ScriptableObject - include JS::Wrapper - - def initialize(object) - super() - @ruby = object - end - - # abstract Object Wrapper#unwrap(); - def unwrap - @ruby - end - - # abstract String Scriptable#getClassName(); - def getClassName - @ruby.class.name - end - - def toString - "[ruby #{getClassName}]" # [object User] - end - - # override Object Scriptable#get(String name, Scriptable start); - # override Object Scriptable#get(int index, Scriptable start); - def get(name, start) - if name.is_a?(String) - # NOTE: preferrably when using a ruby object in JS methods should - # be used but instance variables will work as well but if there's - # a attr reader it is given a preference e.g. : - # - # class Foo - # attr_reader :bar2 - # def initialize - # @bar1 = 'bar1' - # @bar2 = 'bar2' - # end - # end - # - # fooObj.bar1; // 'bar1' - # fooObj.bar2; // function - # fooObj.bar2(); // 'bar2' - # - if @ruby.respond_to?(name) - return RubyFunction.new(@ruby.method(name)) - elsif @ruby.instance_variables.include?(var_name = "@#{name}") - var_value = @ruby.instance_variable_get(var_name) - return Rhino.to_javascript(var_value, self) - end - end - super - end - - # override boolean Scriptable#has(String name, Scriptable start); - # override boolean Scriptable#has(int index, Scriptable start); - def has(name, start) - if name.is_a?(String) - if @ruby.respond_to?(name) || - @ruby.instance_variables.include?("@#{name}") - return true - end - end - super - end - - # override void Scriptable#put(String name, Scriptable start, Object value); - # override void Scriptable#put(int index, Scriptable start, Object value); - def put(name, start, value) - if name.is_a?(String) - if @ruby.respond_to?(set_name = "#{name}=") - return @ruby.send(set_name, Rhino.to_ruby(value)) - end - end - super - end - - # override boolean Scriptable#hasInstance(Scriptable instance); - def hasInstance(instance) - super - end - - # override Object[] Scriptable#getIds(); - def getIds - ids = @ruby.instance_variables.map { |ivar| ivar[1..-1].to_java } - @ruby.public_methods(false).each do |name| - name = name[0...-1] if name[-1, 1] == '=' # 'foo=' ... 'foo' - name = name.to_java - ids << name unless ids.include?(name) - end - super.each { |id| ids.unshift(id) } - ids.to_java - end - - # protected Object ScriptableObject#equivalentValues(Object value) - def equivalentValues(other) # JS == operator - other.is_a?(RubyObject) && unwrap.eql?(other.unwrap) - end - -# def equals(other) -# other.is_a?(RubyObject) && unwrap.eql?(other.unwrap) -# end -# -# def hashCode -# unwrap.hash -# end - - end -end diff --git a/lib/rhino/wormhole.rb b/lib/rhino/wormhole.rb index 88ffaa7..2b4aa10 100644 --- a/lib/rhino/wormhole.rb +++ b/lib/rhino/wormhole.rb @@ -20,9 +20,10 @@ module Rhino when Array then array_to_javascript(object, scope) when Hash then hash_to_javascript(object, scope) when Time then time_to_javascript(object, scope) - when Proc, Method then RubyFunction.new(object) + when Proc, Method then RubyFunction.wrap(object) when JS::Scriptable then object - else RubyObject.new(object) + when Class then RubyConstructor.wrap(object) + else RubyObject.wrap(object) end end diff --git a/spec/rhino/ruby_function_spec.rb b/spec/rhino/ruby_function_spec.rb index c0d8336..d3f03b2 100644 --- a/spec/rhino/ruby_function_spec.rb +++ b/spec/rhino/ruby_function_spec.rb @@ -3,12 +3,12 @@ require File.expand_path('../spec_helper', File.dirname(__FILE__)) describe Rhino::RubyFunction do it "create and unwrap ruby function" do - rb_function = Rhino::RubyFunction.new method = Object.new.method(:to_s) + rb_function = Rhino::RubyFunction.wrap method = Object.new.method(:to_s) rb_function.unwrap.should be(method) end it "call a ruby function" do - rb_function = Rhino::RubyFunction.new method = 'foo'.method(:to_s) + rb_function = Rhino::RubyFunction.wrap method = 'foo'.method(:to_s) context = nil; scope = nil; this = nil; args = nil rb_function.call(context, scope, this, args).should == 'foo' end @@ -19,7 +19,7 @@ describe Rhino::RubyFunction do array.all? { |elem| elem.is_a?(String) } end end - rb_function = Rhino::RubyFunction.new method = klass.new.method(:foo) + rb_function = Rhino::RubyFunction.wrap method = klass.new.method(:foo) context = nil; scope = nil; this = nil args = [ '1'.to_java, java.lang.String.new('2') ].to_java args = [ Rhino::JS::NativeArray.new(args) ].to_java @@ -32,7 +32,7 @@ describe Rhino::RubyFunction do [ 42 ] end end - rb_function = Rhino::RubyFunction.new method = klass.new.method(:foo) + rb_function = Rhino::RubyFunction.wrap method = klass.new.method(:foo) context = nil; scope = nil; this = nil; args = [].to_java rb_function.call(context, scope, this, args).should be_a(Rhino::JS::NativeArray) end diff --git a/spec/rhino/ruby_object_spec.rb b/spec/rhino/ruby_object_spec.rb index 892cdc8..a1c3f3c 100644 --- a/spec/rhino/ruby_object_spec.rb +++ b/spec/rhino/ruby_object_spec.rb @@ -3,7 +3,7 @@ require File.expand_path('../spec_helper', File.dirname(__FILE__)) describe Rhino::RubyObject do it "unwraps a ruby object" do - rb_object = Rhino::RubyObject.new object = Object.new + rb_object = Rhino::RubyObject.wrap object = Object.new rb_object.unwrap.should be(object) end @@ -11,12 +11,12 @@ describe Rhino::RubyObject do end it "returns the ruby class name" do - rb_object = Rhino::RubyObject.new UII.new + rb_object = Rhino::RubyObject.wrap UII.new rb_object.getClassName.should == UII.name end it "reports being a ruby object on toString" do - rb_object = Rhino::RubyObject.new UII.new + rb_object = Rhino::RubyObject.wrap UII.new rb_object.toString.should == '[ruby UII]' end @@ -28,10 +28,10 @@ describe Rhino::RubyObject do def initialize @anAttr0 = nil @the_attr_1 = 'attr_1' - @an_attr_2 = 'an_attr_2' + @an_attr_2 = 'attr_2' end - def theMethod0; nil; end + def theMethod0; @theMethod0; end def a_method1; 1; end @@ -40,11 +40,11 @@ describe Rhino::RubyObject do end it "gets methods and instance variables" do - rb_object = Rhino::RubyObject.new UII.new + rb_object = Rhino::RubyObject.wrap UII.new - rb_object.get('anAttr0', nil).should be_a(Rhino::RubyFunction) - rb_object.get('the_attr_1', nil).should be_a(Rhino::RubyFunction) - rb_object.get('an_attr_2', nil).should == 'an_attr_2' + rb_object.get('anAttr0', nil).should be_nil + rb_object.get('the_attr_1', nil).should == 'attr_1' + rb_object.get('an_attr_2', nil).should be(Rhino::JS::Scriptable::NOT_FOUND) # no reader [ 'theMethod0', 'a_method1', 'the_method_2' ].each do |name| rb_object.get(name, nil).should be_a(Rhino::RubyFunction) @@ -54,11 +54,11 @@ describe Rhino::RubyObject do end it "has methods and instance variables" do - rb_object = Rhino::RubyObject.new UII.new + rb_object = Rhino::RubyObject.wrap UII.new rb_object.has('anAttr0', nil).should be_true rb_object.has('the_attr_1', nil).should be_true - rb_object.has('an_attr_2', nil).should be_true + rb_object.has('an_attr_2', nil).should be_false # no reader nor writer [ 'theMethod0', 'a_method1', 'the_method_2' ].each do |name| rb_object.has(name, nil).should be_true @@ -70,7 +70,7 @@ describe Rhino::RubyObject do it "puts using attr writer" do start = mock('start') start.expects(:put).never - rb_object = Rhino::RubyObject.new UII.new + rb_object = Rhino::RubyObject.wrap UII.new rb_object.put('the_attr_1', start, 42) rb_object.the_attr_1.should == 42 @@ -79,24 +79,25 @@ describe Rhino::RubyObject do it "puts a non-existent attr (delegates to start)" do start = mock('start') start.expects(:put).once - rb_object = Rhino::RubyObject.new UII.new + rb_object = Rhino::RubyObject.wrap UII.new rb_object.put('nonExistingAttr', start, 42) end it "getIds include ruby class methods" do - rb_object = Rhino::RubyObject.new UII.new + rb_object = Rhino::RubyObject.wrap UII.new - [ 'anAttr0', 'the_attr_1', 'an_attr_2' ].each do |attr| + [ 'anAttr0', 'the_attr_1' ].each do |attr| rb_object.getIds.to_a.should include(attr) end + rb_object.getIds.to_a.should_not include('an_attr_2') [ 'theMethod0', 'a_method1', 'the_method_2' ].each do |method| rb_object.getIds.to_a.should include(method) end end it "getIds include ruby instance methods" do - rb_object = Rhino::RubyObject.new object = UII.new + rb_object = Rhino::RubyObject.wrap object = UII.new object.instance_eval do def foo; 'foo'; end end @@ -105,7 +106,7 @@ describe Rhino::RubyObject do end it "getIds include writers as attr names" do - rb_object = Rhino::RubyObject.new object = UII.new + rb_object = Rhino::RubyObject.wrap object = UII.new rb_object.getIds.to_a.should include('the_attr_1') rb_object.getIds.to_a.should_not include('the_attr_1=')