Friday, May 11, 2007

LINQ to Google Desktop

Given the number of recent blog posts and articles talking about creating LINQ providers, I decided to go ahead and try create a provider for Google Desktop.

The work in this post is based on the following posts/articles:



Also samples were created using the March 2007 CTP of Orcas.

The first thing to do is try to figure out how a Linq to Google Desktop query look like.

As discussed in a previous post, the result of a Google Desktop query could yield different kinds of results. For example a query for the "linq" term could return references to files, emails, calendar items, etc.

In order to deal with this, the first thing to do was to create a class hierarchy for the kinds of elements that could result from a Google Desktop query. This class hierarchy will have GDResult as the base class (which will be almost the same as an Indexable in the Google Desktop schemas).

Also we need to create a wrapper around the Google Desktop COM API, to capture the results and instantiate the appropriate class given the selected result.

For simplicity of this experiment only GDFileResult and GDEmailResult will be implemented and supported by the provider.


public class GDResult
{
public string Schema {...}
public string Content {... }

public bool Contains(string term)
{
...
}
}

public class GDFileResult : GDResult
{
public string FileType { ... }
public string Location { ... }
}

public class GDEmailResult : GDResult
{
public string From { ... }
public string To { ... }
public string Cc { ... }
public string Subject { ... }
}


Given this we could image how the Linq queries will look like. For example if we want to get all the PDF files that contain the "statement" and "expression" terms we could write:



GDDesktop gd = new GDProvider();
var t = from t in gd
where t.Contains("statement") &&
t.Contains("expression")&&
t is GDFileResult &&
((GDFileResult)t).FileType == "pdf"
select t;




This Linq query will be translated to a Google Desktop query:

" statement expression filetype:pdf "

And the t is GDFileResult must tell the Google Desktop API that only file references must be retrieved.

This first thing to do is to create the GDesktop this class represents our connection with Google Desktop this object is similar to a database connection object in Linq to Sql . GDesktop must implement the System.Linq.IQueryable<T> interface. This interface inherits from IEnumerable<T>. Since at this time there's no official documentation on how to implement this interface correctly, only the required elements were added.

This implementation of the Google Desktop provider looks like this:


public class GDesktop :IQueryable<GDResult>
{

public IQueryable<T> CreateQuery<T>(Expression expression)
{
GDQuery q = new GDQuery();
if (expression.NodeType == ExpressionType.Call)
{
MethodCallExpression mcExpression =
(MethodCallExpression)expression;
switch (mcExpression.Method.Name)
{
case "Where":
GDQueryInfo qi = ProcessWhereMethodCall(mcExpression);
q.QueryInfo = qi;
break;

default:
throw new NotImplementedException(
"Could not handle method: "+
mcExpression.Method.Name);

}
}
else
{
throw new NotSupportedException(expression.ToString());
}
return (IQueryable<T>)q;

}


public Expression Expression
{
get {
return Expression.Constant(this);
}
}


#region Expression tree processing
....
#endregion


#region Not implemented methods

public TResult Execute<TResult>(Expression expression)
{
throw new Exception("The method or operation is not implemented.");
}


public IEnumerator<GDResult> GetEnumerator()
{
throw new Exception("The method or operation is not implemented.");
}

IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
throw new Exception("The method or operation is not implemented.");
}

public IQueryable CreateQuery(Expression expression)
{
throw new Exception("The method or operation is not implemented.");
}

public Type ElementType
{
get { throw new Exception("The method or operation is not implemented."); }
}

public object Execute(Expression expression)
{
throw new Exception("The method or operation is not implemented.");
}

#endregion



The CreateQuery method is very important and is called with the expression tree representing the contents of the first section the query. For example for the following query:


var e = from t in gd
where t.Contains("linq")
select t;


Is interpreted by the compiler as:


var e = gd.Where(t => t.Contains("linq"));


When the compiler determines that gd is an IQueryable it generates calls to the Expression property and the CreateQuery method.

The CreateQuery method of the GDesktopclass is going to return a instance of a GDQuery object which represents the specific query that is being created. Also a GDQueryInfo object is used store collected query terms and modifiers from the expressions trees.

