Prototype based OO with Groovy

Part of the attraction of languages such as Groovy, JRuby is flexible method dispatching (which is part of metaprogramming). How about Self or JavaScript style prototype based OOP with Groovy?

With the addition of ProtoObject class given below, it is possible to do prototype based OOP with Groovy. [Note that with JRuby's support for singleton classes and clone method, it is possible to write prototype based JRuby scripts].

import groovy.lang.\*;
import org.codehaus.groovy.runtime.InvokerHelper;
import java.util.\*;

public class ProtoObject extends GroovyObjectSupport 
             implements Cloneable {
    private static final Object[] EMPTY_ARGS = new Object[0];
    private static final String PROTO = "__proto__";

    public ProtoObject() {
        this(new HashMap());
    }

    public ProtoObject(Map properties) {
        this.properties = Collections.synchronizedMap(properties);
    }

    public Object getProperty(String property) {
        try {
            return super.getProperty(property);
        } catch (GroovyRuntimeException e) {
            return getAttribute(property);
        }
    }

    public void setProperty(String property, Object newValue) {
        try {
            super.setProperty(property, newValue);
        } catch (GroovyRuntimeException e) {
            properties.put(property, newValue);
        }
    }

    public Object invokeMethod(String name, Object args) {
        try {
            return super.invokeMethod(name, args);
        } catch (GroovyRuntimeException e) {
            Object value = this.getProperty(name);
            if (value instanceof Closure) {
                Closure c = (Closure)value;
                return invokeClosure(c, (Object[])args);
            } else {
                throw e;
            }
        }
    }
    
    public String toString() {
        Object method = getAttribute("toString");
        if (method instanceof Closure) {
            Closure c = (Closure)method;
            return invokeClosure(c).toString();
        } else {
            return properties.toString();
        }
    }

    public boolean equals(Object obj) {
        Object method = getAttribute("equals");
        if (method instanceof Closure) {
            Closure c = (Closure)method;
            Object res = invokeClosure(c, new Object[] {obj});
            Boolean ret = (Boolean) res;
            return ret.booleanValue();
        } else {
            return super.equals(obj);
        }
    }

    public int hashCode() {
        Object method = getAttribute("hashCode");
        if (method instanceof Closure) {
            Object res = invokeClosure((Closure)method);
            Integer ret = (Integer) res;
            return ret.intValue();
        } else {
            return super.hashCode();
        }
    }


    protected Object invokeClosure(Closure closure, 
                                   Object[] args) {

        Object[] tmp = new Object[args.length + 1];
        tmp[0] = this;
        System.arraycopy(args, 0, tmp, 1, args.length);
        return closure.call(tmp);        
    }

    protected Object invokeClosure(Closure closure) {
        return invokeClosure(closure, EMPTY_ARGS);        
    }

    protected Object getAttribute(String name) {
        if (properties.containsKey(name)) {
            return properties.get(name);
        }
        Object proto = properties.get(PROTO);
        if (proto != null) {
            return InvokerHelper.getProperty(proto, name);
        } else {
            return null;
        }
    }

    protected final Map properties;
}


With the above class in CLASSPATH, it is possible to write Groovy script like the following one:

// create a prototype object
def p = new ProtoObject();
// add "method" to it.
p.func = { self -> println("hello from " + self) }
// create another object
def d = new ProtoObject();
d.greet = { println "hello world" }
// set prototype to be "p"
d.__proto__ = p

// prints "hello world"
d.greet();
// calls p.func (because of __proto__)
d.func()

The ProtoObject class is similar to Groovy's Expando. But, Expando sets current ("this") object as delegate to the Closure -- and therefore will not work property with multithreading. In the ProtoObject class, I'm passing "this" as first argument to Closure. Besides, while a closure's delegate is used for method search within closure code, property access is not direct (user has to write delegate.property anyway) - so having explicit "self" as first argument is probably okay.

Comments:

Sweet! have you contacted the Groovy team to see if it may be included in a future version?

Posted by Andres Almiray on October 07, 2006 at 06:06 PM IST #

Hi Andres Almiray: Thanks for your interest - I've created a feature request - GROOVY-1523

Posted by A. Sundararajan on October 08, 2006 at 05:08 AM IST #

Post a Comment:
Comments are closed for this entry.
About

sundararajan

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today
Bookmarks
Links

No bookmarks in folder

Blogroll

No bookmarks in folder

News

No bookmarks in folder