Anonymous types for J
In a recent post I showed how lambda functions are emulated in my LINQ-like Java DSL. Today I extended the language to support n-tuple anonymous types. The most common use case will be to project parts of a query result into a new type, so the implementation shares some of its semantics with the simpler custom projection functions shown in the lambda function post.
In C# 3.0 you can do an anonymous type projection like shown below:
public void Linq9() {
string[] words = { "aPPLE", "BlUeBeRrY", "cHeRry" };
var upperLowerWords =
from w in words
select new {Upper = w.ToUpper(), Lower = w.ToLower()};
foreach (var ul in upperLowerWords) {
Console.WriteLine("Uppercase: {0}, Lowercase: {1}", ul.Upper, ul.Lower);
}
}
The above example creates a two tuple anonymous type. When used in this fashion the C# compiler generates a type with two public properties (Upper and Lower). The types of the properties is inferred from the context.
Since I'm building my DSL with Plain Old JavaTM I do not have the option to extend the compiler the way Microsoft have done to support LINQ, so I'm resorting to runtime code generation instead. Before we go into the details on how this is done, let's take a look at the Java equivalent of the LINQ sample.
@Test
public void canProjectToAnonynousType()
{
String[] words = {"aPPLE", "BlUeBeRrY", "cHeRry"};
Object[] upperLowerWords =
(Object[]) from(words).select(
newBean(
property("upper", new Function()
{
public Object eval(Object elm)
{
return ((String) elm).toUpperCase();
}
}),
property("lower", new Function()
{
public Object eval(Object elm)
{
return ((String) elm).toLowerCase();
}
}
)
));
for (int i = 0; i < words.length; i++)
{
Method upperGetter =
upperLowerWords[i].getClass().getMethod("getUpper");
Assert.assertEquals(
words[i].toUpperCase(), upperGetter.invoke(upperLowerWords[i])
);
Method lowerGetter =
upperLowerWords[i].getClass().getMethod("getLower");
Assert.assertEquals(
words[i].toLowerCase(), lowerGetter.invoke(upperLowerWords[i])
);
}
}
Just like my lambda functions, the implementation is more verbose than the C# version. Another key difference is that I must resort to reflection to invoke the getters for the two properties. This is because there is no compile time type inference here.
I use Jen to define and instantiate the types at runtime. Below you can see the type and property definition methods.
public Class define(AbstractProperty... properties)
{
SoftClass sc = new SoftClass(Opcodes.ACC_PUBLIC, className);
sc.putSoftMethod(new SimpleNullConstructor(sc));
for (AbstractProperty property : properties)
{
createProperty(sc, property);
}
newClazz = sc.defineClass();
return newClazz;
}private static void createProperty(SoftClass sc, AbstractProperty property)
{
Class valueClazz;
if (property instanceof ProjectionProperty)
{
ProjectionProperty projectionProp = (ProjectionProperty) property;
if (!projectionProp.getProjection().returnsArray())
{
valueClazz = Object.class;
}
else
{
valueClazz = new Object[0].getClass();
}
}
else
{
valueClazz = property.getValue().getClass();
}
GeneratedSoftField valueField = new GeneratedSoftField(
sc,
Opcodes.ACC_PUBLIC,
property.getName(),
null,
valueClazz
);
sc.putSoftField(valueField);
sc.putSoftMethod(new BeanPropertyGetter(sc, property.getName(), property.getName(), valueClazz));
}
In essence the type generation code consumes any number of properties inheriting from the AbstractProperty super class. This can be a number of different property types such as assignments, fixed values, projections or custom functions. Which type you'll ultimately end up using is hidden behind the static import used to define the language. This static import consists of a number of different factory methods. Below you can see the one used for lambda functions.
public static ProjectionProperty property(String name, Function projection)
{
return new ProjectionProperty(name,projection);
}
When selection is iterating over the query results a special projection operator (newBean) is asked to process the result elements. This projection will define a new class using the define method shown above and then use the create method shown below to create the instances.
public Object create(Object elm, AbstractProperty... properties)
throws IllegalAccessException, InstantiationException, NoSuchFieldException
{
Object obj = newClazz.newInstance();
for (AbstractProperty property : properties)
{
Object value;
if (property instanceof ProjectionProperty)
{
ProjectionProperty projectionProp = (ProjectionProperty) property;
if (projectionProp.getProjection() instanceof Function)
{
Function func = (Function) projectionProp.getProjection();
value = func.eval(elm);
}
else
{
value = projectionProp.getValue(elm);
}
}
else
{
value = property.getValue(elm);
}
Field field = newClazz.getField(property.getName());
field.set(obj, value);
}
return obj;
}
While this code is a little messy, it is rather basic; depending on the property type it will either retrieve a value from an object or perform a projection on the object. Finally it will assign the property value to its holder field and return the newly created instance.
Adding support for anonymous types was a bit more challenging than I had expected it to be, but this was mostly due a substantial refactoring debt that had to be paid rather than the actual implementation. If you ever need to do bytecode enhancements I can really recommend Jen. This framework is without doubt the most humane bytecode generation tool I've every used regardless of platform.