The only supported operation that can be applied to GDesktop is Where. This is very important because no other operation, like Select, Take or Skip could be applied because there's no way (that I know of) to get all the elements of the Google Desktop index!.

The ProcessWhereMethodCall walks the where expression tree to collect the required query terms and the query modifiers . The only supported elements in the where section are:


  1. Calls to the Contains method meaning that the document contains a term or phrase

  2. 'is' expressions to filter the type of element that must be returned

  3. Equality expression of "query time" properties for elements such as 'Filetype' or 'From' or 'To'



Processing for each of this elements is implemented this way:


#region Expression tree processing

private GDQueryInfo ProcessWhereMethodCall(MethodCallExpression mcExpression)
{
Expression theObjectArgument = mcExpression.Arguments[0];
Expression whereExpressionTree = mcExpression.Arguments[1];

GDesktop provider =
(GDesktop)((ConstantExpression)theObjectArgument).Value;

GDQueryInfo qi = new GDQueryInfo();
CollectQueryInfo(whereExpressionTree, qi);
return qi;

}

private void CollectQueryInfo(Expression whereExpressionTree, GDQueryInfo qi)
{
if (whereExpressionTree is UnaryExpression &&
((UnaryExpression)whereExpressionTree).Operand
is LambdaExpression)
{
UnaryExpression ue = (UnaryExpression)whereExpressionTree;
CollectQueryInfo(((LambdaExpression)ue.Operand).Body, qi);
}

if (whereExpressionTree.NodeType == ExpressionType.AndAlso)
{
BinaryExpression be = (BinaryExpression)whereExpressionTree;
CollectQueryInfo(be.Left,qi);
CollectQueryInfo(be.Right,qi);
}

if (whereExpressionTree.NodeType == ExpressionType.Call)
{
ProcessMethodCall(whereExpressionTree, qi);
}

if (whereExpressionTree.NodeType == ExpressionType.Equal)
{
ProcessEqual(whereExpressionTree, qi);
}

if (whereExpressionTree.NodeType == ExpressionType.TypeIs)
{
ProcessTypeIs(whereExpressionTree, qi);
}

}

private void ProcessTypeIs(Expression whereExpressionTree, GDQueryInfo qi)
{
if (whereExpressionTree is TypeBinaryExpression)
{
TypeBinaryExpression be = (TypeBinaryExpression)whereExpressionTree;

if (be.Expression is ParameterExpression &&
((ParameterExpression)be.Expression).Type == typeof(GDResult) &&
be.TypeOperand.IsSubclassOf(typeof(GDResult)))
{
switch (be.TypeOperand.Name)
{
case "GDFileResult":
qi.ElementType = GDElementType.File;
break;
case "GDEmailResult":
qi.ElementType = GDElementType.Email;
break;
case "GDResult":
qi.ElementType = null;
break;
default:
throw new NotSupportedException("Element type not supported");
}
}
}
else
{
throw new NotSupportedException("TypeIs expression not supported");
}
}

private void ProcessEqual(Expression whereExpressionTree, GDQueryInfo qi)
{

if (whereExpressionTree is BinaryExpression)
{
BinaryExpression be = (BinaryExpression)whereExpressionTree;
Expression leftExpression = be.Left;
if (IsAPropertyAccessToSpecificResultItem(leftExpression, typeof(GDFileResult)))
{
string argumentValue = (string)GetObjectFromArgument(be.Right);

switch (((MemberExpression)leftExpression).Member.Name)
{
case "FileType":
qi.FileType = argumentValue;
break;
}

}
else
{
if (IsAPropertyAccessToSpecificResultItem(
leftExpression,
typeof(GDEmailResult)))
{

string argumentValue = (string)GetObjectFromArgument(be.Right);

switch (((MemberExpression)leftExpression).Member.Name)
{
case "From":
qi.From = argumentValue;
break;
case "To":
qi.To = argumentValue;
break;
case "Cc":
qi.Cc = argumentValue;
break;
case "Subject":
qi.Subject = argumentValue;
break;
}
}
else
{
throw new NotSupportedException("Property not supported");
}
}
}
else
{
throw new NotSupportedException("Member access not supported");
}
}


