JOpenDocument API

Zum Zugriff auf Dokumente und Spreadsheets von OpenOffice ist http://www.jopendocument.org/ eine GPL-Bibliothek, die das mit einer einfachen API unterstützt. Etwa das laden und verändern von Spreadsheets:

File file = new File( „c:/in.ods“ );
SpreadSheet createFromFile = SpreadSheet.createFromFile( file );
Sheet sheet = createFromFile.getSheet( 0 );
sheet.setValueAt( „Filling test“, 1, 1 );
sheet.getCellAt( „A1“ ).setValue( „On site support“ );
sheet.getCellAt( „I10“ ).setValue( new Date() );
sheet.getCellAt( „F24“ ).setValue( 3 );

File outputFile = new File( „c:/out.ods“ );
sheet.getSpreadSheet().saveAs( outputFile );

Weitere Beispiele gibt http://www.jopendocument.org/documentation.html.

Interessant ein ein purer Java-Viewer, und damit die Möglichkeit in PDF zu exportieren, ohne dass man OO dazu fernsteuern muss.

Beim Testen der SpreadSheet-API sind mir leider einige Nachteile aufgefallen:

  • Es gibt keine Named References
  • Die API ist sehr Datei-orientiert. Nur im Speicher Dokumente anzulesen und zu verarbeiten ist nicht möglich. Ich sehe erst einmal keine Methode, wie ein Servlet z.B. sich den InputStream auf ein OO-Dokuments holen und als OutputStream an den Client verschicken kann, ohne dass man vorher das OO-Dokument in eine Datei schreibt.
  • Soll der eingebauter Viewer verwendet werden, können TIFF-Bilder nicht angezeigt werden.
  • GPL könnte für einige Bereiche ein Problem sein. Es werden aber kommerzielle Lizenzen verkauft.

Thema der Woche: Speicherverwaltung, Memory-Leaks

Lies das online-Kapitle http://java.sun.com/developer/Books/javaprogramming/bitterjava/bitterjavach06.pdf über Speicherprobleme.

Lies http://java.sun.com/javase/6/docs/api/java/lang/ref/package-summary.html und insbesondere http://java.sun.com/javase/6/docs/api/java/lang/ref/PhantomReference.html.

Aufgaben: Suche aus http://www.koders.com/default.aspx?s=phantomreference&btn=&la=Java&li=* ein Beispiel für PhantomReference. Dokumentiere, warum hier PhantomReference eingesetzt wird und keine andere Lösung exisitiert.

Was für Ergänzungen bieten die Google Collections mit den folgenden Klassen?

Implementiere ein Beispiel mit den Klassen, wobei eine OutOfMemoryError provoziert werden soll und dann gezeigt werden soll, dass die schwache Referenz gelöst wurde.

Texte umbrechen (Word-Wrap)

Dafür gibt es unterschiedliche Varianten. Die beste ist noch, einen BreakIterator zu nutzen. NetBeans (OpenIDE) bringt hier Funktionen mit. Extrahiert und ohne Abhängigkeiten lässt sich das auf folgendes reduzieren:

