In the past, I wrote about Java class loading in these entries:
Now, if you have downloaded JDK source from http://jdk6.dev.java.net and you want to understand classloading in HotSpot JVM, you may want to look at these files:bool instanceKlass::link_class_impl method
Note that aforementioned DTrace tricks can be used to trace any subsystem of HotSpot - not neccesarily classloading!
You may have read Joseph D. Mocker's excellent collection of JVM Options - a compilation of all the JVM options for various versions of the JVM on primarily SPARC/Solaris Platform. If you have downloaded JDK source from http://jdk6.dev.java.net, you may want to look at these files:
Please note that I am not suggesting this for tuning JVM on deployment - you may want to read No Tuning Required: Java SE Out-of-Box Vs. Tuned Performance.
You may be curious to know what options are available in product, debug modes of the HotSpot JVM. In particular, you may want to see what diagnostic/debug options which may help in debugging/troubleshooting.
These files have HotSpot command line flags (specified by -XX) and argument parsing code (in arguments.cpp). Also, you may want to look at Java launcher source at [some options by "java" are implemented by launcher sources (eg. -client, -server and -J-XXX) and many other options are implemented by hotspot JVM sources]
The launcher sources help in better understanding of JNI Invocation API as well.
This morning my son (3 year old) was watching some video on my PC. He kept pressing keyword - suddenly the screen started looking like this...
I couldn't find how he managed to change (some) setting. Re-starting Windows didn't help. Kannan Balasubramanian helped me. Thanks Kannan! (Sustaining saves the day again!).
Steps to fix "tilted Windows":
For some reason, while Kannan was able to make it "Normal" and immediately view the change, I had to re-boot! Anyway, now it is "Normal"
Hope you had read my previous post on the prerequisites for reading HotSpot sources. Please note that I am not an expert in HotSpot. I'll post "random tips" which may be useful to you
You may want to read Mikhail Dmitriev's thesis - Chapter 7, Section 7.1. In particular, section 7.1.2.5 titled "Internal Data Structures". This section is a very nice (dated, but still useful) description of HotSpot data structures. Of course, you can read the entire thesis if you are interested in the topic of the thesis - it is an interesting thesis to read!. While reading those sections, you may want to look at the header [.hpp] files under $JDK/hotspot/src/share/vm/oops/ directory.
Most files in this directory have the word "oop" in the name. The "oop" here stands for "ordinary object pointer". What is "ordinary"? May be, this explains!
I joined Sun Microsystems as a sustaining engineer - so I had to read code more often than writing new code (which is true in general for many of us!). I like to read great source code and learn from it. Sun's HotSpot Virtual Machine is one such gold mine!
May be, you've already downloaded the source bundles from http://jdk6.dev.java.net. Or you may be waiting for the "open source" announcement. In any case, if you are planning to read HotSpot source, you may want to know the prerequisites for reading HotSpot Java Virtual Machine sources. If so, read ahead ...
explicit or mutable means nor you need to know "partial specialization of templates" etc.) - a decent level of understanding is enough :-)
In addition to programming/assembly language(s), you may want to read the Java Virtual Machine Specification (2'nd Edition). To understand Java Virtual Machine Specification, I've used Java assembler(s) - working at the level of Java bytecode helps a lot. I used Jasmin Assembler.
You may also use a byte code engineering libraries such as
In addition, it is better to view/analyze existing .class files [may be, you compiled your .java files to create those files]. For that purpose, you can use .class viewers such as
javap -c -p your_class. Output of javap -c on a simple Hello class looks as follows:
Compiled from "Hello.java"
class Hello extends java.lang.Object{
Hello();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."":()V
4: return
public static void main(java.lang.String[]);
Code:
0: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3; //String hello, world
5: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
}
JRuby version 0.9.1 has been released. Updated jsr-223 JRuby script engine at scripting.dev.java.net to use version 0.9.1. With JRuby 0.9.1, there is new syntax for accesing Java classes.
I came to know about higher order messages (HOM) - messages that take another message as argument - from here: Higher Order Messaging in Ruby. Then, I read this paper: Higher Order Messaging
To implement higher-order messages with Ruby, two things are usedIn a blog entry, I mentioned about doesNotUnderstand in Groovy. But, there are no open classes in Groovy. But, Groovy supports class categories - a facility by which we can "add" methods to existing classes (within a specific "use" context).
// A class that forwards any message
// to it to the objects of the list
// and returns the result list
class Do {
def list
Do(l) { list = l }
def invokeMethod(String name, args) {
list.collect { it.invokeMethod(name, args) }
}
}
// A class that filters the objects of the list
// using the message invoked on it
class Where {
def list
Where(l) { list = l }
def invokeMethod(String name, args) {
list.findAll { it.invokeMethod(name, args) }
}
}
// A class that sorts the objects of a list
// using the message invoked on it
class Order {
def list
Order(l) { list = l }
def invokeMethod(String name, args) {
list.sort { it.invokeMethod(name, args) }
}
}
// A class category that "adds" methods to List
// class
class ListCategory {
static def getDo(List list) {
return new Do(list);
}
static def getWhere(List list) {
new Where(list);
}
static def getOrderBy(List list) {
new Order(list);
}
}
// we use our List category
use(ListCategory) {
strings = ["JavaScript", "JRuby", "Java", "Groovy", "BeanShell"]
// find all strings that start with "J"
println strings.where.startsWith("J")
// make a list of upper case strings
println strings.do.toUpperCase()
// sort the strings by length
println strings.orderBy.length()
}
As you can see, with higher-order messages, we avoid closures in the method call-chain - making the method chain easily readable.
Update: I was not aware of JSPON - JavaScript Persistent Object Notation (JSPON) - which addresses the problem below (I wrote the text below before my knowledge of JSPON!). Thanks to Andres Almiray for pointing me about that.
JSON (JavaScript Object Notation) is a lightweight data-interchange format. It is easy for humans to read and write. JSON can be used as replacement for XML (AJAX without the "X" for XML).
But, JSON does not have syntax for circular references - most JSON libraries expect the data structure to-be-serialized to have no cycles. In XML, typically special attributes such as ID and IDREF (or HREF in HTML) are used to create links between elements. (see also XML:id).
Here is a proposal for simple conventions to handle circular references. We can use few special properties with name starting with a "$". "$id" property uniquely identifies a JSON object within a JSON document. Such objects can be referred elsewhere by "$idref". In addition, we may "reserve" all properties starting with a letter "$".
Few examples:
String valued property that identifies an object uniquely within a JSON document/file.
String valued property that refers to another object within the same JSON document/file.
Example:
{
"$id" : "thisobj",
"name": "Simple object",
"date": { "day" : 16, "month": 10, "year": 2006},
"mycontainer" : { "$idref": "thisobj" }
}
In the above example, "mycontainer" property refers back to the JSON object that contains "name", "date" and "mycontainer" properties.
Since, we cannot add "$xxx" properties to JSON arrays, we need a slighly different mechanism for hanlding array references. We can serialize an array that will be referred elsewhere as follows:
{
"x": { "$id" : "myarray" , "$": [2, 3, 54654] },
"y": { "$idref": "myarray" }
}
"$class" property helps while mapping JSON objects back to language specific objects - for example, objects of specific Java bean classes. "$class" property may be used differently with other language bindings. For example, with JavaScript, the value of this property may be treated as a code string that will be evaluated to get "prototype" of the object being parsed. This way
{
"$class" : "java.util.Date",
"day" : 16,
"month" : 9,
"year": 106,
}
After deserialization, the object will have two array valued properties "x" and "y" both would point to the same array.
String valued property that refers to another JSON document/file - like HTML Anchor tag. Also, "fragment" identifiers can be mapped to "$id" within the referred JSON URI. This way we can have "network" of JSON documents. Such "href"-ed fragments may be loaded using XMLHttpRequest or some such mechanism [may be lazily when that property is referred for the first time].
I've checked-in Java classes to serialize/de-serialize JSON with the above "extensions" at http://scripting.dev.java.net - the code is here
There is one issue with this scheme - with JavaScript we can deserialize JSON with "$id" and "$idref" etc. and resolve references, it is difficult to serialize. There is IdentityHashMap or equivalent in (pure) JavaScript [did I miss any such cool-trick?]. To detect whether we have serialized an object already or not [so that we can generate $idref instead of serializing it again], we may have to keep a list of visited objects (i.e, JS array) and walk through that list everytime and so the implementation would be slow for bigger object graphs. Note that this issue does not arise with Rhino implementation - because we can use IdentityHashMap API with Rhino.
Came across Rémi Forax's blog entry titled Languages Evolution: introduction of new keywords. He proposes the idea of contextual keywords. i.e., keywords can be used as identifier in other places - which helps in introducing new keywords in a mature language - without breaking existing programs. I've renamed "enum" during JDK 5.0. I've seen others renaming of "assert" during 1.4. So, how about avoiding renaming - but, still have new (context sensitive) keywords....
Now, that reminds me of PL/I - no reserved words! PL/I's "keywords" are recognized only in context, and may otherwise be used as identifier elsewhere. I've never programmed in PL/I. My friend (who used to be a mainframe programmer) used to "scare" us by showing programs such as this one.
Interestingly, Smalltalk does not have reserved words at all. Sometimes it is said that Smalltalk has only 5 reserved words:
But, it is possible to create methods even with these names!
Just added JSR-223 script engine for JUEL - Java Unified Expression Language - which is an implementation of the Unified Expression Language (EL), specified as a part of the JSP 2.1 standard (JSR-245).
EL started as part of JSTL. Then, the EL moved into the JSP 2.0 spec. Now, although EL is part of JSP 2.1, the EL has been separated into package javax.el and all dependencies to the core JSP classes have been removed. So, the EL can be used by non-JSP applications as well. With this new JSR-223 script engine, it is possible to use EL through javax.script API. As usual, the script engine sources and binaries are available at scripting.dev.java.net.
Sample Program
import javax.script.*;
public class Main {
public static void main(String[] a) throws ScriptException {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine e = manager.getEngineByName("juel");
// expose PI
e.put("PI", Math.PI);
// prints false
System.out.println(e.eval("${222 < 32}"));
// context is pre-defined variable of type
// javax.script.ScriptContext
System.out.println(e.eval("${context}"));
// print "hello world"
e.eval("${out:print('hello world')}");
// lang:import function imports all static methods
// of the given class.
// import all static functions in java.lang.Math
e.eval("${lang:import(context, 'Math', 'java.lang.Math')}");
System.out.println();
System.out.println(e.eval("${Math:sin(3.1415/2)}"));
}
}
Compiling the above code is straightforward. While running it, you need to put the
JUEL jar and the jsr-223 script engine jar in the CLASSPATH. For example:
D:\scripting\engines\juel\bin>java -cp D:\scripting\engines\juel\lib\juel-2.1.0-rc2.jar;D:\scripting\engines\juel\build\juel-engine.jar;. Main
false
javax.script.SimpleScriptContext@10b9d04
hello world
0.999999998926914
If you are looking for a lightweight, expression-only language (rather than a full-blown scripting language), you have (atleast) 4 different options:
With OO languages, we can call the superclass version of a overriden method - but, only within the subclass methods. But, how about calling a overriden superclass method from outside the subclass?
You may think you could perhaps use reflection and call specific superclass method on a subclass instance. But, when you call a method by Method.invoke (in Java) or send (in Ruby) perform: (in Smalltalk) the subclass method is called. You may want to refer to the javadoc for Method.invoke which clearly states:
If the underlying method is an instance method, it is invoked using dynamic method lookup as documented in The Java Language Specification, Second Edition, section 15.12.4.4; in particular, overriding based on the runtime type of the target object will occur.Java Example
import java.lang.reflect.Method;
class Person {
public void greet() {
System.out.println("Person's greet");
}
}
class Employee extends Person {
public void greet() {
System.out.println("Employee's greet");
}
}
class Main {
public static void main(String[] args)
throws Exception {
// get the method object from Person class.
Method g = Person.class.getMethod("greet",
new Class[0]);
Employee e = new Employee();
// When "g" is invoked on an "Employee" object,
// the "Employee.greet" method is called.
g.invoke(e, null);
}
}
Now, back to same question at the start. How do we call
Person.greet on an employee object? It turns out that
is possible with Smalltalk. If you don't know Smalltalk,
you may skip the next section and goto Ruby section -- knowing
that it is possible with Smalltalk!
Smalltalk Example
I tried the following with Squeak 3.8.
"Assume that Person and Employee are classes"
"greet methods of Person and Employee are below"
greet
Transcript show: 'Person''s hello'; cr.
greet
Transcript show: 'Employee's hello'; cr.
For completeness, here is the "file-out" of Person
and Employee classes:
Object subclass: #Person
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'MyTest'!
!Person methodsFor: 'as yet unclassified' stamp: 'AS 10/9/2006 07:11'!
greet
Transcript show: 'Person''s Hello'; cr.! !
Person subclass: #Employee
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'MyTest'!
!Employee methodsFor: 'as yet unclassified' stamp: 'AS 10/9/2006 07:12'!
greet
Transcript show: 'Employee''s Hello'; cr.! !
With the above code, you can do something like the following (in a workspace):
(Employee new) perform: #greet withArguments: (Array new:0) inSuperclass: Person.
So, perform:withArguments:inSuperclass: lets us call any superclass method on a subclass instance (even if that method is overriden in subclass).
Ruby Example
It is possible to do the above with Ruby too. In Ruby, method objects carry the receiver object. It is possible to disassociate the receiver by unbind method and then re-associate the unbound method with another object using bind method.
It is possible to
class Person
def greet
puts "Person's Hello"
end
end
class Employee < Person
def greet
puts "Employee's Hello"
end
end
e = Employee.new
p = Person.new
# get greet method from Person instance
m = p.method(:greet)
# unbind greet from "p" and rebind it "e"
# and then call. So, you are calling
# Person.greet on an Employee instance.
um = m.unbind()
um.bind(e).call()
Note for the JRuby users: With JRuby 0.9.0 version,
the above "bind" call does not work. You get en error
that looks like:
org.jruby.exceptions.RaiseException: bind argument must be an instance of Person
When I looked at the JRuby 0.9.0 source (RubyUnboundMethod.java - bind method), it had this comment:
// FIX replace type() == ... with isInstanceOf(...)
It turns out that this issue (
JRUBY-103)
has been fixed in JRuby 0.9.1.
Why Java does not allow this?
Calling any method on any object!
Smalltalk allows us to call any method on any object! For
example, you can get method (CompiledMethod) object from
one class and call that on an object of unrelated (by
inheritance relation) class. For example, let us assume
SomeClass is defined as follows (file-out)
Object subclass: #SomeClass
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'MyTest'!
!SomeClass methodsFor: 'as yet unclassified' stamp: 'AS 10/9/2006 10:14'!
someMethod
Transcript show: 'someMethod'; cr.! !
We can call the SomeClass.someMethod method on say a
Person instance: (Person is unrelated to SomeClass by
inheriatance)
(SomeClass methodDict at: #someMethod) valueWithReceiver: (Person new) arguments: nil
someMethod method of SomeClass class
with (SomeClass methodDict at: #someMethod)Then, we invoke that method on a Person object
with valueWithReceiver:arguments: method.
When using scripting and dynamically typed languages with the Java platform, you would want to take advantage of the Java platform API. In this blog entry, we will see how to use Java API from BeanShell and Jython.
I used the following versions to test my scripts:
| Feature | BeanShell | Jython |
|---|---|---|
| Importing Java packages |
Same as Java!
By default, BeanShell imports There is also (an experimental) import entire classpath option - which may be useful in interactive use.
|
|
| Importing specific class(es) |
Same as Java!
By default, BeanShell imports bsh.EvalError
and bsh.Interpreter classes.
|
|
| Type alias (Referring Java class with different name) |
It does not seem possible. You can assign class name to
a variable - resulting variable is of type "ClassIdentifier".
But, it does not appear that you can treat class identifier
as though it is a class.
|
|
| Creating a Java object |
Same as Java!
|
|
| Calling instance methods |
Same as Java!
|
|
| Calling static methods |
Same as Java!
|
|
| JavaBean support |
|
|
| instanceof check |
Same as Java!
|
|
| Java overloaded resolution | BeanShell automatically selects the overload method to call (based on java overload resolution algorithm) using argument types. It seems we need to use reflection to force selection of a particular method. |
There is automatic selection of the overload variant
based on arguments. But if there are two methods, say
void func(int x) and void func(byte x),
to call the second method you can write:
Another example:
|
| Handling Java exceptions |
Same as Java!
It is possible to omit the exception type in catch clause:
|
|
| Creating Java Arrays |
Same as Java!
|
"jarray" module is used to create Java arrays.
The jarray module exports two functions:
Another example:
|
| Accessing Java Arrays |
Same as Java! usual [] and .length
are supported.
|
Similar to Java [] operator for access - but
array length is accessed by "len" function.
|
| Implementing a Java interface |
Same as Java! (except loose typing can be used)
|
When I tried this example, I got this error: This issue has been fixed. Please download the lastest binary and source. |
| Implementing multiple interfaces |
Same as Java! (except that loose typing may be used in
few places)
|
Jython supports multiple inheritance. Treat
each Java interface to be implemented as
a "superclass".
|
| Extending a Java class |
Same as Java! (except that loose typing may be used)
|
setVisible works with
Jython 2.1 but not with jsr-223 script engine for it. As I mentioned
earlier in interface impl. item, this is an issue with the jsr-223
engine for Jython.
This issue has been fixed. Please download the lastest binary and source. |
When using scripting
and dynamically typed languages with Java platform,
you would want to take advantage of Java platform API.
We know that there is Java API for (nearly) everything under the "Sun"
.
In this blog entry, we will see how to access Java from
JavaScript,
Groovy
and JRuby.
Note: I used the following versions to test my scripts:
Updates:
| Feature | JavaScript | Groovy | JRuby |
|---|---|---|---|
| Importing Java packages |
importPackage function.
Rhino has a built-in variable by the name Packages.
You have to use Package.javax.swing to refer to javax.swing
package. But, "java" is a short-cut for "Packages.java". In
JDK 6, short-cuts have been added to all commonly used package
prefixes as well (like javax, org, com, net etc.). If you are
using Rhino standalone, you can eval the following:
Note: java.lang package is not automatically imported in Java (that would result conflicts - example: JavaScript Object vs. Java's java.lang.Object). If you are importing too many packages and classes, you may be polluting the global namespace. You can avoid that by using JavaImporter function.
|
Same as Java!
By default, Groovy imports java.lang, java.util, java.io,
java.net, groovy.lang, groovy.util packages.
|
You can not import all classes of a package in top-level
scope. Instead, you have to import within a module.
|
| Importing specific class(es) |
importClass function.
|
Same as Java!
By default, Groovy imports java.math.BigInteger and java.math.BigDecimal
classes.
|
include_class(classname_or_list_of_classnames)
{|package, name| optional_renaming_block }
Note: The Java import packages/class is improving.
See also:
|
| Type alias (Referring Java class with different name) |
|
|
include_class(classname_or_list_of_classnames) {
|package, name| optional_renaming_block
}
|
| Creating a Java object |
|
|
|
| Calling instance methods |
|
|
|
| Calling static methods | java.lang.System.exit(0) | System.exit(0) | include_class('java.lang.System'); System.exit(0) |
| JavaBean support |
|
|
|
| instanceof check |
|
|
|
| Java overloaded resolution |
In most cases, Rhino selects proper overload variant automatically. But, if you want to force selection of a particular method, you can use the following:
Essentially, we specify full singature of the method as a string. Note that JavaScript objects are associative arrays - it is always possible to use property/method name within square bracket (as a string). This is same as ".field" or ".method" syntax. |
Groovy selects the "most appropriate" overload automatically
(see also Mutlimethods in Groovy).
|
|
| Handling Java exceptions |
It is possible to have multiple catch classes like in Java. JavaScript error objects have "javaException" property - which points to the actual underlying Java exception (if error was thrown due to a Java exception). You can do instanceof check on the underlying java exception.
|
Exception handling is same as Java.
|
|
| Creating Java Arrays |
There is no "direct" syntax. But, whenever a
Java array is needed (say as an argument to a
Java method call), JavaScript array can be passed.
Script arrays are automatically converted to Java
arrays as needed. If you want you can use
java.lang.reflect.Array
|
Same as Java!
|
Update:
include_class("java.lang.String") {
"JString"
}
s = JString[].new(2)
puts s
I had the following code before Nick Sieger's comment - You can use java.lang.reflect.Array directly:
|
| Accessing Java Arrays |
Same as Java! usual [] and .length
are supported.
|
Same as Java!
|
Same as Java!
|
| Implementing a Java interface |
Use Java anonymous class-like syntax:
For single method interfaces, you can pass
JavaScript function directly:
|
Similar to Java - but anonymous classes are not
supported (yet). There is an RFE to allow auto-conversion of
Closure to single-method interfaces
|
Another example:
Charles Oliver Nutter notes that there is also a more "anonymous" way to implement interface:
|
| Implementing multiple interfaces |
Rhino standalone download supports implementing
multiple interfaces through the use of
|
Same as Java!
|
It appears that it is not possible currently.
See also: Interfaces Should Be Modules
I think you can use java.lang.reflect.Proxy trick for now.
But, when I tried the following I got StackOverflowError!
I've filed a bug (Attempting to create a java.lang.reflect.Proxy instance results in StackOverflow)
require 'java';
include_class('java.lang.reflect.InvocationHandler');
include_class('java.lang.reflect.Proxy') {
|x| "JProxy"
};
include_class('java.lang.Runnable');
include_class('java.util.concurrent.Callable');
include_class("java.lang.reflect.Array") {
|x| "JArray"
};
include_class("java.lang.Class") {
|x| "JClass"
}
class MyRunnable < InvocationHandler
def invoke(obj, name, args)
puts name + " called"
end
end
include_class('java.lang.Runnable')
types = JArray.newInstance(JClass, 2);
types[0] = Runnable
types[1] = Callable
m = MyRunnable.new
r = JProxy.newProxyInstance(nil, types, m);
puts r.kind_of?(Runnable)
puts r.kind_of?(Callable)
|
| Extending a Java class | Rhino standalone download supports extending a java class through JavaAdapter. But, the bundled JavaScript engine in JDK 6 does not support this. Supporting this requires .class file generation -- which has been removed in JDK 6 (for security and footprint reasons). See also: JDK 6 release notes - scripting section. |
Same as Java!
|
This requires generation of .class files. As of now, JRuby is interpreted only. And so extending a Java class and passing it to Java API is not possible. |
Sandip Chitale has created Swing interface to query Java heap dumps.
Java binary heap dumps can be created using
jmap -dump:format=b,file=heap.bin <pid-of-java-process>
Recall that jhat (Java Heap Analysis Tool) supports webbrowser based interface. jhat parses a heap binary file and starts a webserver. Then, we'd use a webbrowser to query the heap. With this new Swing based tool for heap dumps, it is easier to work with heap dumps. I used the following commands to create and view a heap dump using jhat swing interface:
D:\jdk1.6.0\demo\jfc\Java2D>jmap -dump:format=b,file=heap.bin 1204
Dumping heap to D:\jdk1.6.0\demo\jfc\Java2D\heap.bin ...
Heap dump file created
D:\jdk1.6.0\demo\jfc\Java2D>java -Xmx256m -classpath D:\jdk1.6.0\lib\tools.jar;S
wingJHAT.jar org.netbeans.profiling.tools.SwingJHAT heap.bin
Dump file created Wed Oct 04 19:16:13 GMT+05:30 2006
Resolving 54815 objects...
Chasing references, expect 10 dots..........
Eliminating duplicate references..........
Note: classes in tools.jar may be changed without notice. Not all classes in tools.jar are part of platform interface. Such non-public classes may be renamed or removed in next release!
If you have ideas on improving heap dumps, like different/better file format and/or what needs to be included in heap dumps and so on, then please join and contribute to the heap snapshot project.
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.