Skip to content

Commit

Permalink
[ji] packages shall try to handle respond_to? and respond_to_missing?…
Browse files Browse the repository at this point in the history
… right

... also restored singleton method hooks for compatibility with previous versions
  • Loading branch information
kares committed Mar 23, 2016
1 parent f226b78 commit 4036c08
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 28 deletions.
118 changes: 94 additions & 24 deletions core/src/main/java/org/jruby/javasupport/JavaPackage.java
Expand Up @@ -30,6 +30,7 @@
import org.jruby.IncludedModuleWrapper;
import org.jruby.MetaClass;
import org.jruby.Ruby;
import org.jruby.RubyBoolean;
import org.jruby.RubyClass;
import org.jruby.RubyModule;
import org.jruby.RubyString;
Expand All @@ -38,12 +39,15 @@
import org.jruby.exceptions.RaiseException;
import org.jruby.internal.runtime.methods.DynamicMethod;
import org.jruby.internal.runtime.methods.NullMethod;
import org.jruby.runtime.Helpers;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.ClassProvider;

import static org.jruby.runtime.Visibility.PRIVATE;

/**
* A "thin" Java package wrapper (for the runtime to see them as Ruby objects).
*
Expand Down Expand Up @@ -173,13 +177,70 @@ RubyModule relativeJavaProxyClass(final Ruby runtime, final IRubyObject name) {
return Java.getProxyClass(runtime, javaClass);
}

@JRubyMethod(name = "respond_to?")
public IRubyObject respond_to_p(final ThreadContext context, IRubyObject name) {
return respond_to(context, name, false);
}

@JRubyMethod(name = "respond_to?")
public IRubyObject respond_to_p(final ThreadContext context, IRubyObject name, IRubyObject includePrivate) {
return respond_to(context, name, includePrivate.isTrue());
}

private IRubyObject respond_to(final ThreadContext context, IRubyObject mname, final boolean includePrivate) {
String name = mname.asJavaString();
if ( getMetaClass().isMethodBound(name, !includePrivate, true) ) {
return context.runtime.getTrue();
}
/*
if ( ( name = BlankSlateWrapper.handlesMethod(name) ) != null ) {
RubyBoolean bound = checkMetaClassBoundMethod(context, name, includePrivate);
if ( bound != null ) return bound;
return context.runtime.getFalse(); // un-bound (removed) method
}
*/

//if ( ! (mname instanceof RubySymbol) ) mname = context.runtime.newSymbol(name);
//IRubyObject respond = Helpers.invoke(context, this, "respond_to_missing?", mname, context.runtime.newBoolean(includePrivate));
//return context.runtime.newBoolean(respond.isTrue());

return context.nil; // NOTE: this is wrong - should be true but compatibility first, for now
}

private RubyBoolean checkMetaClassBoundMethod(final ThreadContext context, final String name, final boolean includePrivate) {
// getMetaClass().isMethodBound(name, !includePrivate, true)
DynamicMethod method = getMetaClass().searchMethod(name);
if ( ! method.isUndefined() && ! method.isNotImplemented() ) {
if ( ! includePrivate && method.getVisibility() == PRIVATE ) {
return context.runtime.getFalse();
}
return context.runtime.getTrue();
}
return null;
}

@JRubyMethod(name = "respond_to_missing?")
public IRubyObject respond_to_missing_p(final ThreadContext context, IRubyObject name) {
return respond_to_missing(context, name, false);
}

@JRubyMethod(name = "respond_to_missing?")
public IRubyObject respond_to_missing_p(final ThreadContext context, IRubyObject name, IRubyObject includePrivate) {
return respond_to_missing(context, name, includePrivate.isTrue());
}

private RubyBoolean respond_to_missing(final ThreadContext context, IRubyObject mname, final boolean includePrivate) {
final String name = mname.asJavaString();
if ( BlankSlateWrapper.handlesMethod(name) != null ) {
return context.runtime.getFalse(); // not missing!
}
return context.runtime.getTrue();
}

@JRubyMethod(name = "method_missing", visibility = Visibility.PRIVATE)
public IRubyObject method_missing(ThreadContext context, final IRubyObject name) {
final RubyModule result = Java.getProxyOrPackageUnderPackage(context, this, name.toString(), true);
// NOTE: getProxyOrPackageUnderPackage binds the (cached) method for us

if ( result == null ) return context.nil; // TODO this is wrong
return result;
return Java.getProxyOrPackageUnderPackage(context, this, name.toString(), true);
}

@JRubyMethod(name = "method_missing", rest = true, visibility = Visibility.PRIVATE)
Expand Down Expand Up @@ -251,57 +312,66 @@ static final class BlankSlateWrapper extends IncludedModuleWrapper {
}