private void ProcessMethodCall(Expression whereExpressionTree, GDQueryInfo qi)
{
MethodCallExpression mCall =
(MethodCallExpression)whereExpressionTree;
if (mCall.Method.Name == "Contains")
{
object o = GetObjectFromArgument(mCall.Arguments[0]);
qi.AddTerm((string)o);
}
else
{
throw new NotSupportedException("Method call not supported "+mCall.ToString());
}

}


private object GetObjectFromArgument(Expression e)
{
return LambdaExpression.Lambda(e).Compile().DynamicInvoke();

}

private static bool IsAPropertyAccessToSpecificResultItem(Expression leftExpression, Type elementType)
{
return leftExpression.NodeType == ExpressionType.MemberAccess &&
((MemberExpression)leftExpression).Expression.NodeType
== ExpressionType.Convert &&
((UnaryExpression)((MemberExpression)leftExpression).Expression).Type
== elementType &&
((UnaryExpression)((MemberExpression)leftExpression).Expression).Operand.Type
== typeof(GDResult) &&
((MemberExpression)leftExpression).Member is PropertyInfo;
}

#endregion



Certainly this code is not pretty. In the future I'll try to improve it.

Since we want to allow the user to pass variables, function calls, property references, literal, etc. to the query arguments. For example:


var e2 = from t in gdProvider
where t.Contains(args[0]) &&
t.Contains(GetString(1))&&
t is GDFileResult &&
((GDFileResult)t).FileType == "pdf"
select t;



Because of this we need to get the value from the expression tree representing the query argument. This is done in the GetObjectFromArgument method.


private object GetObjectFromArgument(Expression e)
{
return LambdaExpression.Lambda(e).Compile().DynamicInvoke();

}


Note that the expression tree representing the query argument is being compiled as the body of a lambda expression with no parameters. Then we call the generated delegate to get the value. This is a very good example that shows that you have complete control over the complete query, even the arguments.

Now that we have processed the where section of the query and that we have collected the necessary elements to build the query string, we need a place to put the call to Google Desktop. By looking above in the definition of the GDesktop.CreateQuery method is important to note that GDQuery also have to implement IQueryable.For now our implementation is very basic. The place to put the call to Google Desktop will be the GetEnumerator method of the GDQuery class.


public class GDQuery : IQueryable
{

public IEnumerator GetEnumerator()
{
GDesktopWrapper gd = new GDesktopWrapper();
string qs = qInfo.CreateQueryString();
return gd.Query(qs, qInfo.ElementType).GetEnumerator();
}

public Expression Expression
{
get {
return Expression.Constant(this);
}
}

private GDQueryInfo qInfo;
public GDQueryInfo QueryInfo
{
get {
return this.qInfo;
}
set
{
this.qInfo = value;
}
}



#region Not implemented methods
...
#endregion
}


For features like projections we need to put more work on this implementation.

Code for this experiment can be found here.

In future posts I'll continue working with this implementation in order to add new features. For example projections,joins, and other Linq features.

Wednesday, May 2, 2007

Using Google Desktop from .NET

In this post I'm going to show how to use the Google Desktop COM API from .NET .

Google Desktop provides a couple of ways to query its index: using the local HTTP server or using the COM Query API . In this post the COM API will be used from C#.

The first step to start working with this API in .NET is to import the typelib


C:\temp\tst>tlbimp <google-desktop-path>\GoogleDesktopAPI2.dll


This will generate the file GoogleDesktopAPILib.dll.


The next thing you need to do is to register the application that will be using the COM API. Here's the code to register the application (taken from the JScript examples ):


using GoogleDesktopAPILib;

...

static int Register() {
int cookie;
object[] description = new object[]{
"Title",
" tests",
"Description",
"Simple tests",
"Icon",
"My Icon@1"
};

string myGuid = "{5323E036-345C-4323-548D-32AA55603215}";
GoogleDesktopRegistrar registar
= new GoogleDesktopRegistrar();


registar.StartComponentRegistration(myGuid,description);
object regObjObj =
registar.GetRegistrationInterface(
"GoogleDesktop.QueryRegistration");

IGoogleDesktopRegisterQueryPlugin q =
regObjObj as IGoogleDesktopRegisterQueryPlugin;

if (q == null) {
throw new Exception("Registration problem");
}

cookie = q.RegisterPlugin(myGuid,true);

registar.FinishComponentRegistration();

return cookie;
}




