The purpose of this lab is to become familiar with metaclasses in Groovy. No prior knowledge of Groovy is assumed.
Download and unzip both the Groovy binaries and source.
a. Consider this class ColoredPoint, similar to what you have seen in lab 12:
import org.codehaus.groovy.runtime.InvokerHelper; // used later
import java.awt.Color;
import java.awt.Point;
class ColoredPoint extends Point
{
Color color; // a property
public String toString() { return super.toString() + "[color=" + color + "]"; }
}
def p = new Point();
p.x = 1; // calls Point.setX
p.y = 1;
def cp = new ColoredPoint();
cp.x = 3;
cp.y = 4;
cp.color = Color.RED;
println(p.toString());
println(cp.toString());
To try it out, save in a file lab14_1.groovy. In a shell window, run
/path/to/groovy/bin/groovy lab14_1
b. Note that the superclass is java.awt.Point. We'd like to add a distance method, just like in lab 12, but we can't modify a Java library class. Except we can, by monkeying with the metaclass.
Extend this code so that the distance method is added to the Point class. Add the code before the definition of ColoredPoint:
class MyDelegatingMetaClass extends DelegatingMetaClass
{
public MyDelegatingMetaClass(Class cl)
{
super(cl);
initialize();
}
public Object invokeMethod(Object obj, String methodName, Object[] arguments)
{
if (methodName == "distance")
return Math.sqrt(obj.x * obj.x + obj.y * obj.y); // note duck typing
else
return super.invokeMethod(obj, methodName, arguments);
}
}
// mumbo-jumbo to install new metaclass
def myMetaClass = new MyDelegatingMetaClass(Point.class);
InvokerHelper.instance.metaRegistry.setMetaClass(Point.class, myMetaClass);
How do you verify that Point now has a distance method?
c. Sadly, my knowledge of Groovy is not good enough to make this example work really convincingly What happens when you try to call distance on a ColoredPoint?
d*. (Extra credit) Can you figure out what needs to be done so that the method is inherited? I tried tracing through src/main/groovy/lang/MetaClassImpl.java, but I got lost. (Regrettably, there seems to be no documentation other than the source code.)
a. Save this XML file to your lab directory. (Right-click and choose Save Link As—your browser will show you the XML structure if you just click on the link). Consider this simple program (which you should save as lab14_2.groovy) that shows of the GPath syntax. What does it print? Why?
def root = new XmlSlurper().parse(new File('recipes.xml'));
println(root.description);
root.recipe[0].ingredient.each({ println(it.@name); });
(Note the use of the closure. In Groovy, a closure with only one parameter can be written as either { x -> println(x); } or simply { println(it); }. The it -> is implied in this case.)
b. What is the type of root? Of root.description? Of root.recipe? (Hint: getClass())
c. What properties does root have? (Hint: Do an each loop on getMetaClass().getProperties() and print it.getName() for each property.)
d. In the previous step, you saw a small number of properties, none of which is description or recipe. Nevertheless, root.description and root.recipe did not yield a syntax error. Why is that? (Hint: Look at the source code in src/main. Look at the superclass of the class you saw in step 2. Just give a basic idea of what is going on; clearly the details are rather tedious.)
a. Consider this Groovy code.
import groovy.xml.*
import java.io.*
writer = new StringWriter();
builder = new MarkupBuilder(writer);
builder.person() {
name(first:"Megan", last:"Smith") {
age("33")
gender("female")
}
}
println writer.toString()
What happens when you run it?
b. Carefully look at name. It is a call to a method with two parameters: a map and a closure. Two pieces of special syntax are at work here:
Explain what the function does with the map and closure parameters.
c. How do you know that metaprogramming is at work here?
d. Given an array of friend names, say
friendnames = [ "Julie", "Joey", "Hannah"]
augment the Groovy code so that it produces
<person>
<name last='Smith' first='Megan'>
<age>33</age>
<gender>female</gender>
</name>
<friends>
<friend>Julie</friend>
<friend>Joey</friend>
<friend>Hannah</friend>
</friends>
</person>
Of yourse, you should use a loop, not hardwire three friend calls. What is your code?