20th April 2007

Java 6 Compiler API (JSR 199)

Java 6 Mustang introduces the JavaCompiler API to call javac directly from your source code. This can be used for on the fly generation of new classes. To make things fast, the File system required by javac can be implemented in memory. Here I propose an implementation which can be easily called from every application.

package ch.olsen.products.util.compile;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject.Kind;

public class OnTheFlyCompiler {

private static JavaFileObject generateJavaSource(final String fileName, final String source) {

return new SimpleJavaFileObject(toURI(fileName), Kind.SOURCE) {

@Override
public CharSequence getCharContent(boolean
ignoreEncodingErrors)
throws IOException, IllegalStateException,
UnsupportedOperationException {
return source;
}

};
}

static class RAMJavaFileObject extends SimpleJavaFileObject {

RAMJavaFileObject(String name, Kind kind) {
super(toURI(name), kind);
}

ByteArrayOutputStream baos;

@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors)
throws IOException, IllegalStateException,
UnsupportedOperationException {
throw new UnsupportedOperationException();
}

@Override
public InputStream openInputStream() throws IOException,
IllegalStateException, UnsupportedOperationException {
return new ByteArrayInputStream(baos.toByteArray());
}

@Override
public OutputStream openOutputStream() throws IOException,
IllegalStateException, UnsupportedOperationException {
return baos = new ByteArrayOutputStream();
}

}

public static interface FileSpec {
String getFileName();
String getSource();
}

public static class FileSpecImpl implements FileSpec {
String fileName;
String source;
public FileSpecImpl(String fileName, String source) {
this.fileName = fileName;
this.source = source;
}
public final String getFileName() {
return fileName;
}
public final String getSource() {
return source;
}
}

public static ClassLoader onTheFlyCompile(final FileSpec fileSpec) throws Exception {
return onTheFlyCompile(Arrays.asList(fileSpec));
}
@SuppressWarnings(”unchecked”)
public static ClassLoader onTheFlyCompile(final List fileSpecs) throws Exception {

if ( System.getSecurityManager() != null ) {
java.util.PropertyPermission perm = new java.util.PropertyPermission(”java.endorsed.dirs”, “read”);
System.getSecurityManager().checkPermission(perm);
}

final Map output =
new HashMap();

final JavaCompiler compiler =
ToolProvider.getSystemJavaCompiler();

final DiagnosticCollector diagnostics =
new DiagnosticCollector();

final JavaFileManager jfm = new
ForwardingJavaFileManager (
compiler.getStandardFileManager(diagnostics, Locale.getDefault(), Charset.defaultCharset())) {

@Override
public JavaFileObject getJavaFileForOutput(Location location,
String name,
Kind kind,
FileObject sibling) throws IOException {
JavaFileObject jfo = new RAMJavaFileObject(name, kind);
output.put(name, jfo);
return jfo;
}

};

final List files = new ArrayList();
for ( FileSpec fs : fileSpecs )
files.add(generateJavaSource(fs.getFileName(), fs.getSource()));

String ops[] = buildClassPath(OnTheFlyCompiler.class.getClassLoader());
System.out.println(”On the fly compiler: calling with opstions: “+ ops[0]+ ” ” + ops[1]);

CompilationTask task = compiler.getTask(
null, jfm, diagnostics, Arrays.asList(ops), null,
files);
if (! task.call()) {
for(Diagnostic dm : diagnostics.getDiagnostics())
System.err.println(dm);
throw new RuntimeException(”Could not compile”);
}

System.out.println(”On the fly compiler: generated classes: “+ output.keySet());

ClassLoader cl = new OnTheFlyClassLoader(output);

String cp[] = buildClassPath(cl);
System.out.println(”On the fly compiler: generated class loader with classpath: “+cp[1]);

return cl;
/*Class c = Class.forName(”just.generated.Hello”, false, cl);
c.getMethod(”main”, String[].class)
.invoke(null, new Object[] {args});*/

}

static class OnTheFlyClassLoader extends ClassLoader {
final Map output;
public OnTheFlyClassLoader(final Map output) {
super(OnTheFlyCompiler.class.getClassLoader());
this.output = output;
}
@Override
protected Class findClass(String name) throws
ClassNotFoundException {
JavaFileObject jfo = output.get(name);
if (jfo != null) {
byte[] bytes = ((RAMJavaFileObject)
jfo).baos.toByteArray();
return defineClass(name, bytes, 0, bytes.length);
}
//return super.findClass(name);
throw new ClassNotFoundException();
}
}

private static String[] buildClassPath(ClassLoader cl) {
List classpath = new ArrayList();
if ( cl instanceof URLClassLoader ) {
URLClassLoader cl2 = (URLClassLoader)cl;
for (URL jar : cl2.getURLs()) {
classpath.add(jar);
}
}
String classPath = System.getProperty(”java.class.path”);
for (String path : classPath.split(File.pathSeparator)) {
try {
classpath.add(new URL(”file:”+path));
} catch (MalformedURLException e) {
System.err.println(”Wrong url: “+e.getMessage());
e.printStackTrace();
}
}

StringBuffer sb = new StringBuffer();
for (URL jar : classpath) {
sb.append(jar.getPath());
sb.append(File.pathSeparatorChar);
}
String ops[] = new String[] {”-cp” , sb.toString() };
return ops;
}
private static URI toURI(String name) {
try {
return new URI(name);
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}

@Deprecated
/**
* just as an example. do not use it
*/
static JavaFileObject generateJavaSourceCode() {

final String source =
“package just.generated;\n” +
“public class Hello {\n” +
“public static void main(String… args) {\n” +
“System.out.println(new Object() {\n” +
“public String toString() {\n” +
“return \”just hello!\”;\n” +
“}\n” +
“});\n” +
“}\n” +
“}”;
return generateJavaSource(”Hello.java”, source);
}

}

To use it you just have to call something like this:

public static void test1() throws Exception {
ClassLoader cl = OnTheFlyCompiler.onTheFlyCompile(
new OnTheFlyCompiler.FileSpecImpl(”Test1.java”,
“package ch.olsen.test; ” +
“public class Test1 implements ch.olsen.test.TestCompiler.Interface1 { ” +
“  public String say() {” +
“    return \”hello compiled on the fly\”; ” +
“  }” +
“}”
));
Class clazz = cl.loadClass(”ch.olsen.test.Test1″);
Interface1 i = (Interface1)clazz.newInstance();
System.out.println(i.say());
}

public interface Interface1 {
String say();
}

Hope it is helpful!

posted in Uncategorized | 0 Comments

30th March 2007

GWT Demo

Hi have built a small project using:

  • GWT
  • Servlets
  • Stateful EJB3

The GWT application uses an asynchronous call to a Servlet, which stores in the session the reference to a EJB which in turn stores how many times it has been called. In the file you can find the Eclipse project and a build.xml which does the packaging. The file

/ear/test1.ear can be deployed using glassfish (or JBoss I guess). To deploy in Glassfish, try to use the admin console found at http://server:4848/asadmin
Attached File

posted in Uncategorized, gwt | 0 Comments

29th March 2007

Java Web Toolkits

I’ll talk a bit about building web applications using Java. To my view it is very useful to keep the business code separate from the design code. However I don’t like to go too further in this idea, I am happy of a good compromise where I don’t see HTML code mixed with some SQL queries.

With the start of the Web 2.0 there are now many toolkits available for easing the development of a web application. I can distinguish 3 rough types:

  1. Kits that do not require development skills and define a new descriptive language to describe what should go where and how. This is mainly useful for simple applications where one cannot be bothered in writing javascript or dhtml or flash himself and prefers better to describe his data model and let the framework do the rest.
  2. Kits fully implemented in a development language using the Model-View-Controller design pattern. The framework makes the controller part available, and the programmer has to take care only about the Model and the View part. That means the business logic (Model) and its representation (View) are kept completely separate and interact only though the Controller. This kind of approach is well suited for big corporate companies. In Java we are talking about frameworks based on Java Server Faces (JSF). One of the newest ones is Jboss Seams.
  3. Kits which resemble GUI toolkits such as SWT or Swing. In this case we only talk about a Model-View design pattern. The framework provides basic widgets and panels the developer can use to build the View part. Binding between the Model and View is done by hand. The advantage of this is no real need to write HTML or Javascript, as the translation between abstract Java Widgets and actual HTML components is done by the library. Examples of this toolkits are Echo2, Thinwire, Google Web Toolkit (GWT).

I discard straight away the first kind of toolkits since we don’t mind write code. The second ones I claim are a bit over designed and takes lot of effort and lot of knowledge of para languages like XML files or annotations to go through the Controller.

Then it’s clear I prefer the 3rd type of kits. They are a good compromise of separation of the business logic and its representation without making each too far away from the other. I want to talk now about Echo2 and GWT. I skip Thinwire because it has been released quite many months ago Echo2 and provides very similar functionalities.

The main difference between Echo2 and GWT is that Echo2 puts most of the work load on the server side, while GWT on the client side.

  • Echo2: Pros: Full java power in the Graphical part; Cons: Might not scale well for high load web applications. All the design has to coded in Java
  • GWT: Pros: lightweight server process can scale very well. Does not need to take control of the whole application, thus only small parts of a page might be managed with GWT; Cons: The User Interface part is coded in Java, however it is translated in JavaScript by a compiler. This means in the UI part we are not really allowed to use the full Java power.

Talking about GWT UI limitations:

  • not every java class can be used. Only java.lang and java.util are fully supported, while for the rest there is a partial coverage.
  • most of the code is running on the client, but we still need to talk to the server where the real business logic is running. This is done through asynchronous RPCs, where the server part is managed by servlets. The limitation that comes here is for Serialization. In general the objects need to be translated in JavaScript objects. It works quite well but lists and sets have to be taken with extra care, i.e. we need to write a small javadoc annotation saying what the conent of the list/set is. I could not find a word on maps, so their support may be lacking.

Generally however I still like the idea to give GWT a try.

posted in java, web | 1 Comment

26th March 2007

Link to the Development blog

I have moved the blog about the opening up issue here

posted in Uncategorized | 0 Comments