Registration needs to be executed just once. The returned cookie value could be stored somewhere and reused when the application is executed. For example the JScript demos in the GD SDK use the registry to store the cookie. In this demo I'm going to use a text file to store the cookie.


static int GetRegistrationCookie() {
int cookie;
FileInfo cookieFile = new FileInfo(CookieFileName);
if (cookieFile.Exists)
{
StreamReader reader =
new StreamReader(cookieFile.FullName);
string line = reader.ReadLine();
cookie = int.Parse(line.Trim());
reader.Close();
}
else
{
cookie = Register();
StreamWriter writer =
new StreamWriter(CookieFileName);
writer.WriteLine( cookie.ToString() );
writer.Close();

}
return cookie;
}


Now that we have the registration cookie we can call the Query API . This API is accessed with the GoogleDesktopQueryAPI class. This class has a Query method which receives the registration cookie, a string with the query, and some options. Here's the source for our Main method of a program which receives a query from the command line and prints the location of the matching resources:


public static void Main(string[] args)
{
if (args.Length == 1 && args[0] == "-unregister") {
UnRegister();
} else {
int cookie = GetRegistrationCookie();
string queryString = string.Join(" ",args);

GoogleDesktopQueryAPI queryAPI =
new GoogleDesktopQueryAPI();

IGoogleDesktopQueryResultSet rs =
queryAPI.Query(cookie,queryString,null,null);

IGoogleDesktopQueryResultItem2 i;
while ((i = (IGoogleDesktopQueryResultItem2)rs.Next()) != null) {
Console.WriteLine(i.GetProperty("uri"));
}
}
}


There's a lot of properties that can be extracted from a result item depending on its schema, for example if the result is a mail message.

Code for this post can be found here.

Sunday, April 29, 2007

Interesting papers on pattern matching

I've been reading two very interesting papers on pattern matching in the context of an object oriented language:



