I got fed up with just thinking about a new Java layer and start to mock up a working prototype for a "minijava" library that skips all our Java integration code and does it all directly. A patch is attached for it.

The general idea behind this is to solve a few key problems with current Java scripting and provide a "raw" enough interface to build everything else upon.

1. No automatic coercions

No object types automatically coerce. Key types will gain "to_java" methods for a simple coercion and "as" methods for coercion to an explicit type. For example:

"foo".to_java # produces a wrapped java.lang.String

import "java.lang.Integer", "JInt"
123.as(JInt) # produces a wrapped java.lang.Integer

The problem solved here is manifold:

- performance cost of coercing types all the time

Because this model expect explicit coercion, you would typically coerce e.g. strings ahead of time and use them for a series of calls. This isn't a requirement, since some libraries/situations may call for implicit coercion, but at least with this you have an option to *not* do it, which did not exist before.

- inability to coerce to a specific Java type easily

There's no way to call a less-precise version of a primitive method if there's a more-precise version. For example, calling with Fixnum will always try long first, never getting to int versions. This has caused many subtle bugs.

- un-rubylike automatic coercion with no way to override (i.e. you can't define your own to_java easily and expect it to be called)

It should be possible to define a coercion for custom Ruby types entirely in Ruby. Currently, it can't be done easily.

2. All method signatures are bound separately

Check this out:

$ jruby -rminijava -e "t = Time.now; import 'java.lang.System'; p System.methods.grep /getProperty/" ["getProperty", "getProperty(java.lang.String,java.lang.String)java.lang.String", "getProperty(java.lang.String)java.lang.String"]

The no-signature method would eventually be the same "guessing" version we have now, but all signatures would be bound with the long names. These names are not directly invokable, of course, but can be "send" invoked or aliased to usable names.

Multiple problems are also solved here:

- Inability to call a specific signature

It requires aliasing or "send", but all signatures are always present and accessible. This is necessary when the guessing heuristic breaks down.

- Poor performance of overloaded method selection

In cases like StringBuffer, where there's dozens of overloads, method selection can be a slow process. We have improved it by building in caches mapping all the incoming parameter type combinations to the first selected method signature, but that's a crapload of caching if you're calling with many types...potentially even a cache leak right now. With the ability to skip the heuristic entirely, there's no caching necessary and far less logic required to do a call.

3. Class = Class, Module = Interface

This is mostly the same as what we have now, but it's all done actively rather than passively. In other words, when you import 'javax.swing.JFrame', it walks JFrame's superclasses and interfaces and all those types' methods and all those methods' return and parameter types, and so on. And it's still faster to import with minijava than with existing JI code.

~/NetBeansProjects/jruby ➔ jruby -rminijava -e "t = Time.now; import 'javax.swing.JTable'; puts Time.now - t; cls = JTable; until cls == Object; puts cls; cls = cls.superclass; end"
0.418
javax.swing.JTable
javax.swing.JComponent
java.awt.Container
java.awt.Component
java.lang.Object

~/NetBeansProjects/jruby ➔ jruby -rjava -e "t = Time.now; import 'javax.swing.JTable'; puts Time.now - t; cls = JTable; until cls == Object; puts cls; cls = cls.superclass; end"
0.511
Java::JavaxSwing::JTable
Java::JavaxSwing::JComponent
Java::JavaAwt::Container
Java::JavaAwt::Component
Java::JavaLang::Object
ConcreteJavaProxy
JavaProxy

Notice also the simpler hierarchy. When in Ruby land, Java types are rooted at Ruby Object, which is the immediate superclass of Java Object. Just like in Java land, Java Object is the immediate superclass of Ruby Object.

4. A few basic coercions so far...

String and Fixnum can simply coerce to Java String and Java Long, respectively. Fixnum also has an "as" method for other numeric Java types, and Proc has an "as" method that wraps it in a java.lang.reflect.Proxy for use as an interface implementation. These are very easy to add.

*****

So there's obviously more to do with this, but in just about 500 lines of code, it's already very functional. Here's a Swing example that takes advantage of most of the above features (note that most of the method rewiring would probably be handled by either third-party "nicifying" libraries or by standard libs we would ship to match current JI features).

And don't forget...this isn't using Java integration *at all*.

<CODE>
require 'minijava'

import "javax.swing.JFrame"
import "javax.swing.JButton"
import "java.awt.event.ActionListener"
import "java.lang.Integer"

class JFrame
  alias_method :add, :"add(Ljava/awt/Component;)Ljava/awt/Component;"
  alias_method :setSize, :"setSize(II)V"

  def size=(size)
    x, y = *size
    setSize(x.as(Integer), y.as(Integer))
  end

  class << self
alias_method :new_with_title, :"new(Ljava/lang/String;)Ljavax/swing/JFrame;"

    def new(title)
      new_with_title(title.to_java)
    end
  end
end

class JButton
  class << self
alias_method :new_with_text, :"new(Ljava/lang/String;)Ljavax/swing/JButton;"

    def new(text)
      new_with_text(text.to_java)
    end
  end
end

frame = JFrame.new "This is my frame"
frame.show
frame.size = [300, 300]

button = JButton.new "Press me"
button.addActionListener(proc { button.setText("Pressed!".to_java) }.as(ActionListener))

frame.add button
frame.show
</CODE>

Comments are requested.

- Charlie
diff --git a/src/org/jruby/Ruby.java b/src/org/jruby/Ruby.java
index 9f891e3..e6a0b67 100644
--- a/src/org/jruby/Ruby.java
+++ b/src/org/jruby/Ruby.java
@@ -93,6 +93,7 @@ import org.jruby.ext.posix.POSIXFactory;
 import org.jruby.internal.runtime.GlobalVariables;
 import org.jruby.internal.runtime.ThreadService;
 import org.jruby.internal.runtime.ValueAccessor;
+import org.jruby.java.MiniJava;
 import org.jruby.javasupport.JavaSupport;
 import org.jruby.parser.Parser;
 import org.jruby.parser.ParserConfiguration;
@@ -1113,6 +1114,9 @@ public final class Ruby {
     private void initBuiltins() {
         addLazyBuiltin("java.rb", "java", "org.jruby.javasupport.Java");
         addLazyBuiltin("jruby.rb", "jruby", 
"org.jruby.libraries.JRubyLibrary");
+        
+        addLazyBuiltin("minijava.rb", "minijava", "org.jruby.java.MiniJava");
+        
         addLazyBuiltin("jruby/ext.rb", "jruby/ext", 
"org.jruby.RubyJRuby$ExtLibrary");
         addLazyBuiltin("iconv.rb", "iconv", 
"org.jruby.libraries.IConvLibrary");
         addLazyBuiltin("nkf.rb", "nkf", "org.jruby.libraries.NKFLibrary");
diff --git a/src/org/jruby/RubyFixnum.java b/src/org/jruby/RubyFixnum.java
index c04a1da..bd2dc4f 100644
--- a/src/org/jruby/RubyFixnum.java
+++ b/src/org/jruby/RubyFixnum.java
@@ -37,15 +37,19 @@
 package org.jruby;
 
 import java.math.BigInteger;
+import java.util.HashMap;
+import java.util.Map;
 import org.jruby.anno.JRubyClass;
 import org.jruby.anno.JRubyMethod;
 import org.jruby.common.IRubyWarnings.ID;
+import org.jruby.java.MiniJava;
 import org.jruby.runtime.ClassIndex;
 import org.jruby.runtime.ObjectAllocator;
 import org.jruby.runtime.ThreadContext;
 import org.jruby.runtime.builtin.IRubyObject;
 import org.jruby.runtime.marshal.UnmarshalStream;
 import org.jruby.util.Convert;
+import org.jruby.util.TypeCoercer;
 
 /** 
  * Implementation of the Fixnum class.
@@ -754,4 +758,46 @@ public class RubyFixnum extends RubyInteger {
     public static IRubyObject induced_from(IRubyObject recv, IRubyObject 
other) {
         return RubyNumeric.num2fix(other);
     }
+
+    @Override
+    public IRubyObject to_java() {
+        return MiniJava.javaToRuby(getRuntime(), Long.valueOf(value));
+    }
+
+    @Override
+    public IRubyObject as(Class javaClass) {
+        return MiniJava.javaToRuby(getRuntime(), 
coerceToJavaType(getRuntime(), this, javaClass));
+    }
+    
+    private static Object coerceToJavaType(Ruby ruby, RubyFixnum self, Class 
javaClass) {
+        if (!Number.class.isAssignableFrom(javaClass)) {
+            throw ruby.newTypeError(javaClass.getCanonicalName() + " is not a 
numeric type");
+        }
+        
+        TypeCoercer coercer = JAVA_COERCERS.get(javaClass);
+        
+        if (coercer == null) {
+            throw ruby.newTypeError("Cannot coerce Fixnum to " + 
javaClass.getCanonicalName());
+        }
+        
+        return coercer.coerce(self);
+    }
+    
+    private static final Map<Class, TypeCoercer> JAVA_COERCERS = new 
HashMap<Class, TypeCoercer>();
+    
+    static {
+        TypeCoercer intCoercer = new TypeCoercer() {
+            public Object coerce(IRubyObject self) {
+                RubyFixnum fixnum = (RubyFixnum)self;
+                
+                if (fixnum.value > Integer.MAX_VALUE) {
+                    throw self.getRuntime().newRangeError("Fixnum " + 
fixnum.value + " is too large for Java int");
+                }
+                
+                return Integer.valueOf((int)fixnum.value);
+            }
+        };
+        JAVA_COERCERS.put(int.class, intCoercer);
+        JAVA_COERCERS.put(Integer.class, intCoercer);
+    }
 }
diff --git a/src/org/jruby/RubyObject.java b/src/org/jruby/RubyObject.java
index 8801b5c..5f31e75 100644
--- a/src/org/jruby/RubyObject.java
+++ b/src/org/jruby/RubyObject.java
@@ -1908,6 +1908,14 @@ public class RubyObject implements Cloneable, 
IRubyObject, Serializable, CoreObj
        return getRuntime().getFalse();
     }
     
+    public IRubyObject to_java() {
+        throw getRuntime().newTypeError(getMetaClass().getBaseName() + " 
cannot coerce to a Java type.");
+    }
+
+    public IRubyObject as(Class javaClass) {
+        throw getRuntime().newTypeError(getMetaClass().getBaseName() + " 
cannot coerce to a Java type.");
+    }
+    
     /**
      * @see org.jruby.runtime.builtin.IRubyObject#getType()
      */
diff --git a/src/org/jruby/RubyString.java b/src/org/jruby/RubyString.java
index f450e5d..523a13a 100644
--- a/src/org/jruby/RubyString.java
+++ b/src/org/jruby/RubyString.java
@@ -52,6 +52,7 @@ import org.joni.encoding.specific.ASCIIEncoding;
 import static org.jruby.anno.FrameField.*;
 import org.jruby.anno.JRubyMethod;
 import org.jruby.anno.JRubyClass;
+import org.jruby.java.MiniJava;
 import org.jruby.runtime.Arity;
 import org.jruby.runtime.Block;
 import org.jruby.runtime.ClassIndex;
@@ -3374,5 +3375,10 @@ public class RubyString extends RubyObject {
         } catch (Exception e) {
             throw new RuntimeException("Something's seriously broken with 
encodings", e);
         }
-    }    
+    }
+
+    @Override
+    public IRubyObject to_java() {
+        return MiniJava.javaToRuby(getRuntime(), new String(getBytes()));
+    }
 }
diff --git a/src/org/jruby/java/MiniJava.java b/src/org/jruby/java/MiniJava.java
new file mode 100644
index 0000000..cc21b60
--- /dev/null
+++ b/src/org/jruby/java/MiniJava.java
@@ -0,0 +1,510 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package org.jruby.java;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Proxy;
+import java.util.HashMap;
+import java.util.Map;
+import org.jruby.Ruby;
+import org.jruby.RubyClass;
+import org.jruby.RubyModule;
+import org.jruby.RubyObject;
+import org.jruby.RubyProc;
+import org.jruby.RubySymbol;
+import org.jruby.anno.JRubyMethod;
+import org.jruby.internal.runtime.methods.DynamicMethod;
+import org.jruby.internal.runtime.methods.JavaMethod;
+import org.jruby.runtime.Arity;
+import org.jruby.runtime.Block;
+import org.jruby.runtime.ObjectAllocator;
+import org.jruby.runtime.ThreadContext;
+import org.jruby.runtime.Visibility;
+import org.jruby.runtime.builtin.IRubyObject;
+import org.jruby.runtime.callback.Callback;
+import org.jruby.runtime.load.Library;
+import org.jruby.util.CodegenUtils;
+import org.jruby.util.IdUtil;
+
+/**
+ *
+ * @author headius
+ */
+public class MiniJava implements Library {
+    public void load(Ruby runtime, boolean wrap) {
+        runtime.getKernel().defineAnnotatedMethods(MiniJava.class);
+        
+        // load up object and add a few useful methods
+        RubyModule javaObject = getMirrorForClass(runtime, Object.class);
+        
+        javaObject.defineFastMethod("to_s", new Callback() {
+            public IRubyObject execute(IRubyObject recv, IRubyObject[] args, 
Block block) {
+                return 
recv.getRuntime().newString(((JavaObjectWrapper)recv).object.toString());
+            }
+            public Arity getArity() { return Arity.NO_ARGUMENTS; }
+        });
+        
+        javaObject.defineFastMethod("hash", new Callback() {
+            public IRubyObject execute(IRubyObject recv, IRubyObject[] args, 
Block block) {
+                return 
recv.getRuntime().newFixnum(((JavaObjectWrapper)recv).object.hashCode());
+            }
+            public Arity getArity() { return Arity.NO_ARGUMENTS; }
+        });
+        
+        javaObject.defineFastMethod("==", new Callback() {
+            public IRubyObject execute(IRubyObject recv, IRubyObject[] args, 
Block block) {
+                if (args[0] instanceof JavaObjectWrapper) {
+                    return 
recv.getRuntime().newBoolean(((JavaObjectWrapper)recv).object.equals(((JavaObjectWrapper)args[0]).object));
+                } else {
+                    return recv.getRuntime().getFalse();
+                }
+            } 
+            public Arity getArity() { return Arity.ONE_ARGUMENT; }
+        });
+        
+        // open up the 'to_java' and 'as' coercion methods on Ruby Objects, 
via Kernel
+        RubyModule rubyKernel = runtime.getKernel();
+        rubyKernel.defineFastPublicModuleFunction("to_java", new Callback() {
+            public IRubyObject execute(IRubyObject recv, IRubyObject[] args, 
Block block) {
+                return ((RubyObject)recv).to_java();
+            } 
+            public Arity getArity() { return Arity.NO_ARGUMENTS; }
+        });
+        
+        rubyKernel.defineFastPublicModuleFunction("as", new Callback() {
+            public IRubyObject execute(IRubyObject recv, IRubyObject[] args, 
Block block) {
+                return ((RubyObject)recv).as(getJavaClassFromObject(args[0]));
+            } 
+            public Arity getArity() { return Arity.NO_ARGUMENTS; }
+        });
+        
+        // add an "as" method to proc to allow coercing to specific interface
+        RubyClass rubyProc = runtime.getProc();
+        rubyProc.defineFastMethod("as", new Callback() {
+            public IRubyObject execute(IRubyObject recv, IRubyObject[] args, 
Block block) {
+                final Ruby ruby = recv.getRuntime();
+                if (!args[0].respondsTo("java_class")) {
+                    throw 
ruby.newTypeError(args[0].getMetaClass().getBaseName() + " is not a Java type");
+                } else {
+                    final RubyProc proc = (RubyProc)recv;
+                    Class asClass = 
(Class)rubyToJava(args[0].callMethod(recv.getRuntime().getCurrentContext(), 
"java_class"));
+                    
+                    if (!asClass.isInterface()) {
+                        throw 
recv.getRuntime().newTypeError(asClass.getCanonicalName() + " is not an 
interface");
+                    }
+                    
+                    return javaToRuby(ruby, 
Proxy.newProxyInstance(Ruby.getClassLoader(), new Class[] {asClass}, new 
InvocationHandler() {
+                        public Object invoke(Object proxy, Method method, 
Object[] args) throws Throwable {
+                            IRubyObject[] rubyArgs = new 
IRubyObject[args.length + 1];
+                            rubyArgs[0] = RubySymbol.newSymbol(ruby, 
method.getName());
+                            for (int i = 1; i < rubyArgs.length; i++) {
+                                rubyArgs[i] = javaToRuby(ruby, args[i - 1]);
+                            }
+                            return 
rubyToJava(proc.call(proc.getRuntime().getCurrentContext(), rubyArgs));
+                        }
+                    }));
+                }
+            }
+
+            public Arity getArity() {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+        });
+    }
+    
+    @JRubyMethod(name = "import", module = true)
+    public static IRubyObject rb_import(ThreadContext context, IRubyObject 
self, IRubyObject name) {
+        String className = name.toString();
+        try {
+            Class cls = Class.forName(className);
+
+            RubyModule namespace;
+            if (self instanceof RubyModule) {
+                namespace = (RubyModule)self;
+            } else {
+                namespace = self.getMetaClass().getRealClass();
+            }
+            
+            namespace.defineConstant(cls.getSimpleName(), 
getMirrorForClass(context.getRuntime(), cls));
+
+            return context.getRuntime().getNil();
+        } catch (Exception e) {
+            if (context.getRuntime().getDebug().isTrue()) e.printStackTrace();
+            throw context.getRuntime().newTypeError("Could not find class " + 
className + ", exception: " + e);
+        }
+    }
+    
+    @JRubyMethod(name = "import", module = true)
+    public static IRubyObject rb_import(ThreadContext context, IRubyObject 
self, IRubyObject name, IRubyObject as) {
+        String className = name.toString();
+        try {
+            Class cls = Class.forName(className);
+
+            RubyModule namespace;
+            if (self instanceof RubyModule) {
+                namespace = (RubyModule)self;
+            } else {
+                namespace = self.getMetaClass().getRealClass();
+            }
+            
+            namespace.defineConstant(as.toString(), 
getMirrorForClass(context.getRuntime(), cls));
+
+            return context.getRuntime().getNil();
+        } catch (Exception e) {
+            if (context.getRuntime().getDebug().isTrue()) e.printStackTrace();
+            throw context.getRuntime().newTypeError("Could not find class " + 
className + ", exception: " + e);
+        }
+    }
+    
+    static Map<Class, RubyModule> classMap = new HashMap<Class, RubyModule>();
+    public static RubyModule getMirrorForClass(Ruby ruby, Class cls) {
+        if (cls == null) {
+            return ruby.getObject();
+        }
+        
+        RubyModule rubyCls = classMap.get(cls);
+        
+        if (rubyCls == null) {
+            rubyCls = createMirrorForClass(ruby, cls);
+            
+            classMap.put(cls, rubyCls);
+            populateMirrorForClass(rubyCls, cls);
+            rubyCls = classMap.get(cls);
+        }
+        
+        return rubyCls;
+    }
+    
+    protected static RubyModule createMirrorForClass(Ruby ruby, Class cls) {
+        if (cls.isInterface()) {
+            // interfaces are handled as modules
+            RubyModule rubyMod = RubyModule.newModule(ruby);
+            return rubyMod;
+        } else {
+            // construct the mirror class and parent classes
+            RubyClass rubyCls = RubyClass.newClass(ruby, 
(RubyClass)getMirrorForClass(ruby, cls.getSuperclass()));
+            return rubyCls;
+        }
+    }
+    
+    protected static void populateMirrorForClass(RubyModule rubyMod, final 
Class cls) {
+        Ruby ruby = rubyMod.getRuntime();
+        
+        // set the full name
+        rubyMod.setBaseName(cls.getCanonicalName());
+        
+        // include all interfaces
+        Class[] interfaces = cls.getInterfaces();
+        for (Class ifc : interfaces) {
+            rubyMod.includeModule(getMirrorForClass(ruby, ifc));
+        }
+        
+        // if it's an inner class and it's not public, we can't access it;
+        // skip population of declared elements
+        if (cls.getEnclosingClass() != null && 
!Modifier.isPublic(cls.getModifiers())) {
+            return;
+        }
+        
+        // add all instance and static methods
+        Method[] methods = cls.getDeclaredMethods();
+        for (final Method method : methods) {
+            String name = method.getName();
+            RubyModule target;
+            
+            // only public methods
+            if (!Modifier.isPublic(method.getModifiers())) {
+                continue;
+            }
+            
+            if (Modifier.isStatic(method.getModifiers())) {
+                target = rubyMod.getSingletonClass();
+            } else {
+                target = rubyMod;
+            }
+            
+            JavaMethodFactory factory = 
getMethodFactory(method.getReturnType());
+            DynamicMethod dynMethod = factory.createMethod(target, method);
+            
+            // if not overloaded, we add a method that guesses at which 
signature to use
+            // TODO: just adding first one right now...add in 
signature-guessing logic
+            if (target.getMethods().get(name) == null) {
+                target.addMethod(name, dynMethod);
+            }
+            
+            // add method with full signature, so it's guaranteed to be 
directly accessible
+            // TODO: no need for this to be a full, formal JVM signature
+            name = name + CodegenUtils.pretty(method.getReturnType(), 
method.getParameterTypes());
+            target.addMethod(name, dynMethod);
+        }
+        
+        // add all constructors
+        Constructor[] constructors = cls.getConstructors();
+        for (final Constructor constructor : constructors) {
+            // only public constructors
+            if (!Modifier.isPublic(constructor.getModifiers())) {
+                continue;
+            }
+            
+            DynamicMethod dynMethod;
+            if (constructor.getParameterTypes().length == 0) {
+                dynMethod = new 
JavaMethod.JavaMethodZero(rubyMod.getSingletonClass(), Visibility.PUBLIC) {
+                    @Override
+                    public IRubyObject call(ThreadContext context, IRubyObject 
self, RubyModule clazz, String name) {
+                        try {
+                            return javaToRuby(context.getRuntime(), 
constructor.newInstance());
+                        } catch (Exception e) {
+                            if (context.getRuntime().getDebug().isTrue()) 
e.printStackTrace();
+                            throw context.getRuntime().newTypeError("Could not 
instantiate " + cls.getCanonicalName() + " using " + CodegenUtils.pretty(cls, 
constructor.getParameterTypes()));
+                        }
+                    }
+                };
+            } else {
+                dynMethod = new 
JavaMethod.JavaMethodNoBlock(rubyMod.getSingletonClass(), Visibility.PUBLIC) {
+                    @Override
+                    public IRubyObject call(ThreadContext context, IRubyObject 
self, RubyModule clazz, String name, IRubyObject[] rubyArgs) {
+                        Object[] args = new Object[rubyArgs.length];
+                        
+                        for (int i = 0; i < args.length; i++) args[i] = 
rubyToJava(rubyArgs[i]);
+                        
+                        try {
+                            return javaToRuby(context.getRuntime(), 
constructor.newInstance(args));
+                        } catch (Exception e) {
+                            if (context.getRuntime().getDebug().isTrue()) 
e.printStackTrace();
+                            throw context.getRuntime().newTypeError("Could not 
instantiate " + cls.getCanonicalName() + " using " + CodegenUtils.pretty(cls, 
constructor.getParameterTypes()));
+                        }
+                    }
+                };
+            }
+            
+            // if not already defined, we add a 'new' that guesses at which 
signature to use
+            // TODO: just adding first one right now...add in 
signature-guessing logic
+            if (rubyMod.getSingletonClass().getMethods().get("new") == null) {
+                rubyMod.getSingletonClass().addMethod("new", dynMethod);
+            }
+            // add 'new' with full signature, so it's guaranteed to be 
directly accessible
+            // TODO: no need for this to be a full, formal JVM signature
+            rubyMod.getSingletonClass().addMethod("new" + 
CodegenUtils.pretty(cls, constructor.getParameterTypes()), dynMethod);
+        }
+        
+        // add a few type-specific special methods
+        rubyMod.getSingletonClass().defineFastMethod("java_class", new 
Callback() {
+            public IRubyObject execute(IRubyObject recv, IRubyObject[] args, 
Block block) {
+                return javaToRuby(recv.getRuntime(), cls);
+            }
+
+            public Arity getArity() {
+                return Arity.NO_ARGUMENTS;
+            }
+        });
+        
+        // add all static variables
+        Field[] fields = cls.getDeclaredFields();
+        for (Field field : fields) {
+            // only public static fields that are valid constants
+            if (Modifier.isStatic(field.getModifiers()) && 
Modifier.isPublic(field.getModifiers()) && IdUtil.isConstant(field.getName())) {
+                Object value = null;
+                try {
+                    value = field.get(null);
+                } catch (Exception e) {
+                    throw ruby.newTypeError("Could not access field " + 
cls.getCanonicalName() + "::" + field.getName() + " using " + 
CodegenUtils.ci(field.getType()));
+                }
+                rubyMod.defineConstant(field.getName(), new 
JavaObjectWrapper((RubyClass)getMirrorForClass(ruby, value.getClass()), value));
+            }
+        }
+    }
+    
+    static final Map<Class, JavaMethodFactory> methodFactories = new HashMap();
+    
+    static final JavaMethodFactory JAVA_OBJECT_METHOD_FACTORY = new 
JavaMethodFactory() {
+        public DynamicMethod createMethod(RubyClass klazz, Method method) {
+            return new JavaObjectWrapperMethod(klazz, method);
+        }
+    };
+    
+    protected static JavaMethodFactory getMethodFactory(Class returnType) {
+        JavaMethodFactory factory = methodFactories.get(returnType);
+        
+        if (factory == null) {
+            return JAVA_OBJECT_METHOD_FACTORY;
+        }
+        
+        return factory;
+    }
+    
+    static {
+        methodFactories.put(void.class, new JavaMethodFactory() {
+            @Override
+            public DynamicMethod createMethod(RubyModule klazz, Method method) 
{
+                Class[] parameters = method.getParameterTypes();
+                if (parameters.length > 0) {
+                    return new JavaVoidWrapperMethod(klazz, method);
+                } else {
+                    return new JavaVoidWrapperMethodZero(klazz, method);
+                }
+            }
+        });
+    }
+    
+    public static class JavaMethodFactory {
+        public DynamicMethod createMethod(RubyModule klazz, Method method) {
+            Class[] params = method.getParameterTypes();
+            if (params.length > 0) {
+                return new JavaObjectWrapperMethod(klazz, method);
+            } else {
+                return new JavaObjectWrapperMethodZero(klazz, method);
+            }
+        }
+    }
+    
+    protected static class JavaObjectWrapperMethodZero extends 
JavaMethod.JavaMethodZero {
+        private Method method;
+        private boolean isStatic;
+        
+        public JavaObjectWrapperMethodZero(RubyModule klazz, Method method) {
+            super(klazz, Visibility.PUBLIC);
+            
+            this.method = method;
+            this.isStatic = Modifier.isStatic(method.getModifiers());
+        }
+        
+        @Override
+        public IRubyObject call(ThreadContext context, IRubyObject self, 
RubyModule clazz, String name) {
+            try {
+                Object result = (Object)method.invoke(isStatic ? null : 
((JavaObjectWrapper)self).object);
+                
+                return javaToRuby(context.getRuntime(), result);
+            } catch (Exception e) {
+                if (context.getRuntime().getDebug().isTrue()) 
e.printStackTrace();
+                throw context.getRuntime().newTypeError("Could not dispatch to 
" + method.getDeclaringClass().getCanonicalName() + "#" + method.getName() + " 
using " + CodegenUtils.pretty(method.getReturnType(), 
method.getParameterTypes()));
+            }
+        }
+    }
+    
+    protected static class JavaObjectWrapperMethod extends JavaMethod {
+        private Method method;
+        private boolean isStatic;
+        
+        public JavaObjectWrapperMethod(RubyModule klazz, Method method) {
+            super(klazz, Visibility.PUBLIC);
+            
+            this.method = method;
+            this.isStatic = Modifier.isStatic(method.getModifiers());
+        }
+        
+        @Override
+        public IRubyObject call(ThreadContext context, IRubyObject self, 
RubyModule clazz, String name, IRubyObject[] args, Block block) {
+            Object[] newArgs = new Object[args.length];
+            for (int i = 0; i < args.length; i++) {
+                IRubyObject arg = args[i];
+                newArgs[i] = rubyToJava(arg);
+            }
+
+            try {
+                Object result = (Object)method.invoke(isStatic ? null : 
((JavaObjectWrapper)self).object, newArgs);
+                
+                return javaToRuby(context.getRuntime(), result);
+            } catch (Exception e) {
+                if (context.getRuntime().getDebug().isTrue()) 
e.printStackTrace();
+                throw context.getRuntime().newTypeError("Could not dispatch to 
" + method.getDeclaringClass().getCanonicalName() + "#" + method.getName() + " 
using " + CodegenUtils.pretty(method.getReturnType(), 
method.getParameterTypes()));
+            }
+        }
+    }
+    
+    protected static class JavaVoidWrapperMethod extends JavaMethod {
+        private Method method;
+        private boolean isStatic;
+        
+        public JavaVoidWrapperMethod(RubyModule klazz, Method method) {
+            super(klazz, Visibility.PUBLIC);
+            
+            this.method = method;
+            this.isStatic = Modifier.isStatic(method.getModifiers());
+        }
+        
+        @Override
+        public IRubyObject call(ThreadContext context, IRubyObject self, 
RubyModule clazz, String name, IRubyObject[] args, Block block) {
+            Object[] newArgs = new Object[args.length];
+            for (int i = 0; i < args.length; i++) {
+                IRubyObject arg = args[i];
+                newArgs[i] = rubyToJava(arg);
+            }
+
+            try {
+                method.invoke(isStatic ? null : 
((JavaObjectWrapper)self).object, newArgs);
+                
+                return self;
+            } catch (Exception e) {
+                if (context.getRuntime().getDebug().isTrue()) 
e.printStackTrace();
+                throw context.getRuntime().newTypeError("Could not dispatch to 
" + method.getDeclaringClass().getCanonicalName() + "#" + method.getName() + " 
using " + CodegenUtils.pretty(method.getReturnType(), 
method.getParameterTypes()));
+            }
+        }
+    }
+    
+    protected static class JavaVoidWrapperMethodZero extends 
JavaMethod.JavaMethodZero {
+        private Method method;
+        private boolean isStatic;
+        
+        public JavaVoidWrapperMethodZero(RubyModule klazz, Method method) {
+            super(klazz, Visibility.PUBLIC);
+            
+            this.method = method;
+            this.isStatic = Modifier.isStatic(method.getModifiers());
+        }
+        
+        @Override
+        public IRubyObject call(ThreadContext context, IRubyObject self, 
RubyModule clazz, String name) {
+            try {
+                method.invoke(isStatic ? null : 
((JavaObjectWrapper)self).object);
+                
+                return self;
+            } catch (Exception e) {
+                if (context.getRuntime().getDebug().isTrue()) 
e.printStackTrace();
+                throw context.getRuntime().newTypeError("Could not dispatch to 
" + method.getDeclaringClass().getCanonicalName() + "#" + method.getName() + " 
using " + CodegenUtils.pretty(method.getReturnType(), 
method.getParameterTypes()));
+            }
+        }
+    }
+    
+    public static Object rubyToJava(IRubyObject object) {
+        if (object.isNil()) {
+            return null;
+        } else if (object instanceof JavaObjectWrapper) {
+            return ((JavaObjectWrapper)object).object;
+        } else {
+            return object;
+        }
+    }
+    
+    public static IRubyObject javaToRuby(Ruby ruby, Object object) {
+        if (object == null) {
+            return ruby.getNil();
+        } else if (object instanceof IRubyObject) {
+            return (IRubyObject)object;
+        } else {
+            return new JavaObjectWrapper((RubyClass)getMirrorForClass(ruby, 
object.getClass()), object);
+        }
+    }
+    
+    public static class JavaObjectWrapper extends RubyObject {
+        Object object;
+        
+        public JavaObjectWrapper(RubyClass klazz, Object object) {
+            super(klazz.getRuntime(), klazz);
+            this.object = object;
+        }
+    };
+    
+    public static Class getJavaClassFromObject(IRubyObject obj) {
+        if (!obj.respondsTo("java_class")) {
+            throw 
obj.getRuntime().newTypeError(obj.getMetaClass().getBaseName() + " is not a 
Java type");
+        } else {
+            return 
(Class)rubyToJava(obj.callMethod(obj.getRuntime().getCurrentContext(), 
"java_class"));
+        }
+    }
+}
diff --git a/src/org/jruby/javasupport/JavaClass.java 
b/src/org/jruby/javasupport/JavaClass.java
index 907b451..7e07627 100644
--- a/src/org/jruby/javasupport/JavaClass.java
+++ b/src/org/jruby/javasupport/JavaClass.java
@@ -341,9 +341,8 @@ public class JavaClass extends JavaObject {
         }
 
         public IRubyObject execute(IRubyObject self, IRubyObject[] args, Block 
block) {
-            if (!initialized) { // read-volatile
-                createJavaMethods(self.getRuntime());
-            }
+            createJavaMethods(self.getRuntime());
+
             // TODO: ok to convert args in place, rather than new array?
             int len = args.length;
             IRubyObject[] convertedArgs = new IRubyObject[len];
@@ -383,12 +382,12 @@ public class JavaClass extends JavaObject {
         }
 
         public IRubyObject execute(IRubyObject self, IRubyObject[] args, Block 
block) {
-            if (!initialized) { // read-volatile
-                createJavaMethods(self.getRuntime());
-            }
+            createJavaMethods(self.getRuntime());
+
             // TODO: ok to convert args in place, rather than new array?
             int len = args.length;
-            if (block.isGiven()) { // convert block to argument
+            boolean blockGiven = block.isGiven();
+            if (blockGiven) { // convert block to argument
                 len += 1;
                 IRubyObject[] newArgs = new IRubyObject[args.length+1];
                 System.arraycopy(args, 0, newArgs, 0, args.length);
@@ -398,7 +397,7 @@ public class JavaClass extends JavaObject {
             IRubyObject[] convertedArgs = new IRubyObject[len+1];
             convertedArgs[0] = 
self.getInstanceVariables().fastGetInstanceVariable("@java_object");
             int i = len;
-            if (block.isGiven()) {
+            if (blockGiven) {
                 convertedArgs[len] = args[len - 1];
                 i -= 1;
             }
diff --git a/src/org/jruby/util/CodegenUtils.java 
b/src/org/jruby/util/CodegenUtils.java
index c5457fb..9451592 100644
--- a/src/org/jruby/util/CodegenUtils.java
+++ b/src/org/jruby/util/CodegenUtils.java
@@ -41,8 +41,14 @@ public class CodegenUtils {
                     return "[B";
                 } else if (n == Boolean.TYPE) {
                     return "[Z";
+                } else if (n == Short.TYPE) {
+                    return "[S";
+                } else if (n == Character.TYPE) {
+                    return "[C";
                 } else if (n == Integer.TYPE) {
                     return "[I";
+                } else if (n == Float.TYPE) {
+                    return "[Float";
                 } else if (n == Double.TYPE) {
                     return "[D";
                 } else if (n == Long.TYPE) {
@@ -59,8 +65,14 @@ public class CodegenUtils {
                     return "B";
                 } else if (n == Boolean.TYPE) {
                     return "Z";
+                } else if (n == Short.TYPE) {
+                    return "S";
+                } else if (n == Character.TYPE) {
+                    return "C";
                 } else if (n == Integer.TYPE) {
                     return "I";
+                } else if (n == Float.TYPE) {
+                    return "F";
                 } else if (n == Double.TYPE) {
                     return "D";
                 } else if (n == Long.TYPE) {
@@ -75,6 +87,13 @@ public class CodegenUtils {
             }
         }
     }
+
+    /**
+     * Creates a human-readable representation, from a Class.
+     */
+    public static String human(Class n) {
+        return n.getCanonicalName();
+    }
     
     /**
      * Create a method signature from the given param types and return values
@@ -102,6 +121,19 @@ public class CodegenUtils {
         return signature.toString();
     }
     
+    public static String pretty(Class retval, Class... params) {
+        StringBuffer signature = new StringBuffer("(");
+        
+        for (int i = 0; i < params.length; i++) {
+            signature.append(human(params[i]));
+            if (i < params.length - 1) signature.append(',');
+        }
+        
+        signature.append(")").append(human(retval));
+        
+        return signature.toString();
+    }
+    
     public static Class[] params(Class... classes) {
         return classes;
     }
diff --git a/src/org/jruby/util/TypeCoercer.java 
b/src/org/jruby/util/TypeCoercer.java
index f9d77a0..ef23cd8 100644
--- a/src/org/jruby/util/TypeCoercer.java
+++ b/src/org/jruby/util/TypeCoercer.java
@@ -5,12 +5,6 @@
 
 package org.jruby.util;
 
-import org.jruby.RubyArray;
-import org.jruby.RubyClass;
-import org.jruby.RubyFloat;
-import org.jruby.RubyHash;
-import org.jruby.RubyInteger;
-import org.jruby.RubyString;
 import org.jruby.runtime.builtin.IRubyObject;
 
 /**
@@ -18,86 +12,5 @@ import org.jruby.runtime.builtin.IRubyObject;
  * @author headius
  */
 public interface TypeCoercer {
-    /**
-     * Methods which perform to_xxx if the object has such a method
-     * @return
-     */
-    RubyArray convertToArray(IRubyObject src);
-    /**
-     *
-     * @return
-     */
-    RubyHash convertToHash(IRubyObject src);    
-    /**
-    *
-    * @return
-    */    
-    RubyFloat convertToFloat(IRubyObject src);
-    /**
-     *
-     * @return
-     */
-    RubyInteger convertToInteger(IRubyObject src);
-    /**
-     *
-     * @return
-     */
-    RubyInteger convertToInteger(IRubyObject src, int convertMethodIndex, 
String convertMethod);
-    /**
-     *
-     * @return
-     */
-    RubyString convertToString(IRubyObject src);
-    
-    /**
-     * Converts this object to type 'targetType' using 'convertMethod' method 
(MRI: convert_type).
-     *
-     * @param targetType is the type we are trying to convert to
-     * @param convertMethod is the method to be called to try and convert to 
targeType
-     * @param raiseOnError will throw an Error if conversion does not work
-     * @return the converted value
-     */
-    IRubyObject convertToType(IRubyObject src, RubyClass targetType, int 
convertMethodIndex, String convertMethod, boolean raiseOnError);
-
-    /**
-     * Converts this object to type 'targetType' using 'convertMethod' method 
and raises TypeError exception on failure (MRI: rb_convert_type).
-     *
-     * @param targetType is the type we are trying to convert to
-     * @param convertMethod is the method to be called to try and convert to 
targeType
-     * @return the converted value
-     */    
-    IRubyObject convertToType(IRubyObject src, RubyClass targetType, int 
convertMethodIndex, String convertMethod);    
-
-    /**
-     * Higher level conversion utility similar to convertToType but it can 
throw an
-     * additional TypeError during conversion (MRI: rb_check_convert_type).
-     *
-     * @param targetType is the type we are trying to convert to
-     * @param convertMethod is the method to be called to try and convert to 
targeType
-     * @return the converted value
-     */
-    IRubyObject convertToTypeWithCheck(IRubyObject src, RubyClass targetType, 
int convertMethodIndex, String convertMethod);
-    
-    /**
-     *
-     * @return
-     */
-    IRubyObject anyToString(IRubyObject src);
-    
-    /** rb_obj_as_string
-     * @return
-     */
-    RubyString asString(IRubyObject src);
-    
-    /**
-     *
-     * @return
-     */
-    IRubyObject checkStringType(IRubyObject src);
-    
-    /**
-     *
-     * @return
-     */
-    IRubyObject checkArrayType(IRubyObject src);
+    public Object coerce(IRubyObject self);
 }

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email

Reply via email to