import java.text.BreakIterator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.StringTokenizer;
public class StringUtils
{
/**
* Indent and wrap multi-line strings.
*
* @param original the original string to wrap
* @param width the maximum width of lines
* @param breakIterator algorithm for breaking lines
* @param removeNewLines if <code>true</code>, any newlines in the original string are ignored
* @return the whole string with embedded newlines
*/
public static String wrapAndIndentString( String original, String indent, int width )
{
BreakIterator breakIterator = BreakIterator.getWordInstance();
    List<String> lines = wrapStringToArray( original, width, breakIterator, true );
StringBuffer retBuf = new StringBuffer();
    for ( String line : lines )
{
retBuf.append( indent );
retBuf.append( line );
retBuf.append( '\n' );
}
    return retBuf.toString();
}
  /**
* Wrap multi-line strings (and get the individual lines).
*
* @param original the original string to wrap
* @param width the maximum width of lines
* @param breakIterator breaks original to chars, words, sentences, depending on what instance you provide.
* @param removeNewLines if <code>true</code>, any newlines in the original string are ignored
* @return the lines after wrapping
*/
public static List<String> wrapStringToArray( String original, int width,
BreakIterator breakIterator, boolean removeNewLines )
{
if ( original.length() == 0 )
return Arrays.asList( original );
    String[] workingSet; 
    // substitute original newlines with spaces,
// remove newlines from head and tail
if ( removeNewLines )
{
original = original.trim();
original = original.replace( '\n', ' ' );
workingSet = new String[] { original };
}
else
{
StringTokenizer tokens = new StringTokenizer( original, "\n" ); // NOI18N
int len = tokens.countTokens();
workingSet = new String[len];
      for ( int i = 0; i < len; i++ )
workingSet[ i ] = tokens.nextToken();
}
    if ( width < 1 )
width = 1;
    if ( original.length() <= width )
return Arrays.asList( workingSet );
    widthcheck :
{
boolean ok = true;
      for ( int i = 0; i < workingSet.length; i++ )
{
ok = ok && (workingSet[ i ].length() < width);
        if ( !ok )
break widthcheck;
}
      return Arrays.asList( workingSet );
}
    ArrayList<String> lines = new ArrayList<String>(); 
    int lineStart = 0; // the position of start of currently processed line in
// the original string
    for ( int i = 0; i < workingSet.length; i++ )
{
if ( workingSet[ i ].length() < width )
lines.add( workingSet[ i ] );
else
{
breakIterator.setText( workingSet[ i ] );
        int nextStart = breakIterator.next();
int prevStart = 0;
        do
{
while ( ((nextStart - lineStart) < width) && (nextStart != BreakIterator.DONE) )
{
prevStart = nextStart;
nextStart = breakIterator.next();
}
          if ( nextStart == BreakIterator.DONE )
nextStart = prevStart = workingSet[ i ].length();
          if ( prevStart == 0 )
prevStart = nextStart;
          lines.add( workingSet[ i ].substring( lineStart, prevStart ) ); 
          lineStart = prevStart;
prevStart = 0;
} while ( lineStart < workingSet[ i ].length() );
        lineStart = 0;
}
}
    return lines;
}
}

Schade nur, dass GWT keinen BreakIterator für die Clientseite emuliert und selbst das Paket http://code.google.com/p/gwtx/ nicht. Schade …

Ext GWT Beispiel einer editierbaren Tabelle

Die Daten einer Tabelle liegen zeilenweise in einem ListStore. Die ListStore wird mit dem generischen Typ parametrisiert, den das Modell speichern soll. Es können entweder eigene Beans sein, oder wir nutzen BaseModel, ein generischer Speicher von Ext GWT, der wie eine Map (oder Dyna-Bean) arbeitet.

ListStore<BaseModel> store = new ListStore<BaseModel>();

Geben wir der Tabelle zwei Zeilen, also zwei BaseModel-Objekte.

