World domination

17. November, 2010

Bored? Looking for something new? How about … world domination?

Start with “A Brief Guide to World Domination” 🙂


Using Tycho to build Eclipse plugins

15. November, 2010

After my horrible time with PDE, I have Tycho a whirl today. I must say the whole experience was much more pleasant (despite the unfriendly Tycho home page at tycho.sonatype.org – don’t go there!).

As before, I tried to build BIRT. Unfortunately, I failed (but much faster and I know why): Tycho 0.10.0 can’t resolve extra JAR dependencies: TYCHO-533 Tycho should honor jars.extra.classpath

If you want to get started with Tycho, visit this page. There is an exemplary POM and lots of other bits and pieces.


Using Java in BIRT reports

12. November, 2010
BIRT Project

Image via Wikipedia

If you need to add complex operations to BIRT reports, you have several options. One that is often overlooked is to write the operation in Java and then use the Java code in the report. This is more simple than you’d think.

Instead of creating a “Report” project, create a Java project for your reports. Now you can put the Java code in the same project or a different project and add that second project to the list of dependencies of your report project. Note that this only works if the report project is of type “Java”.

When you edit your code, you just need to save and run your report (using the various ways to run the preview).

You can even debug the code. There is just one thing you need to be aware of: In the debug configuration, you can specify if you want to debug Java, JavaScript of both. JavaScript is the default. In this mode, Java breakpoints have no effect.


Wayland to replace X11

11. November, 2010
The logo of the X Window System (X11).

Image via Wikipedia

X11 is a bit problematic when it comes to compositing. This is nicely explained on the Wayland architecture page.

The solution is that Wayland requests that every client renders itself in a bitmap and the Wayland server creates a composite image from that. So far so good but I’m really unhappy that Wayland drops support for one of the best features of X11: the remote display.

X11 tried really hard to separate rendering from the application. This allowed lean clients and sending rendering commands (draw a line here, a rectangle there) over an optimized network protocol to the server which displays it to the user. This was priceless in the early days of computing when clients were thin and CPU power was expensive: You would run your application on a server and just get a bunch of rendering commands on your local display (which was basically a graphics card connected to an interpreter for X11 rendering commands which it received over the ‘net).

Other strategies, like copying part of the bitmap which has changed to the server, are much more expensive. A 32×32 pixel image in true color needs 4*32*3 = 384 bytes. That is the same amount you need to render 48 lines or rectangles (using 16-bit coordinates – how big is your screen?). On top of that, rendering commands have a much better compression rate (since there are lots of zero-bits) than images.

While I understand the motivation behind Wayland, I’m not happy with the implementation.


Building patches for Eclipse

10. November, 2010

Frustrated by mysterious error messages from PDE? Overwhelmed by Buckminster?

If you need to apply a simple patch to a plug-in for Eclipse, there is a more simple way. Follow this recipe:

  1. Download the source for the plug-in
  2. Create a new project
  3. Add all plug-ins in the eclipse/plugins folder to the build path. Use the variable eclipse_home. This is most simple if you use an ANT build script. If you want to waste time, try to figure out which plugins you need and only add those.
  4. Extract the few Java source files that you need to modify and copy them into your project.
  5. Copy the JARs of plugins you want to patch into your project.
  6. Fix the bugs.
  7. Use a bit of ANT magic to replace the Java classes in the JARs you copied in step #5 with the fixed versions. The trick here is that most Eclipse JARs are signed. You’ll need to remove the cryptographic keys in order to be able to load the JARs. See below for a piece of code that does the trick.
  8. Add a rule to your build.xml to copy the fixed JARs back into the plugins folder of Eclipse.
  9. Exit Eclipse (or start another instance) to test your fixes.

Here is the source to strip the SHA1-Digest keys from a MANIFEST.MF file:

// Needs commons-io 1.4
public class FilterManifest {

    public static void main( String[] args ) {
        try {
            FilterManifest tool = new FilterManifest();
            tool.run( args );
        } catch( Exception e ) {
            e.printStackTrace();
            System.exit( 1 );
        }
    }

    private void run( String[] args ) throws Exception {
        File manifestFile = new File( args[0] );
        Manifest manifest = readManifest( manifestFile );

        manifest.getEntries().clear();

        File backup = new File( manifestFile.getAbsolutePath() + ".bak" );
        if(! manifestFile.renameTo( backup ) ) {
            throw new RuntimeException( "Can't backup file" );
        }

        save( manifest, manifestFile );
    }