The Matching Objects With Patterns paper is presented in the context of Scala, and is very interesting because it discusses several approaches for OO pattern matching: object-oriented decomposition, visitors, type-tests/type-casts, typecase, case classes, and extractors. The last two approaches are Scala specific. The extractor approach (which is similar to F# active patterns) lets you create special functions that can be used to expose parts of an object to pattern matching.


The Extensible Pattern Matching via a Lightweight Language paper is presented in the context of F# and is a very nice description of the active patterns feature. This feature (which is similar to Scala extractors) allows the definition of special functions that expose parts of an object. This paper also talks about the special kinds of active patterns (with nice examples presented in each case), semantics, compilation and future work (which looks very interesting).

I think that having the option of using pattern matching on complex object models or frameworks found in .NET and the Java Platform, opens new possibilities for a more declarative and powerful code.

In future posts I hope I can post some experiments using F# active patterns and Scala extractors.

Saturday, March 31, 2007

Plotting 3D functions with Groovy and Java3D

In this post I'm going to show a little demo that plots 3D functions by using Groovy and Java3D.

For this demo I'm going to use the Java3D builder created for previous posts.

The TriangleArray class will be used to create the grid to plot the function . Two triangles will be create for each set of 4 points. For future posts I'm going to try to change this to support TriangleStripArray or QuadArray.

In order to create each coordinate of the plane, a function that maps between the iteration number and the real coordinate to be plotted is required. In order to do this the createMapFunc was created, this function returns a function that maps between two coordinates.


Closure createMapFunc(int x1,int x2,double y1,double y2) {
double m = (y2 - y1)/(x2 - x1);
double b = y1 - m*x1;
return { x -> m*x + b}
}

Closure createFloatMapFunc(int x1,int x2,double y1,double y2) {
Closure f = createMapFunc(x1,x2,y1,y2);
return { (float)f(it)};
}



Given this we can now create the TriangleArray data required to plot the function:



Geometry createGeometry(Closure f,double min,double max,int gridSize) {
Closure fInter = createFloatMapFunc(0,gridSize-1,min,max);
TriangleArray ta = new TriangleArray(((gridSize-1)**2)*6,
TriangleArray.COORDINATES
|TriangleArray.NORMALS
);
int idx = 0;
float x,y,z;

for ( iY in (0..(gridSize-2))) {
for( iX in (0..(gridSize-2))) {
// First triangle
x = fInter(iX);
y = fInter(iY);
z = (float) f(x,y);
ta.setCoordinate(idx,new Point3f(x,y,z));

x = fInter(iX+1);
y = fInter(iY);
z = (float) f(x,y);
ta.setCoordinate(idx+1,new Point3f(x,y,z));

x = fInter(iX);
y = fInter(iY+1);
z = (float) f(x,y);
ta.setCoordinate(idx+2,new Point3f(x,y,z));

idx += 3;

// Second triangle
x = fInter(iX+1);
y = fInter(iY);
z = (float) f(x,y);
ta.setCoordinate(idx,new Point3f(x,y,z));

x = fInter(iX+1);
y = fInter(iY+1);
z = (float) f(x,y);
ta.setCoordinate(idx+1,new Point3f(x,y,z));

x = fInter(iX);
y = fInter(iY+1);
z = (float) f(x,y);
ta.setCoordinate(idx+2,new Point3f(x,y,z));

idx += 3;
}
}
return ta;
}


The function to be plotted is a parameter to the createGeometry function.

For this example the following function will be used:


def f = { x,y -> Math.cos(x+y)*Math.sin(x-y)}


Now that we have the code to generate the 3D function geometry we can create the Java3D/Swing required elements by using the Swing, Java3D and SimpleUniverse builders.




SwingBuilder sb = new SwingBuilder();
SimpleUniverseBuilder sub = new SimpleUniverseBuilder();
Java3dBuilder jb = new Java3dBuilder();


JFrame aFrame = sb.frame(title:"Function",size:[500,500]){
panel(layout: new FlowLayout()) {
thePanel = panel(preferredSize:[500,500],
layout:new BorderLayout())
}
}

SimpleUniverse univ =
sub.simpleUniverse() {
viewingPlatform(nominalViewingTransform:true) {
orbitBehavior(
flags:OrbitBehavior.REVERSE_ALL,
bounds:new BoundingSphere(
new Point3d(0.0,0.0,0.0),
100.0))
}
viewer() {
view(minFrameCycleTime:5)
}
}

thePanel.add(univ.getCanvas(),BorderLayout.CENTER);

BranchGroup bg =
jb.branchGroup() {
transformGroup(
capability:
TransformGroup.ALLOW_TRANSFORM_WRITE) {
shape3d(geometry:createGeometry(f,-2.5,2.5,20),
appearance:polyAppearance())
}
}
bg.compile();

univ.addBranchGraph(bg);
aFrame.show()





The result of running the program looks like this:




As a last detail, the appearance of the surface is created with the createPolyAppearance function:



Appearance polyAppearance() {
Appearance app = new Appearance();
PolygonAttributes pa = new PolygonAttributes();
pa.setPolygonMode(PolygonAttributes.POLYGON_LINE);
app.setPolygonAttributes(pa);
return app;
}



Code for this experiment can be found here.

In future posts I'm going to try to add more features, like materials applied to the surface geometry, axis lines, etc.

Tuesday, March 27, 2007

Formating Fortress code with Fortify

A couple of days ago I noticed that a new tool to generate LaTeX code from Fortress source code was available. The tool is called Fortify can be found in the same page where the reference implementation is located.

In this post I'm going to try to create a little example and run Fortify to see the output of the generated LaTeX document.

One of the interesting things about Fortress is that, the language will have an alternative syntax that uses a lot of graphical symbols that will make a Fortress program look like a mathematical document.

For this test I took my old Numerical Analysis book and copied the Newton Raphson algorithm.

The program looks like this:


component Test

export Executable

nr(approx:RR64,f : RR64 -> RR64, f' : RR64 -> RR64,
N : Number,tol : RR64) = do

i:Number := 1
p:RR64 := 0
p0:RR64 := approx
ready:Boolean := false

while(i <= N AND NOT ready) do
p := p0 - f(p0)/f'(p0)
if |p - p0| < tol then
ready := true
end
i := i + 1
p0 := p
end

p
end

myF(x) = cos(x) - x
myF'(x) = - sin(x) - 1

run(a:String...) = print nr(1.5,myF,myF',10,0.0001)

end


Fortify is a tool that works inside Emacs. So I loaded fortify inside Emacs:


(load "..../fortify.el")


Then loaded Fortify by calling M-x fortify.

Selected the code:



And pressed M-&, then LaTeX code is generated.




To the generated code I added the LaTeX document declaration elements and imported a macros file included with Fortify.



\documentclass{article}
\usepackage{url, amssymb, amsmath, amsthm, stmaryrd, xspace}
\usepackage{amsfonts, graphicx, fullpage, times, hyperref}
\input ../fortify/fortify-macros

\begin{document}

....

\end{document}




After running LaTeX the code looks like this:

Thursday, March 15, 2007

An implementation of 'du -s *' in Powershell

While reading about Powershell I noticed that there's no out-of-the-box way(that I know of) to do something similar to 'du -s *' in Unix/Linux. In this post I'm going to show how to implement a similar functionality.

The -s option of the du is used to show only a the total size each argument. This is very useful when trying to determine which directories or files are taking too much space. For example:



$ cd /cygdrive/c/Python25/
$ du -bs *
4794012 DLLs
4160038 Doc
13817 LICENSE.txt
13752327 Lib
88573 NEWS.txt
56691 README.txt
547784 Tools
382592 include
948600 libs
24064 python.exe
24576 pythonw.exe
3248808 tcl
4608 w9xpopen.exe


For better results the output could be combined with sort.


$ du -bs * | sort -n
4608 w9xpopen.exe
13817 LICENSE.txt
24064 python.exe
24576 pythonw.exe
56691 README.txt
88573 NEWS.txt
382592 include
547784 Tools
948600 libs
3248808 tcl
4160038 Doc
4794012 DLLs
13752327 Lib



In order to implement this in Powershell (with the little knowledge I have from it) I wanted to implement the following strategy


  1. get all the child elements from the base directory

  2. for each element get the total size

  3. generate a tuple with the element name and the total size



In order to implement #1 a common get-childitem command was used. To implement #2, a combination of foreach-object + get-childitem -r + measure-object was used. And finally for #3 the select-object command was used.

One implementation for this functionality is the following:


function directory-summary($dir=".") {
get-childitem $dir |
% { $f = $_ ;
get-childitem -r $_.FullName |
measure-object -property length -sum |
select @{Name="Name";Expression={$f}},Sum} }



Running this command in the same directory shows:


PS C:\Python25> directory-summary

Name Sum
---- ---
DLLs 4794012
Doc 4160038
include 382592
Lib 13752327
libs 948600
tcl 3248808
Tools 547784
LICENSE.txt 13817
NEWS.txt 88573
python.exe 24064
pythonw.exe 24576
README.txt 56691
w9xpopen.exe 4608


We can also sort the results:


PS C:\Python25> directory-summary | sort sum

Name Sum
---- ---
w9xpopen.exe 4608
LICENSE.txt 13817
python.exe 24064
pythonw.exe 24576
README.txt 56691
NEWS.txt 88573
include 382592
Tools 547784
libs 948600
tcl 3248808
Doc 4160038
DLLs 4794012
Lib 13752327


Maybe there's a shorter/better way to implement this, however it was very interesting and fun to learn a little bit more about Powershell while trying to solve this.

Thursday, March 8, 2007

More Java3D and Groovy Builders

In the previous post, a basic Builder for Java3D was shown. For this post I've decided to take one another example from the Java3D distribution and try to implement the scene creation code by using the builder. Also I've received nice suggestions that and I'm going to implement.


The example that I'm going to use is SphereMotion.java which is a nice demonstration of spheres, animation and lights.

The scene creation code of this example can be found in the createSceneGraph method of the SphereMotion class in SphereMotion.java. I'm not going to show the code in this post.

This example contains the following elements:


  1. Uses the Background class to set the background color.

  2. Creates a big Sphere which is the central element of the scene

  3. Conditionally creates a PointLight, DirectionalLight or SpotLight for the Sphere illumination.

  4. Uses a PositionInterpolator and RotationInterpolator to perform an animations

  5. Uses AmbientLight for general scene illumination.




Helpers for Background, Sphere, PointLight, DirectionalLight, SpotLight, PositionInterpolator and AmbientLight were created.

According to the suggestions, we can use Groovy array literals to specify attribute values that require classes. For example instead of using "alpha: new Alpha(-1,4000)" use "alpha: [-1,4000]". This makes the code more readable.

The flexibility of having the control over attribute and element creation let's you support many ways of doing the same thing. For example, I wanted to allow the "position" attribute of the PointLight class to be specified as an instance of the Point3f class(for example "position:new Point(0.0f,0.0f,0.0f)) or as an array of three elements (for example "position:[0.0f, 0.0f, 0.0f]"). To implement this in the builder, the PointLightHelper code for the position attribute looks like this:


public class PointLightHelper extends LightHelper {

protected void applyAttributes(Node aNode,
Map attributes) {
super.applyAttributes(aNode, attributes);
PointLight pl = (PointLight)aNode;

// Check the position attribute
if (attributes.get("position") != null) {
Object value = attributes.get("position");
if (AttributeHelper.isArrayList3fTuple(value)) {
pl.setPosition(
AttributeHelper.arrayListToPoint3f(
(ArrayList)value));
} else
if (value instanceof Point3f) {
pl.setPosition((Point3f)value);
}
}
...


where


public static Point3f arrayListToPoint3f(ArrayList a) {
Vector3f result;
return
new Point3f(
((Number)a.get(0)).floatValue(),
((Number)a.get(1)).floatValue(),
((Number)a.get(2)).floatValue());
}

public static boolean isArrayList3fTuple(Object value) {
boolean result = false;
if (value instanceof ArrayList &&
((ArrayList)value).size() == 3 ) {
ArrayList a = ((ArrayList)value);
result =
a.get(0) instanceof Number &&
a.get(1) instanceof Number &&
a.get(2) instanceof Number;
}
return result;
}




The TransformGroupHelper class was also modified so more specific transformations can be specified. For example a I wanted to create a transform group to rotate on Y axis only. To solve this an attribute rotY(and rotX and rotZ) was created to support this directly on the TransformGroupHelper. This attribute was implemented as:



if (key.matches("rot(X|Y|Z)") &&
((value instanceof Number) )) {

double dValue = ((Number)value).doubleValue();

Transform3D theRotationTransform =
new Transform3D();
tg.getTransform(theRotationTransform);

switch(Character.toUpperCase(
key.charAt(key.length() - 1))) {
case 'X':
theRotationTransform.rotX(dValue);
break;
case 'Y':
theRotationTransform.rotY(dValue);
break;
case 'Z':
theRotationTransform.rotZ(dValue);
break;
}
tg.setTransform(theRotationTransform);
}




Once having the helpers for the new elements implemented, the only thing missing to resolve was to translate the scene creation code to the builder. One of the major issues was the creation of the Light object. In the original code, the Light was created like this:



switch (lightType) {
case DIRECTIONAL_LIGHT:
lgt1 = new DirectionalLight(lColor1, lDirect1);
lgt2 = new DirectionalLight(lColor2, lDirect2);
break;
case POINT_LIGHT:
lgt1 = new PointLight(lColor1, lPoint, atten);
lgt2 = new PointLight(lColor2, lPoint, atten);
break;
case SPOT_LIGHT:
lgt1 = new SpotLight(lColor1, lPoint, atten, lDirect1,
25.0f * (float)Math.PI / 180.0f, 10.0f);
lgt2 = new SpotLight(lColor2, lPoint, atten, lDirect2,
25.0f * (float)Math.PI / 180.0f, 10.0f);
break;
}

...

l1Trans.addChild(lgt1);
l2Trans.addChild(lgt2);




This means that the creation of the instance of the desired light is a parameter of the program. Also another issue was that two lights were created and different parents are assigned to each one. In order to support this we split the creation of both lights into two different switch statements.

The final code for the creation of this scene looks like this:



public BranchGroup createSceneGraph() {
Color3f eColor = new Color3f(0.0f, 0.0f, 0.0f);
Color3f sColor = new Color3f(1.0f, 1.0f, 1.0f);
Color3f objColor = new Color3f(0.6f, 0.6f, 0.6f);
Color3f lColor1= new Color3f(1.0f, 0.0f, 0.0f);
Color3f lColor2= new Color3f(0.0f, 1.0f, 0.0f);
Color3f alColor= new Color3f(0.2f, 0.2f, 0.2f);
Color3f bgColor= new Color3f(0.05f, 0.05f, 0.2f);

TransformGroup t1;
TransformGroup t2;

Java3dBuilder j3b = new Java3dBuilder()

BoundingSphere bounds =
new BoundingSphere(new Point3d(0.0,0.0,0.0), 100.0);

// Create the root of the branch graph
BranchGroup objRoot =
j3b.branchGroup() {
transformGroup(scale:0.4) {
background(color:new Color3f(0.05f, 0.05f, 0.2f),bounds:bounds)
sphere(radius:1.0f,
flags:Sphere.GENERATE_NORMALS,
divisions:80,
material:new Material(objColor,
eColor,
objColor,
sColor,
100.0f),
lighting:true
)
t1 = transformGroup(
capability:TransformGroup.ALLOW_TRANSFORM_WRITE){
transformGroup(
translate:[0.0, 0.0, 2.0])
{
sphere(radius:0.05f,coloringAttributes:lColor1)
switch(lightType) {
case DIRECTIONAL_LIGHT:
directionalLight(color:lColor1,
direction:[0.0, 0.0, -2.0],
influencingBounds:bounds)
break;
case POINT_LIGHT:
pointLight(color:lColor1,
position:[0, 0, 0],
attenuation:[1,0,0],
influencingBounds:bounds)
break;
case SPOT_LIGHT:
spotLight(color:lColor1,
position:[0, 0, 0],
attenuation:[1,0,0],
direction:[0.0, 0.0, -2.0],
spreadAngle:25.0f * (float)Math.PI / 180.0f,
concentration:10.0,
influencingBounds:bounds)
break;
}
}
}
t2 = transformGroup(
capability:TransformGroup.ALLOW_TRANSFORM_WRITE){
transformGroup(
translate:[0.5, 0.8, 2.0])
{
sphere(radius:0.05f,coloringAttributes:lColor2)
switch(lightType) {
case DIRECTIONAL_LIGHT:
directionalLight(color:lColor2,
direction:[-0.5, -0.8, -2.0],
influencingBounds:bounds)
break;
case POINT_LIGHT:
pointLight(color:lColor2,
position:[0, 0, 0],
attenuation:[1,0,0],
influencingBounds:bounds)
break;
case SPOT_LIGHT:
spotLight(color:lColor2,
position:[0, 0, 0],
attenuation:[1,0,0],
direction:[-0.5, -0.8, -2.0],
spreadAngle:25.0f * (float)Math.PI / 180.0f,
concentration:10,
influencingBounds:bounds)
break;
}
}
}
ambientLight(color:alColor,
influencingBounds:bounds)
rotationInterpolator(alpha:new Alpha(-1, Alpha.INCREASING_ENABLE,
0, 0,
4000, 0, 0,
0, 0, 0),
target:t1,
axisOfTransform:new Transform3D(),
schedulingBounds:bounds,
minAngle:0.0f,
maxAngle:Math.PI*2.0f);
rotationInterpolator(alpha:new Alpha(-1, Alpha.INCREASING_ENABLE,
0, 0,
1000, 0, 0,
0, 0, 0),
target:t2,
axisOfTransform:new Transform3D(),
schedulingBounds:bounds,
minAngle:0.0f,
maxAngle:0.0f);
positionInterpolator(
alpha:new Alpha(-1,
Alpha.INCREASING_ENABLE |
Alpha.DECREASING_ENABLE,
0, 0,
5000, 0, 0,
5000, 0, 0),
target:univ.getViewingPlatform().getViewPlatformTransform(),
yAxisOfTransform:(-1*Math.PI/2.0),
schedulingBounds:bounds,
start:2.0f,
end:3.5f);


}
}
objRoot.compile();

return objRoot;
}





The result is the same as the Java example:




If someone wants to look at the code of this experiment, it can be found here.

In future posts I'm going to try to implement more Java3D functionality into the builder.