BaseModel c = new BaseModel();
c.set( „url“, „http://www.tutego.com/index.html „);
c.set( „days“, „34“);
c.set( „price“, „233“);
store.add( c );

c = new BaseModel();
c.set( „url“, „http://www.heise.de/newsticker/ „);
c.set( „days“, „3“);
c.set( „price“, „2553“);
store.add( c );

Die BaseModel-Objekte beschreiben also drei Spalten.

Nun sind die Daten im Speicher und die Informationen für die Spalten müssen aufgebaut werden. Jede Spalte wird durch ein ColumnConfig-Objekt beschrieben. Die ColumnConfig-Objekte werden dann in einer Liste gespeichert und zur Initialisierung eines ColumnModels genutzt. Ein ColumnConfig-Objekt bekommt Informationen wie die Breite, aber auch Editoren zugewiesen.

List<ColumnConfig> configs = new ArrayList<ColumnConfig>();

TextField<String> column1TextEditor = new TextField<String>();
column1TextEditor.setAllowBlank( false );
ColumnConfig column1 = new ColumnConfig( „url“, „URL“, 400 );
column1.setEditor( new CellEditor( column1TextEditor ) );
configs.add( column1 ); 

ColumnConfig column2 = new ColumnConfig( „days“, „Tag(e)“, 80 );
column2.setEditor( new CellEditor( new TextField<String>() ) );
configs.add( column2 );

ColumnConfig column3 = new ColumnConfig( „price“, „Gesamtpreis“, 80 );
column3.setEditor( new CellEditor( new TextField<String>() ) );
configs.add( column3 );

ColumnModel cm = new ColumnModel( configs );

Der parametrisierte Konstruktor von ColumnConfig erwartet einen Schlüsselnamen, den Titel für den Spaltenkopf und die Breite.

Jetzt kommt es zur Hautkomponente, dem EditorGrid. Er benötigt drei Informationen: Die Tabellendaten (der ListStore store), die Spaltendaten (ColumnModel cm) und als generische Angabe der Datentyp der Modellelemente.

EditorGrid<BaseModel> grid = new EditorGrid<BaseModel>( store, cm );

Die Komponenten wird nun wie jede andere auf den Container gesetzt.

panel.add( grid );

Zugriff auf die Daten liefert store.getModels(). Möchte man durch alle Zeilen laufen, erledigt dies einfach das erweiterte for:

for ( BaseModel model :store.getModels() )
  …

Ext GWT BeanBinding Beispiel

Ext GWT bietet eine einfache Möglichkeit, JavaBeans an Formulare zu binden. Im ersten Schritt muss die Bean dazu vorbereitet werden.

  1. Die JavaBean implementiert das Marker-Interface com.extjs.gxt.ui.client.data.BeanModelTag.
  2. Will man’s nicht-invasiv, so deklariert man ein Zusatz-Interface, was BeanModelMarker erweitert und hängt dort eine spezielle Zusatz-Annotation an, mit dem Verweise auf die JavaBean.

Gebunden werden soll der Titel eines Seminars an das Textfeld titleTextField auf dem FormPanel:

FormPanel courseDescriptionPanel = new FormPanel();

TextField<String> titleTextField = new TextField<String>();
titleTextField.setFieldLabel( „Kurstitel“ );
titleTextField.setName( „title“ );
titleTextField.setAllowBlank( false ); 
courseDescriptionPanel.add( titleTextField );

Im nächsten Schritt kann man die Bean aufbauen und etwa mit Werten aus einem Service füllen:

Course c = new Course();
c.setTitle( „ejb super kurs“ );

Dann wird um diese Standard-JavaBean eine Ext GWT-Klasse gelegt, quasi ein Wrapper:

BeanModel userModel = BeanModelLookup.get().getFactory(Course.class).createModel( c );

Dieses BeanModel wird als nächstes mit dem FormPanel verbunden, auf dem das Texteingabefeld liegt.

FormBinding formBindingCourse = new FormBinding( courseDescriptionPanel );

Und das muss mit dem BeanModel (dem Wrapper um das Exemplar unserer JavaBean) verbunden werden:

formBindingCourse.bind( userModel );

Nun kennt der formBindingCourse die Bean, aber noch nicht, welches Gui-Element eigentlich mit welcher Property verbunden ist. Das kann man mit autoBind() automatisieren, oder aber mit addFieldBinding() manuell für jedes Element setzen. Bei unserem Textfeld heißt das:

formBindingCourse.addFieldBinding( new FieldBinding( titleTextField, „title“ ) );

Jede Änderung an der Gui wird nun an die Bean weitergeben.

Weiteres Infos unter http://extjs.com/blog/2008/07/14/preview-java-bean-support-with-ext-gwt/ oder in Foren-Beispielen.

Wenn Captchas zu langweilig sind, wie wär’s mit Kätzchen?

Dass Captchas wie

pwntcha/testsuite/passport/passport_000.jpeg

pwntcha/testsuite/yahoo/yahoo_053.jpeg

für uns Menschen nicht leicht zu lesen ist eine Sache, eine andere ist, dass viele schon gehackt wurden und Spammer so die Form-Formulare vollmüllen.

Ein anderes Verfahren zeigt Bilder, aus denen man ein bestimmtes auswählen muss. Diese Idee verfolgt zum Beispiel http://www.thepcspy.com/kittenauth. Hier muss man aus einer Reihe von Tieren auswählen, um das Formular abzuschicken.

Unbenannt

Mein einziges Problem: Bei einigen Fragen fällt die Auswahl nicht leicht. Etwa bei der Frage, was alles Fohlen (engl. foals) sind. Einige Bilder sind so undeutlich, dass ich drei Versuche brauche um ein Formular zu bestätigen. Das ergibt wenig Sinn.

Siehe dazu auch http://research.microsoft.com/en-us/um/redmond/projects/asirra/.

gwtrpc-spring zur Einfachen Integration von Spring in GWT 1.6

GWT-SL bietet schon seit längerem die Möglichkeit, die RPC-Services durch Spring-Beans zu definieren. Ist Spring auf der Serverseite, so möchte man gerne Spring-POJOs als GWT-RPC-Service freigeben. GWT-SL ist aber relativ groß und mit http://code.google.com/p/gwtrpc-spring/ gibt es eine sehr schlanke Alternative, die nur aus zwei Klassen besteht. Das die Klassen, die beide im Quellcode unter 3 KB liegen, liegt daran, dass GWT in den neuen Versionen eine Integration RPC-Implementierungen vereinfacht. Die Hauptseite zeigt die 4 Schritten zur Integration anschaulich.

Noch besser beschreibt es allerdings http://devbright.com/2009/05/super-simple-gwt-spring-integration/; hier kann man gleich ein Archiv laden, mit allen Jar-Dateien und alles ist fertig. So läuft das Beispiel nach wenigen Minuten.

JUnit 4.6 ist raus

Seit längerem gibt es mal wieder in Update in JUnit. Das Projekt ist relativ fehlerfrei und daher ist das neue Release kein Bug-Fix-Release, sondern JUnit 4.6 integriert einige interessante Neuigkeiten.

MaxCore:

JUnit now includes a new experimental Core, `MaxCore`.  `MaxCore`
remembers the results of previous test runs in order to run new
tests out of order. `MaxCore` prefers new tests to old tests, fast
tests to slow tests, and recently failing tests to tests that last
failed long ago. There's currently not a standard UI for running
`MaxCore` included in JUnit, but there is a UI included in the JUnit
Max Eclipse plug-in.

Scheduling-Strategien für parallele Abarbeitung von Tests:

`JUnitCore` now includes an experimental method that allows you to
specify a model of the `Computer` that runs your tests. Currently,
the only built-in Computers are the default, serial runner, and two
runners provided in the `ParallelRunner` class:
`ParallelRunner.classes()`, which runs classes in parallel, and
`ParallelRunner.methods()`, which runs classes and methods in parallel.
This feature is currently less stable than MaxCore, and may be
merged with MaxCore in some way in the future.

Dann lassen sich Arrays mit Fließkommazahlen auch mit einem Delta vergleichen:

assertArrayEquals(new double[] {1.0, 2.0}, new double[] {1.0, 2.0}, 0.01);

Das tutego-JUnit-Seminar berücksichtigt diese Änderungen.

JavaNCSS (Source Measurement Suite) in der Version 30.51

JavaNCSS ist ein “A Source Measurement Suite for Java”, mit dem man Statistiken über Projekte bekommt. Das Release vom 9. Feb. 2009 trägt die Versionsnummer 30.51 und gehört damit den zu höchsten Versionsnummer, die ich je gesehen habe.

Die Ausgabe, hier etwa für das JDK 1.1.5 (nicht 1.5!), sieht so aus:

Output generated by JavaNCSS with Sun's JDK 1.1.5 java.* source tree

Nr. Classes Functions NCSS Package
1 3 11 376 .
2 4 38 95 java.applet
3 70 1232 7060 java.awt
4 6 25 94 java.awt.datatransfer
5 30 117 861 java.awt.event
6 14 137 1023 java.awt.image
7 27 117 196 java.awt.peer
8 27 201 1268 java.beans
9 73 716 4221 java.io
10 72 711 3327 java.lang
11 7 92 288 java.lang.reflect
12 2 105 760 java.math
13 33 282 1504 java.net
14 19 73 318 java.rmi
15 3 10 71 java.rmi.dgc
16 3 13 47 java.rmi.registry
17 23 95 412 java.rmi.server
18 28 193 827 java.security
19 8 30 53 java.security.acl
20 5 8 22 java.security.interfaces
21 18 352 923 java.sql
22 40 522 4657 java.text
23 103 108 722 java.text.resources
24 30 322 2472 java.util
25 19 170 1064 java.util.zip
26 1 51 1093 sun.tools.ttydebug
27 1 0 2 sunw.io
28 2 1 6 sunw.util
--------- --------- ---------
671 5732 33762 Total

Packages Classes Functions NCSS | per
---------------------------------------------------
28.00 671.00 5,732.00 33,762.00 | Project
23.96 204.71 1,205.79 | Package
8.53 50.32 | Class
5.89 | Function

NCSS steht für “Non Commenting Source Statements (NCSS)”.

Für die Statistiken gibt es auch einen Ant-Task. Desweiteren gibt es eine kleine Gui und ein SVG-Output.

gwt-connectors – Verbindungen zwischen Formen

gwt-connectors ist eine GWT-Bibliothek, um Formen miteinander zu verbinden.

Die Präsentation zeigt, wie das geht.

Wer’s selber ausprobieren möchte, schaut unter demo (IE, Firefox, Opera, Chrome). (Aber wie kann man nun die Verbindung wieder lösen …)

Der nötige Programmcode ist kurz:

// Create boundary panel
AbsolutePanel boundaryPanel = new AbsolutePanel();
boundaryPanel.setSize("600px", "400px");
RootPanel.get().add(boundaryPanel, 10, 10);
Diagram diagram = new Diagram(boundaryPanel);
boundaryPanel.add(new Label("Connectors example"), 10, 2);

// Add connectors
Connector connector1 = new Connector(50, 80, 100, 100);
connector1.showOnDiagram(diagram);

Connector connector2 = new Connector(350, 200, 270, 80);
connector2.showOnDiagram(diagram);

Connector connector3 = new Connector(450, 120, 500, 80);
connector3.showOnDiagram(diagram);

// Add some elements that can be connected
Label label = new Label("LABEL");
Image image = new Image("http://code.google.com/images/code_sm.png");
HTML html = new HTML("<b>HTML<br>ELEMENT</b>");

boundaryPanel.add(label, 50, 270);
boundaryPanel.add(image, 180, 250);
boundaryPanel.add(html, 450, 250);

Shape shapeForLabel = new Shape(label);
shapeForLabel.showOnDiagram(diagram);

Shape shapeForImage = new Shape(image);
shapeForImage.showOnDiagram(diagram);

Shape shapeForHtml = new Shape(html);
shapeForHtml.showOnDiagram(diagram);

Ich bin gespannt, wann es das erste UML-Tool mit GWT gibt. gwt-connectores basiert im Übrigen auf Fred Sauer’s gwt-dnd. (Dazu auch das Demo http://allen-sauer.com/com.allen_sauer.gwt.dnd.demo.DragDropDemo/DragDropDemo.html.)

Dazu passt auch die Ankündigung von http://googledocs.blogspot.com/2009/03/drawing-on-your-creativity-in-docs.html, ein Zeichenwerkzeug in Google Docs einzubetten:

http://www.googlewatchblog.de/2009/03/26/google-docs-drawing-veroeffentlicht/ hat dazu ebenfalls ein Bild parat:

GWT Charting Bibliothek gflot und charts4j.

Alles fängt mit http://jquery.com/ an. Darauf baut auf http://code.google.com/p/flot/, eine JavaScript-Bib. für Chars:

http://code.google.com/p/gflot/ ist nun der GWT-Aufsatz auf flot.

Für eine Bar-Diagramm ist nur folgender Quellcode nötig:

final String[] MONTH_NAMES = { "jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec" };

PlotModel model = new PlotModel();
PlotOptions plotOptions = new PlotOptions();

BarSeriesOptions barSeriesOptions = new BarSeriesOptions();
barSeriesOptions.setShow(true);
barSeriesOptions.setLineWidth(1);
barSeriesOptions.setBarWidth(1);
barSeriesOptions.setAlignment(BarAlignment.CENTER);

plotOptions.setDefaultBarsSeriesOptions(barSeriesOptions);
plotOptions.setLegendOptions(new LegendOptions().setShow(false));



// add tick formatter to the options

plotOptions.setXAxisOptions(new AxisOptions().setTicks(12).setTickFormatter(new TickFormatter() {

public String formatTickValue(double tickValue, Axis axis) {

if (tickValue > 0 && tickValue <= 12) {

return MONTH_NAMES[(int) (tickValue - 1)];

}

return "";

}

}));


// create a series

SeriesHandler handler = model.addSeries("Ottawa's Month Temperatures (Daily Average in &deg;C)", "blue");


// add data

handler.add(new DataPoint(1, -10.5));

handler.add(new DataPoint(2, -8.6));
handler.add(new DataPoint(3, -2.4));
handler.add(new DataPoint(4, 6));
handler.add(new DataPoint(5, 13.6));
handler.add(new DataPoint(6, 18.4));
handler.add(new DataPoint(7, 21));
handler.add(new DataPoint(8, 19.7));
handler.add(new DataPoint(9, 14.7));
handler.add(new DataPoint(10, 8.2));
handler.add(new DataPoint(11, 1.5));
handler.add(new DataPoint(12, -6.6));

// create the plot
SimplePlot plot = new SimplePlot(model, plotOptions);

// put it on a panel
FlowPanel panel = new FlowPanel();
panel.add(plot);

return panel;

Eine Alternative dazu ist http://code.google.com/p/charts4j/  bzw. http://code.google.com/p/charts4j/wiki/GWT_Port.

Dozer 5.0

Letzten Monat ist Dozer 5.0 veröffentlicht worden. Vor einem Jahr hatte ich schon über Dozer berichtet: http://www.tutego.de/blog/javainsel/2008/01/bean-bean-mapping-mit-dozer.html. Neu beim Java-Bean-Mapper sind:

  • Added Generics to public api
  • Switched to XSD instead of DTD
  • Simpler packaging
  • Upgraded 3rd party dependencies
  • Upgraded code to use jdk 1.5 features
  • Various feature requests and bug fixes

Auch die Namen sind vereinheitlicht worden, nun heißt es org.dozer, BeanFactory, CustomFieldMapper, usw.

Wie das Mapping über XML-Dateien beschrieben wird, klärt das Dokument http://dozer.sourceforge.net/documentation/mappings.html genauer. Auch wenn Dozer nun Java 5 unterstützt, beginnt Dozer nicht mit Annotationen, um die Mappings zu beschreiben.

Derby Datenbank endlich auch pur In-Memory

Andere Java-Datenbanken können es schon lange, Derby kann es nun auch: Daten nur im Speicher ohne File-Backup halten. Die Derby-URL für dieses Feature lautet dann jdbc:derby:memory:datenbank;create=true. (Warum eigentlich nich einfach nur „mem“?)

Zum Weiterlesen: http://blogs.sun.com/kah/entry/derby_10_5_preview_in, http://wiki.apache.org/db-derby/InMemoryBackEndPrimer. Die aktuelle Derby-Datenbank kann man unter http://db.apache.org/derby/releases/release-10.5.1.1.cgi beziehen.

Thema der Woche: Micro-Benchmarks

Lies zunächst die folgenden Artikel

Versuche dann mit http://jetm.void.fm/index.html die Frage zu klären, wie groß der Unterschied ist, wenn man Threads für eine Aufgabe per Hand startet oder aus einem Thread-Pool holt.