    private void save( Manifest manifest, File manifestFile ) throws IOException {
        FileOutputStream stream = new FileOutputStream( manifestFile );
        try {
            manifest.write( stream );
        } finally {
            IOUtils.closeQuietly( stream );
        }
    }

    private Manifest readManifest( File manifestFile ) throws IOException {
        FileInputStream stream = new FileInputStream( manifestFile );
        try {
            return new Manifest( stream );
        } finally {
            IOUtils.closeQuietly( stream );
        }
    }

}

To use this code, use this ANT code:

    <target name="fix-org.eclipse.birt.engine" depends="init">
        <unjar src="plugins/org.eclipse.birt.report.engine_2.6.1.v20100915.jar" dest="tmp">
            <patternset>
                <include name="META-INF/MANIFEST.MF"/>
            </patternset>
        </unjar>
        <java classname="tools.FilterManifest">
            <arg file="tmp/META-INF/MANIFEST.MF"/>
            
            <classpath>
                <pathelement location="target-eclipse/classes" />
                <pathelement location="target/classes" />
                <pathelement location="${m2_repo}/commons-io/commons-io/1.4/commons-io-1.4.jar" />
            </classpath>
        </java>
        <jar destfile="tmp/org.eclipse.birt.report.engine_2.6.1.v20100915.jar"
            compress="true" update="true" duplicate="preserve" index="true"
            manifest="tmp/META-INF/MANIFEST.MF"
        >
            <fileset dir="target-eclipse/classes">
                <include name="org/eclipse/birt/report/engine/**/*" />
            </fileset>
            <zipfileset src="plugins/org.eclipse.birt.report.engine_2.6.1.v20100915.jar">
                <exclude name="META-INF/*"/>
            </zipfileset>
        </jar>
    </target>

The two <pathelement> elements are necessary to make the code work from Eclipse and command line Maven (I’m using different target directories for Eclipse and Maven).

The complex <jar> target allows to copy everything from the existing plugin JAR but the crypto info.


Cycles in dependency injection

9. November, 2010

There is an old argument in DI: How to handle dependency cycles?

Say you have logging and reading of configuration files. Logging needs the config (how should I log?) and the config needs to log (where was the config read from?). How do you solve that?

It gets worse when people insist that DI fields have to be final (i.e. immutable) or that dependencies must be injected via constructors. How on earth can you create the logger if it needs a config instance as constructor parameter and the config instance needs a logger in the constructor?

The “solution”: Proxies. You create the config instance with a proxy to the real logger, then create the logger with the config instance and finally, you replace the proxy with the logger.

Why is that solution bad?

Because your code now has two bugs: You have a cyclic dependency (bad but sometimes necessary) and you’re trying hard to pretend you don’t have one. If someone will have to fix a bug in there, they won’t expect that the “final” instances can actually change.

On top of that, it makes your code inflexible. My gut feeling is that there is a reason why you can’t add plug-ins to Eclipse without having to restart the whole IDE. The infrastructure to manage plug-ins can add and remove them at runtime – unless you prevent that by using “solutions” code like the one outlined above.

Or as Yoda would have said: Much fear in you I sense.

Get over your fear. Write better unit tests (which will also become more simple if you don’t use final fields). Avoid “final” unless Java forces you to use it.


Using Eclipse to parse Java code

5. November, 2010

Eclipse comes with its own Java compiler. You can use this compiler to generate an AST from Java code by adding plugins/org.eclipse.jdt.core_<version>.jar to the classpath (details):

import java.io.*;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import org.apache.log4j.Logger;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.CompilationUnit;

public class EclipseAstParser {

    public static final String VERSION_1_4 = "1.4";
    public static final String VERSION_1_5 = "1.5";
    public static final String VERSION_1_6 = "1.6";

    private static final Set<String> ALLOWED_TARGET_JDKS = new LinkedHashSet<String>();
    static {
        ALLOWED_TARGET_JDKS.add(VERSION_1_4);
        ALLOWED_TARGET_JDKS.add(VERSION_1_5);
        ALLOWED_TARGET_JDKS.add(VERSION_1_6);
    }

    private static final Logger log = Logger.getLogger(EclipseAstParser.class);
    public static boolean DEBUG;

    private String targetJdk = VERSION_1_4;
    private String encoding = "UTF-8";

    public void setTargetJdk( String targetJdk ) {
        if(!ALLOWED_TARGET_JDKS.contains(targetJdk))
            throw new IllegalArgumentException("Invalid value for targetJdk: [" + targetJdk + "]. Allowed are "+ALLOWED_TARGET_JDKS);

        this.targetJdk = targetJdk;
    }