@Override
protected DynamicMethod searchMethodCommon(final String name) {
protected DynamicMethod searchMethodCommon(String name) {
// this module is special and only searches itself;

// TODO implement a switch to allow for 'more-aligned' behavior

// do not go to superclasses except for special methods :
return (name = handlesMethod(name)) != null ? superClass.searchMethodInner(name) : NullMethod.INSTANCE;
}

private static String handlesMethod(final String name) {
switch (name) {
case "class" : case "singleton_class" :
case "object_id" : case "name" :
case "class" : case "singleton_class" : return name;
case "object_id" : case "name" : return name;
// these are handled already at the JavaPackage.class :
// case "const_get" : case "const_missing" : case "method_missing" :
case "const_set" :
case "inspect" : case "to_s" :
case "const_set" : return name;
case "inspect" : case "to_s" : return name;
// these are handled bellow in switch (name.charAt(0))
// case "__method__" : case "__send__" : case "__id__" :

//case "require" : case "load" :
case "throw" : case "catch" : //case "fail" : case "raise" :
//case "exit" : case "at_exit" :
return superClass.searchMethodInner(name);

case "__constants__" : // @Deprecated compatibility with 1.7
return superClass.searchMethodInner("constants");
case "__methods__" : // @Deprecated compatibility with 1.7
return superClass.searchMethodInner("methods");
return name;

case "singleton_method_added" :
// JavaPackageModuleTemplate handled "singleton_method_added"
case "singleton_method_undefined" :
case "singleton_method_removed" :
case "define_singleton_method" :
return name;

// NOTE: these should maybe get re-thought and deprecated (for now due compatibility)
case "__constants__" : return "constants";
case "__methods__" : return "methods";
}

final int last = name.length() - 1;
if ( last >= 0 ) {
switch (name.charAt(last)) {
case '?' : case '!' : case '=' :
return name;
}
switch (name.charAt(0)) {
case '<' : case '>' : case '=' : // e.g. ==
return superClass.searchMethodInner(name);
return name;
case '_' : // e.g. __send__
if ( last > 0 && name.charAt(1) == '_' ) {
return superClass.searchMethodInner(name);
return name;
}
}
switch (name.charAt(last)) {
case '?' : case '!' : case '=' :
return superClass.searchMethodInner(name);
}
}

//if ( last >= 5 && (
// name.indexOf("method") >= 0 || // method, instance_methods, singleton_methods ...
// name.indexOf("variable") >= 0 || // class_variables, class_variable_get, instance_variables ...
// name.indexOf("constant") >= 0 ) ) { // constants, :public_constant, :private_constant
// return superClass.searchMethodInner(name);
// return true;
//}

return NullMethod.INSTANCE;
return null;
}

@Override
Expand Down
32 changes: 28 additions & 4 deletions test/jruby/test_higher_javasupport.rb
Expand Up @@ -850,8 +850,11 @@ def test_that_misspelt_fq_class_names_dont_stop_future_fq_class_names_with_same_
end

def test_that_subpackages_havent_leaked_into_other_packages
assert_equal(false, Java::java.respond_to?(:zip))
assert_equal(false, Java::com.respond_to?(:util))
assert ! Java::java.respond_to?(:zip)
assert ! Java::com.respond_to?(:util)

assert Java::java.respond_to_missing?(:zip)
assert Java::comx.respond_to_missing?(:foo)
end

def test_that_sub_packages_called_java_javax_com_org_arent_short_circuited
Expand Down Expand Up @@ -902,6 +905,26 @@ def test_package_object_id
assert Java::java::lang.object_id.is_a?(Fixnum)
end

def test_package_singleton_method_hooks
assert org.respond_to?(:singleton_method_added, true)
assert java.lang.respond_to?(:singleton_method_removed, true)

assert_nil org.__send__(:singleton_method_added, :sym)
assert_nil java.lang.__send__(:singleton_method_removed, :sym)
end

def test_package_does_not_respond_to_hidden_methods
assert Kernel.respond_to?(:test)
assert ! org.respond_to?(:test)
assert ! java.lang.respond_to?(:test, true)
end

def test_package_does_respond_to_missing
assert org.respond_to_missing?(:test)
assert java.lang.respond_to_missing?(:test)
assert java.lang.respond_to_missing?(:test, true)
end

@@include_proc = Proc.new do
Thread.stop
java_import "java.lang.System"
Expand Down Expand Up @@ -1240,9 +1263,10 @@ def test_that_classes_beginning_with_small_letter_can_be_referenced
# JRUBY-1076
def test_package_module_aliased_methods
assert java.lang.respond_to?(:__constants__)
assert java.lang.respond_to?(:__methods__)
assert java.lang.respond_to?(:__methods__, true)

java.lang.String # ensure java.lang.String has been loaded
java.lang.String # ensure java.lang.
# String has been loaded
assert java.lang.__constants__.include?(:String)
end

Expand Down

0 comments on commit 4036c08

Please sign in to comment.