Debugging Java Macros for OpenOffice.org with Eclipse

I recently had the pleasure to develop a OpenOffice.org Java Macro and from that experience I can tell you that it is a pain in the ass.

No really, to start with the API is well not that simple and easy to work with. I understand that it is a complex task to create a cross platform cross language, network aware component framework but the whole UNO thing is probably a bit over designed (see the Developer Guide with 1100+ pages).

Maybe I just don’t grasp it yet, so to let you judge yourself here is a junk of code. It’s a simple helper class which demonstrates some basic functions like filling out form fields.

import com.sun.star.beans.UnknownPropertyException;
import com.sun.star.beans.XPropertySet;
import com.sun.star.container.NoSuchElementException;
import com.sun.star.container.XEnumerationAccess;
import com.sun.star.container.XNameAccess;
import com.sun.star.document.XDocumentInfo;
import com.sun.star.document.XDocumentInfoSupplier;
import com.sun.star.io.IOException;
import com.sun.star.lang.ArrayIndexOutOfBoundsException;
import com.sun.star.lang.WrappedTargetException;
import com.sun.star.script.provider.XScriptContext;
import com.sun.star.text.XTextFieldsSupplier;
import com.sun.star.uno.UnoRuntime;
import com.sun.star.util.XRefreshable;

public class OOUtils {
    
    private static XScriptContext context;
    
    public static void setContext(XScriptContext c){
        context = c;
    }
    
    public static void setOOFormField(String name, String value) throws NoSuchElementException, WrappedTargetException, IllegalArgumentException{
        
        XTextFieldsSupplier xTextFieldsSupplier = (XTextFieldsSupplier) UnoRuntime
            .queryInterface(XTextFieldsSupplier.class, context.getDocument());
        
        XNameAccess xNamedFieldMasters = xTextFieldsSupplier
            .getTextFieldMasters();
        
        XEnumerationAccess xEnumeratedFields = xTextFieldsSupplier
            .getTextFields();
        
        Object fieldMaster = xNamedFieldMasters.getByName(
                "com.sun.star.text.FieldMaster.User." + name);
        
        // query the XPropertySet interface, we need to set the Content property
        
        XPropertySet xPropertySet = (XPropertySet)UnoRuntime.queryInterface(
                XPropertySet.class, fieldMaster);
        
        try {
            xPropertySet.setPropertyValue("Content", value);
            
            // afterwards we must refresh the textfields collection
            XRefreshable xRefreshable = (XRefreshable) UnoRuntime.queryInterface(
                    XRefreshable.class, xEnumeratedFields);
            xRefreshable.refresh();
        } catch (IllegalArgumentException e) {
            throw e;
        } catch (Exception e){}        
    }
    
    public static Object getOOFormField(String name) throws NoSuchElementException, WrappedTargetException, IllegalArgumentException{
        
        XTextFieldsSupplier xTextFieldsSupplier = (XTextFieldsSupplier) UnoRuntime
            .queryInterface(XTextFieldsSupplier.class, context.getDocument());
        
        XNameAccess xNamedFieldMasters = xTextFieldsSupplier
            .getTextFieldMasters();
        
        Object fieldMaster = xNamedFieldMasters.getByName(
                "com.sun.star.text.FieldMaster.User." + name);
        
        // query the XPropertySet interface, we need to set the Content property
        
        XPropertySet xPropertySet = (XPropertySet)UnoRuntime.queryInterface(
                XPropertySet.class, fieldMaster);
        
        try {
            return xPropertySet.getPropertyValue("Content");
        } catch (UnknownPropertyException e) {
            return null;
        }
    }
    
    public static void saveCurrentDocument(String url) throws IOException{
        
        // Build necessary argument list for store properties.
        // Use flag "Overwrite" to prevent exceptions, if file already exists.
        
        com.sun.star.beans.PropertyValue[] lProperties =
            new com.sun.star.beans.PropertyValue[2];
        lProperties[0]       = new com.sun.star.beans.PropertyValue();
        lProperties[0].Name  = "FilterName ";
        lProperties[0].Value = "OpenDocument Text ";
        // lProperties[0].Value = "swriter: StarOffice XML (Writer)";
        lProperties[1]       = new com.sun.star.beans.PropertyValue();
        lProperties[1].Name  = "Overwrite ";
        lProperties[1].Value = new Boolean(true);
        
        com.sun.star.frame.XStorable xStore = (com.sun.star.frame.XStorable)UnoRuntime.queryInterface (
                com.sun.star.frame.XStorable .class, context.getDocument());
        
        xStore.storeAsURL (url, lProperties);
    }
    
    public static String getDocumentField(short index){
        try {
            XDocumentInfoSupplier  xDocumentInfoSupplier  = (XDocumentInfoSupplier ) UnoRuntime
            .queryInterface(XDocumentInfoSupplier .class, context.getDocument());
            
            XDocumentInfo xDocumentInfo = xDocumentInfoSupplier.getDocumentInfo();
            
            return xDocumentInfo.getUserFieldValue(index);
        } catch(Exception e){
            return "";
        }
    }
    
    public static void setDocumentField(short index,String value) throws ArrayIndexOutOfBoundsException{
        XDocumentInfoSupplier  xDocumentInfoSupplier  = (XDocumentInfoSupplier ) UnoRuntime
        .queryInterface(XDocumentInfoSupplier .class, context.getDocument());
        
        XDocumentInfo xDocumentInfo = xDocumentInfoSupplier.getDocumentInfo();
        
        xDocumentInfo.setUserFieldValue(index,value);
    }
}

So end of rant, lets advance to the real topic.

Debugging Java Macros

To debug OpenOffice Macros within Eclipse you have to configure the JVM which is internally used by OpenOffice. To do this, go to Tools => Options => OpenOffice.org => Java then select the preferred virtual machine and click on Parameters. This will open a new window which allows us to add Java Virtual Machine start parameters. So add the following.

-Xdebug
-Xrunjdwp:transport=dt_socket,server=y,address=8000,suspend=n

The last line tells the JVM to listen to port 8000 for a debugger. Restart OpenOffice and make sure that the JVM actually runs, OpenOffice seems to load it on the first time a Java based feature is needed. Therefore the simplest way to make this happen is to actually run a Java Macros, clever isn’t it ;)

You should now be able to connect to the JVM and debug it with your favorite Debugger/IDE. In my case that is Eclipse, so go to Run => Debug => Remote Java Application make sure that connection properties are set to localhost and port 8000 (or whatever you configured in OpenOffice).

In the OpenOffice Documentation there is a description for NetBeans available, which is slightly different it works by adding the debug options to the global java resource file (java.ini or javarc).

Happy debugging.

Marc