    public void setEncoding( String encoding ) {
        if( encoding == null )
            throw new IllegalArgumentException("encoding is null");
        if( encoding.trim().length() == 0 )
            throw new IllegalArgumentException("encoding is empty");
        this.encoding = encoding;
    }

    public AstVisitor visitFile( File file ) throws IOException {
        if(!file.exists())
            new IllegalArgumentException("File "+file.getAbsolutePath()+" doesn't exist");

        String source = readFileToString( file, encoding );

        return visitString( source );
    }

    public static String readFileToString( File file, String encoding ) throws IOException {
        FileInputStream stream = new FileInputStream( file );
        String result = null;
        try {
            result = readInputStreamToString( stream, encoding );
        } finally {
            try {
                stream.close();
            } catch (IOException e) {
                // ignore
            }
        }
        return result;
    }

    public AstVisitor visit( InputStream stream, String encoding ) throws IOException {
        if( stream == null )
            throw new IllegalArgumentException("stream is null");
        if( encoding == null )
            throw new IllegalArgumentException("encoding is null");
        if( encoding.trim().length() == 0 )
            throw new IllegalArgumentException("encoding is empty");

        String source = readInputStreamToString( stream, encoding );

        return visitString( source );
    }

    public static String readInputStreamToString( InputStream stream, String encoding ) throws IOException {

        Reader r = new BufferedReader( new InputStreamReader( stream, encoding ), 16384 );
        StringBuilder result = new StringBuilder(16384);
        char[] buffer = new char[16384];

        int len;
        while((len = r.read( buffer, 0, buffer.length )) >= 0) {
            result.append(buffer, 0, len);
        }

        return result.toString();
    }

    public AstVisitor visitString( String source ) {
        ASTParser parser = ASTParser.newParser(AST.JLS3);

        @SuppressWarnings( "unchecked" )
        Map<String,String> options = JavaCore.getOptions();
        if(VERSION_1_5.equals(targetJdk))
            JavaCore.setComplianceOptions(JavaCore.VERSION_1_5, options);
        else if(VERSION_1_6.equals(targetJdk))
            JavaCore.setComplianceOptions(JavaCore.VERSION_1_6, options);
        else {
            if(!VERSION_1_4.equals(targetJdk)) {
                log.warn("Unknown targetJdk ["+targetJdk+"]. Using "+VERSION_1_4+" for parsing. Supported values are: "
                        + VERSION_1_4 + ", "
                        + VERSION_1_5 + ", "
                        + VERSION_1_6
                );
            }
            JavaCore.setComplianceOptions(JavaCore.VERSION_1_4, options);
        }
        parser.setCompilerOptions(options);

        parser.setResolveBindings(false);
        parser.setStatementsRecovery(false);
        parser.setBindingsRecovery(false);
        parser.setSource(source.toCharArray());
        parser.setIgnoreMethodBodies(false);

        CompilationUnit ast = (CompilationUnit) parser.createAST(null);

        // AstVisitor extends org.eclipse.jdt.core.dom.ASTVisitor
        AstVisitor visitor = new AstVisitor();
        visitor.DEBUG = DEBUG;
        ast.accept( visitor );

        return visitor;
    }
}

Spring Roo again

3. November, 2010

A while ago, I tried Spring Roo — without much success. One of the main obstacles was the fact that most of the dependencies come from a special, internal repository and not from Maven Central. Which is odd. It’s OSS after all, so why do I need a special repository? Is it too much work for the Roo people to push their changes to Maven Central?

Whatever. A few days ago, Spring Roo 1.1 was released. I downloaded it and followed the tutorial.

Everything worked fine (good work, guys!) until I tried to run the tests.  Again, I had to fight with Nexus to fix all the missing dependencies. Seems like the problem with the dependencies is still there. Bug #ROO-1111 just improved the situation by listing the necessary repositories so you don’t have to figure them out yourself. But if you’re behind a corporate Maven proxy, you’re still doomed.

Okay. All dependencies are there and … 9 generated tests work. What worries me are two things:

  1. Compiling a project with a single class and field takes two minutes.
  2. This simple project needs 976KB sources.

I know this is all generated code but in the end, I will still have to wade through all this to get to the place where I have to make my changes. I prefer frameworks which take the ten lines from the tutorial and put them into a script where I can run them again and again. Generated code is not an issue as long as I don’t see it. Take Grails: It will add everything that you omit in the background. It’s still too slow for my liking but at least the source isn’t bloated with stuff that I don’t care about just yet.

Conclusion: Not quite there, yet.


%d bloggers like this: