File-Klassen auf NIO.2 umstellen

Um sich von der Klasse java.io.File zu lösen und in Richtung Path zu migrieren, ist Handarbeit angesagt. Zunächst gilt es, alle Stellen zu finden, die im Workspace oder Projekt die Klasse File referenzieren. Am Einfachsten ist es, eine Anweisung wie File f; in den Code zu setzen, dann zum Beispiel in Eclipse Strg+Shift+G zu aktivieren. Es folgt eine Liste aller Vorkommen. Diese Liste muss nun abgearbeitet werden und der Code auf NIO.2 konvertiert werden. Nicht immer ist es so einfach

Scanner s = new Scanner( new File(dateiname) );

in

Scanner s = new Scanner( Paths.get(dateiname) );

umzusetzen oder

new File( dateiname ).delete();

in

Files.delete( Paths.get( dateiname ) );

Eine andere Stelle, an der Konvertierungen möglich sind, betreffen FileReader und FileWriter. Diese Klassen sind gefährlich, weil sie standardmäßig die im System voreingestellte Kodierung verwenden. Eine Kodierung wie UTF-8 im Konstruktor explizit vorzugeben ist jedoch nicht möglich. NIO.2 bietet eine bessere Methode, um an einen Reader/Writer aus einer Datei zu kommen und Entwickler werden gezwungen, die Kodierung immer sichtbar anzugeben.

Deklaration

Klassisch

Mit NIO.2

Reader r =

new FileReader(f);

Files.newBufferedReader(Paths.get(f),

StandardCharsets.ISO_8859_1);

Writer w =

new FileWriter(f);

Files.newBufferedWriter(Paths.get(f),

StandardCharsets.ISO_8859_1);

Beziehen eines Dateistroms klassisch und mit NIO.2

Der Quellcode wird erst einmal länger, doch der Gewinn ist, dass die Kodierung nicht verschwindet und auch die Ein-/Ausgabe gleich gepuffert ist. Freunde der statischen Import-Anweisung komprimieren weiterhin zu Anweisungen wie

Reader r = newBufferedReader( get( f ), ISO_8859_1 );

Es ist auf den Fall Handarbeit angesagt. Eigene Blöcke, etwa zum Schreiben in Dateien können komplett zusammengestrichen werden auf einfache Methodenaufrufe wie

Files.write( Paths.get( f ), bytes );

List<String> lines = Files.readAllLines( Paths.get( f ), StandardCharsets.ISO_8859_1 );

Hilfe bei der Migration auf Java 7 mit NetBeans und Eclipse

Wer eine große Codebasis auf Java 7 migrieren möchte, steht vor dem Programm, wie das ohne großen manuellen Aufwand funktionieren kann. Als erstes steht die Umsetzung auf die neuen Syntax-Möglichkeiten an. Das ist ein echter Mehrwert, bei den Bibliotheken gibt es in dem Sinne keine Umsetzung von alt nach neu, die File-Klasse ist nicht als veraltet markiert, wobei es durchaus interessant ist, auch File-Operationen durch die NIO.2-Operationen zu ersetzen. Schauen wir uns an, welche Möglichkeiten die modernen IDEs bieten.

Migration mit NetBeans

Die aktuelle NetBeans IDE bringt einen Code-Transformer mit, der elegant die Code-Basis durchsucht und Transformationen automatisch durchführt. Das Menü Refactor bietet den Unterpunkt Inspect and Transform…, der zu einem Dialog führt.

Hier lässt sich bei Configuration auswählen, ob imports organisiert, oder in Java 7 transformiert werden soll. Unter Manage… öffnet sich ein weiter Dialog, der im Baum JDK 1.5 and later die erkannten Muster anzeigt. Für Java 7 ist dies Can use Diamond, Convert to try-with-resources, Join catch sections using mulitcatch, Use specific catch und Use switch over Strings when possible.

Wer neu Programmcode mit NetBeans schreibt wird gleich auf die neue Syntax hingewiesen. Vervollständigungen nutzen ganz natürlich zum Beispiel den Diamond-Operator.

Migration mit Eclipse

Eclipse integriert kein Werkzeug, um die Code-Basis automatisch in die Syntax von Java 7 zu konvertieren. Seit der Version Eclipse 3.7 sind jedoch Transformationen über die Quick-Fix/Quick-Assist möglich, was es erlaubt, Generics zu mit dem Diamanten zu verkürzen, Ausnahme-Anweisungen mit einem Multi-Catch zu umrahmen, gleiche Catch-Blöcke mit einem Multi-Catch zusammenzufassen und AutoCloseable-Resourcen mit einem try-mit-Resourcen zu schließen. Allerdings setzt Eclipse bis auf eine mögliche Diamanten-Umsetzung keine Warnung ein. Und diese Warnung ist standardmäßig abgeschaltet und muss aktiviert werden. Dazu wählte Window > Preferences im Baum Java > Compiler > Errors/Warning rechts in der Rubrik Generic Type bei Redundant type arguments statt Ignore dann Warning.

Inselraus: Swing/Auswahlmenü mit Separator

Standardmäßig unterstützt die JList und auch JComboBox keine Separatoren, doch die Unterteilung in Segmente ist in der Praxis sehr nützlich. Microsoft Word und PowerPoint benutzen sie zum Beispiel, um die zuletzt vom Benutzer ausgewählten Zeichensätze prominent oben in der Liste zu haben (Excel dagegen tut dies nicht).

Abbildung

Abbildung 9.42: Separator in Words Zeichensatzauswahlliste

Wir wollen diese Möglichkeit nachbilden und dabei noch einiges über Modelle und Renderer lernen.

Abbildung

Abbildung 9.43: Eine eigene Variante mit JSeparator in JComboBox

Bei der Umsetzung gibt es unterschiedliche Varianten, die sich außer in der technischen Implementierung darin unterscheiden, ob das Modell eine Markierung für den Separator enthält oder nicht. Wir stellen beide Ansätze vor und beginnen mit der ersten Variante, also damit, einem Zellen-Renderer Positionen mitzugeben, die sagen, wo ein Trennstrich zu zeichnen ist.

package com.tutego.insel.ui.list;

import javax.swing.*;

public class JComboBoxWithSeparator1 {
  public static void main( String[] args ) throws Exception {
    UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() );
    JFrame frame = new JFrame();
    frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
    String[] items = { "Cambria", "Arial", "Verdana", "Times" };
    JComboBox<String> comboBox = new JComboBox<>( items );
    ListCellRenderer<String> renderer = new SeparatorAwareListCellRenderer1<>(
                                          comboBox.getRenderer(), 0 );
    comboBox.setRenderer( renderer );
    frame.add( comboBox );
    frame.pack();
    frame.setVisible( true );
  }
}

Die eigene Klasse SeparatorAwareListCellRenderer1 ist ein ListCellRenderer, den die JComboBox zur Darstellung der Komponenten nutzt. Im Konstruktor des Renderers geben wir den Original-Renderer mit – es kann ein bestimmter Renderer schon vorinstalliert sein, den wollen wir dekorieren – sowie eine variable Argumentliste von Positionen. Das Beispiel übergibt nur 0, da nach dem ersten Element (Index = 0) ein Trennzeichen zu setzen sein soll, sodass Cambria und Arial abgetrennt sind.

package com.tutego.insel.ui.list;

import java.awt.*;
import java.util.Arrays;

import javax.swing.*;

public class SeparatorAwareListCellRenderer1<E> implements ListCellRenderer<E>
{
  private final ListCellRenderer<? super E> delegate;
  private final int[] indexes;
  private final JPanel panel = new JPanel( new BorderLayout() );

  public SeparatorAwareListCellRenderer1( ListCellRenderer<? super E> delegate,
                                          int... indexes )
  {
    Arrays.sort( indexes );
    this.delegate = delegate;
    this.indexes  = indexes;
  }

  @Override
  public Component getListCellRendererComponent( JList<? extends E> list, E value,
                                                 int index, boolean isSelected,
                                                 boolean cellHasFocus )
  {
    panel.removeAll();
    panel.add( delegate.getListCellRendererComponent( list, value, index,
                                                      isSelected, cellHasFocus ) );
    if ( Arrays.binarySearch( indexes, index ) >= 0 )
      panel.add( new JSeparator(), BorderLayout.PAGE_END );

    return panel;
  }
}

Die Implementierung basiert auf der Idee, jede Komponente in einen Container (JPanel) zu setzen und in diesem Container dann je nach Bedarf ein JSeparatorunten an diesem Container anzufügen. Statt JPanel mit einem JSeparator auszustatten, kann ebenfalls auch ein Border unten gezeichnet werden. Die AnweisungArrays.binarySearch(indexes, index) >= 0 ist als »contains« zu verstehen, also als ein Test, ob der Index im Feld ist – leider gibt es so eine Methode nicht in der Java API. Wenn der Index im Feld ist, soll der Separator unter der Komponente erscheinen. Dass sich eine Trennlinie auch am Anfang befinden kann, berücksichtigt die Lösung nicht; diese Variante bleibt als Übungsaufgabe für die Leser.

Diese Lösung ist einfach und funktioniert gut, denn vorhandene Renderer werden weiterverwendet, was sehr wichtig ist, denn größere Swing-Anwendungen nutzen viele eigene Renderer, etwa um Icons und Text zusammenzufassen. Ein Nachteil ist, dass der Separator zu einen Element gehört, und wenn das Element etwa in der Liste ausgewählt wird, steht der Separator mit in der Selektion und ist nicht abgetrennt (das ist aber bei Word genauso).

Während die vorgestellte Variante in der Praxis gut funktioniert, wollen wir uns noch mit einer alternativen Umsetzung beschäftigen. Sie ist deutlich komplexer und auch nicht so flexibel. Die Lösung basiert auf der Idee, dass die Modelldaten eine Markierung für den Separator enthalten – die folgende Lösung nutzt null dafür. DaJList oder JComboBox eine null problemlos verträgt, ist die Basis schnell umgesetzt:

JFrame frame = new JFrame();
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
String[] items = { "Cambria", null, "Arial", "Cambria", "Verdana", "Times" };
JComboBox<String> comboBox = new JComboBox<String>( items );
frame.add( comboBox );
frame.pack();
frame.setVisible( true );

Wenn wir das Programm starten, zeigt es dort, wo das Model eine null liefert, einfach nichts an. Die Selektion dieses null-Elements ist auch möglich und führt zu keiner Ausnahme.

Damit Swing das null-Element nicht als Leereintrag anzeigt, verpassen wir unserer JComboBox einen Zellen-Renderer. Der soll immer dann, wenn das Element nullist, ein Exemplar von JSeparator zeichnen. Vom Design ist es das beste, wenn der Zell-Renderer selbst die null-Elemente darstellt, aber das Zeichnen der Nicht-null-Elemente an einen anderen Zell-Renderer abgibt, anstatt diese etwa durch einen BasicComboBoxRenderer (ein JLabel, das ListCellRendererimplementiert) selbst zu renderen – das würde die Flexibilität der Lösung massiv einschränken.

package com.tutego.insel.ui.list;

import java.awt.Component;
import javax.swing.*;

public class SeparatorAwareListCellRenderer2<E> implements ListCellRenderer<E>
{
  private final ListCellRenderer<? super E> delegate;

  public SeparatorAwareListCellRenderer2( ListCellRenderer<? super E> delegate )
  {
    this.delegate = delegate;
  }

  @Override
  public Component getListCellRendererComponent( JList<? extends E> list, E value,
                                                 int index, boolean isSelected,
                                                 boolean cellHasFocus )
  {
    if ( value == null )
      return new JSeparator();

    return delegate.getListCellRendererComponent( list, value, index, isSelected, cellHasFocus );
  }
}

Die eigene Klasse SeparatorAwareListCellRenderer2 bekommt einen anderen ListCellRenderer übergeben und liefert die Komponenten dieses Delegate-Renderers immer dann, wenn das Element ungleich null ist. Ist es dagegen gleich null, liefert getListCellRendererComponent() ein Exemplar des JSeparators.

Im Beispielprogramm muss der Renderer nun angemeldet werden, und dazu ist Folgendes zu ergänzen:

comboBox.setRenderer(
  new SeparatorAwareListCellRenderer2<String>(comboBox.getRenderer()) );

Nach dem Start des Programms ist das Ergebnis schon viel besser: Dort, wo vorher die JComboBox eine leere Zeile darstellte, ist nun ein Strich.

Die Lösung ist jedoch nur ein Etappensieg, denn die Navigation mit der Tastatur durch die Liste zeigt eine Schwachstelle: Das null-Element lässt sich auswählen und erscheint auch als Linie im Editor/Textfeld. Beheben lässt sich das Problem nicht mit dem Renderer, denn der ist nur mit der Darstellung in der Liste beauftragt. Hier muss in die Interna der Swing-Komponente eingegriffen werden. Jede Swing-Komponente hat ein korrespondierendes UI-Delegate, das für das Verhalten und die Darstellung der Komponente verantwortlich ist. Für die JComboBox sind das Unterklassen von ComboBoxUI, und zwei Methoden sind besonders interessant:selectNextPossibleValue() und selectPreviousPossibleValue().

Da jede UI-Implementierung ihr eigenes Look and Feel (LAF) mitbringt, müssen wir hier eigentlich einen Dekorator bauen und jede Methode bis auf die beiden genannten an das Original weiterleiten, doch das ist jetzt zu viel Arbeit, und so nehmen wir die Basisklasse WindowsComboBoxUI als Basisklasse, denn unser Beispiel nutzt das Windows-LAF. In der Unterklasse implementieren wir eigene Versionen der Methoden selectNextPossibleValue() undselectPreviousPossibleValue(), die so lange die Liste nach oben/unten laufen müssen, bis sie ein Element ungleich null finden.

package com.tutego.insel.ui.list;

import com.sun.java.swing.plaf.windows.WindowsComboBoxUI;

public class SeparatorAwareComboBoxUI extends WindowsComboBoxUI
{
  @Override
  protected void selectNextPossibleValue()
  {
    for ( int index = comboBox.getSelectedIndex() + 1;
          index < comboBox.getItemCount();
          index++ )
      if ( comboBox.getItemAt( index ) != null )
      {
        comboBox.setSelectedIndex( index );
        break;
      }
  }

  @Override
  protected void selectPreviousPossibleValue()
  {
    for ( int index = comboBox.getSelectedIndex() - 1;
          index >= 0;
          index-- )
      if ( comboBox.getItemAt( index ) != null )
      {
        comboBox.setSelectedIndex( index );
        break;
      }
  }
}

Das UI-Objekt muss ebenfalls im main()-Programm angemeldet werden:

comboBox.setUI( new SeparatorAwareComboBoxUI() );

Enthält die Liste null-Elemente, überspringt die Tastennavigation über die Cursor-Taste diese. Doch auch mit diesem Teilstück fehlt ein weiteres Detail: Mit einem Klick lässt sich die Linie doch noch auswählen. Das ist keine Frage des Renderers und auch keine Frage der Tastaturnavigation – es muss untersagt werden, dass bei der Aktivierung ein null-Element zum Editor kommen kann. Hier ist die Methode setSelectedItem() von JComboBox entscheidend. Denn jedes Element, das selektiert wird – und dadurch auch in das Textfeld kommt –, geht durch die Methode hindurch. Wenn wir die Methode überschreiben und bei null-Elementen einfach nichts tun, wird auch das null-Element nicht selektiert, und im Textfeld bleibt das letzte Element stehen.

Damit auch spezielle Implementierungen von JComboBox von diesem Verhalten profitieren können, müssen wir wieder einen Dekorator schreiben, doch das kostet zu viel Mühe, und so überschreibt eine einfache Unterklasse die setSelectedItem()-Methode. (Im Prinzip wäre auch eine überschriebene Methode vonsetSelectedIndex() sinnvoll, denn das könnte eine programmierte Aktivierung von null vermeiden.)

package com.tutego.insel.ui.list;

import javax.swing.*;

public class SeparatorAwareJComboBox<E> extends JComboBox<E>
{
  @SafeVarargs
  public SeparatorAwareJComboBox( E... items )
  {
    super( items );
  }

  @Override
  public void setSelectedItem( Object anObject )
  {
    if ( anObject != null )
      super.setSelectedItem( anObject );
  }
}

Im Hauptprogramm muss nur diese spezielle Klasse zum Einsatz kommen und so folgt:

package com.tutego.insel.ui.list;

import javax.swing.*;

public class JComboBoxWithSeparator2
{
  public static void main( String[] args ) throws Exception
  {
    UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() );
//    UIManager.setLookAndFeel( new NimbusLookAndFeel() );

    JFrame frame = new JFrame();
    frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
    String[] items = { "Cambria", null, "Arial", "Verdana", "Times" };
    JComboBox<String> comboBox = new SeparatorAwareJComboBox<>( items );
    comboBox.setRenderer( new SeparatorAwareListCellRenderer2<>(comboBox.getRenderer()) );
    comboBox.setUI( new SeparatorAwareComboBoxUI() );
    comboBox.setSelectedIndex( 1 );
    frame.add( comboBox );
    frame.pack();
    frame.setVisible( true );
  }
}

Inselraus: Variablen mit Xor vertauschen

Eine besonders trickreiche Idee für das Vertauschen von Variableninhalten arbeitet mit dem Xor-Operator und benötigt keine temporäre Zwischenvariable. Die Zeilen zum Vertauschen von x und y lauten wie folgt:

int x = 12, 
  
    y = 49;
x ^= y; // x = x ^ y = 001100bin ^ 110001bin = 111101bin
y ^= x; // y = y ^ x = 110001bin ^ 111101bin = 001100bin
x ^= y; // x = x ^ y = 111101bin ^ 001100bin = 110001bin
System.out.println( x + " " + y );  // Ausgabe ist: 49 12

Der Trick funktioniert, da wir mit Xor etwas »hinein- und herausrechnen« können. Zuerst rechnet die erste Zeile das y in das x. Wenn wir anschließend die Zuweisung an das y machen, dann ist das der letzte schreibende Zugriff auf y, also muss hier schon das vertauschte Ergebnis stehen. Das stimmt auch, denn expandieren wir die zweite Zeile, steht dort: »y ^ x wird zugewiesen an y«, und dies ist y ^ (x ^ y). Der letzte Ausdruck verkürzt sich zu y = x, da aus der Definition des Xor-Operators für einen Wert a hervorgeht: a ^ a = 0. Die Zuweisung hätten wir zwar gleich so schreiben können, aber dann wäre der Wert von y verloren gegangen. Der steckt aber noch in x aus der ersten Zuweisung. Betrachten wir daher die letzte Zeile x ^ y: y hat den Startwert von x, doch in x steckt ein Xor-y. Daher ergibt x ^ y den Wert x ^ x ^ y, und der verkürzt sich zu y. Demnach haben wir den Inhalt der Variablen vertauscht. Im Übrigen können wir für die drei Xor-Zeilen alternativ schreiben:

y ^= x ^= y;   // Auswertung automatisch y ^= (x ^= y) 
  
x ^= y;

Da liegt es doch nahe, die Ausdrücke weiter abzukürzen zu x ^= y ^= x ^= y. Doch leider ist das falsch (es kommt für x immer null heraus). Motivierten Lesern bleibt dies als Denksportaufgabe überlassen.

Wanted: Native English speaker for proofreadig my ebook about Google Guava I/O

Seit etwa einem Jahr schreibe ich unregelmäßig an Tutorials zu Google Guava. Das Paket com.google.common.io ist nahezu komplett dokumentiert und Schritt für Schritt taste ich mich an die fast 300 Typen der Google Bibliothek ran. Der I/O Teil fasst etwa 50 Seiten und ich möchte dieses Bündel als ebook veröffentlichen. Da mir mein englisch Geschriebenes holprig und nicht flüssig vorkommt, suche ich einen Java-affinen englische Muttersprachler, der/die etwas über Google Guava IO lernen möchte bzw. im Idealfall selbst Erfahrung mit der Lib gesammelt hat und Details einstreuen kann. Interessenten sollten sich bitte unter ullenboom<at>gmail<dot>com melden. Danke.

Nach meiner jetzigen Planung werden es drei ebooks:

  • I/O and Networks
  • Basic Utilities and Java SE Extensions
  • Common Collection

Die Dokumentation zu com.google.common.base und com.google.common.primitives ist zu 80 % abgeschlossen, sodass ich denke, das zweite ebook auch noch in diesem Jahr fertigstellen zu können.

JDOM 2.0.1 freigegeben

http://www.jdom.org/news/index.html. Von der Webseite:

04.28.2012: JDOM 2.0.1 Released!

JDOM 2.0.1 is here!

JDOM 2.0.1 Introduces official support for Android! See the JDOM and Android page. JDOM 2.0.1 also fixes a bug in the ‚Compact‘ output of XML.

Get JDOM 2.0.0 Here! or from the maven-central repository here: Group: org.jdom, Articact: jdom

04.08.2012: JDOM 2.0.0 Released!

JDOM 2.0.0 is here!

JDOM 2.0.0 brings JDOM in to the world of Generics and other Java language items introduced with Java 5. As a result, JDOM 2.0.0 requires Java 5 or later, but is only fully supported on Java 6 and later.

Get JDOM 2.0.0 Here! or from the maven-central repository here: Group: org.jdom, Articact: jdom

In der Insel habe ich meine Programme nun auf JDOM 2 gebracht – ohne große Probleme. Nur die Generics muss ich in der Doku dokumentieren. Das einzige, was ich umschreiben musste, was das Kapitel über XPath, dazu gleich ein eigener Beitrag.

Inselraus: identityHashCode() ist für eine System-eindeutige Objekt-IDs nicht geeignet

Stellen wir uns vor, wir hätten eine Objekthierarchie im Speicher, die zum Beispiel jeder Socke einen Besitzer zuspricht. Wenn wir im Speicher Assoziationen abbilden, dann sollen diese Verweise auch noch nach dem Tod des Programms überleben. Eine Lösung ist die Serialisierung, eine andere eine Objekt-Datenbank oder aber auch eine XML-Datei. Doch überlegen wir selbst, wo bei der Abbildung auf eine Datenbank oder eine Datei das Problem besteht. Zunächst stehen ganz unterschiedliche Objekte mit ihren Eigenschaften im Speicher. Das Speichern der Zustände ist kein Problem, denn nur die Attribute müssten abgespeichert werden. Doch wenn ein Objekt auf ein anderes verweist, muss dieser Verweis gesichert werden. Aber in Java ist ein Verweis durch eine Referenz gegeben, und was sollte es da zu speichern geben? Eine Lösung für das Problem ist, jedem Objekt im Speicher einen Zähler zu geben und beim Speichen etwa zu sagen: »Der Besitzer 2 kennt Socke 5«.

Der Identifizierer für die Objekte muss eindeutig sein, und wir können überlegen, System.identityHashCode() zu nutzen. In der Implementierung der virtuellen Maschine von Oracle geht in den Wert von identityHashCode() die Information über den wahren Ort des Objekts im Speicher ein. Bei einer 64-Bit-Implementierung würden auch 32 Bit abgeschnitten, und die Eindeutigkeit wäre somit automatisch nicht mehr gewährleistet. Ein weiteres Problem besteht darin, dass zwar die Implementierung von Oracles identityHashCode() auf die eindeutige Objektspeicheradresse abbildet, aber dass das nicht jeder Hersteller so machen muss. Damit ist identityHashCode() nicht überall gesichert unterschiedlich. Zudem ist es prinzipiell denkbar, dass die Speicherverwaltung die Objekte verschiebt. Was sollte identityHashCode() dann machen? Wenn die neue Speicheradresse dahinter steckt, würde sich der Hashcode ändern, und das darf nicht sein. Es käme ebenfalls zu einem Problem, wenn mehr als Integer.MAX_INTEGER viele Objekte im Speicher stünden. (Doch wenn wir uns die große Zahl 2^32 = 4.294.967.296 vor Augen halten, dann es ist unwahrscheinlich, dass sich mehr als 4 Milliarden Objekte im Speicher tummeln. Zudem bräuchten wir 4 Gigabyte Speicher, wenn jedes Objekt auch nur 1 Byte kosten würde.)

Es ist gar nicht so schwierig, zwei unterschiedliche Objekte mit gleichen identityHashCode()-Resultat zu bekommen. Wir erzeugen ein paar String-Objekte und testen, jedes mit jedem, ob identityHashCode() den gleichen Wert ergibt:

String[] strings = new String[5000];
for ( int i = 0; i < strings.length; i++ )
  strings[i] = Integer.toString( i );
int cnt = 0;
for ( int i = 0; i < strings.length; i++ ) {
  for ( int j = i + 1; j < strings.length; j++ ) {
    int id1 = System.identityHashCode( strings[i] );
    int id2 = System.identityHashCode( strings[j] );
    if ( id1 == id2 ) {
      out.println( "Zwei Objekte mit identityHashCode() = " + id1 );
      out.println( " Objekt 1: \"" + strings[i] + "\"" );
      out.println( " Objekt 2: \"" + strings[j] + "\"" );
      out.println( " Object1.hashCode(): " + strings[i].hashCode() );
      out.println( " Object2.hashCode(): " + strings[j].hashCode() );
      out.println( " Object1.equals(Object2): " + strings[i].equals( strings[j] ) );
      cnt++;
    }
  }
}
System.out.println( cnt + " Objekte mit gleichem identityHashCode() gefunden." );

Ein Durchlauf bringt schnell Ergebnisse wie:

Zwei Objekte mit identityHashCode() = 9578500
Objekt 1: "541"
Objekt 2: "2066"
Object1.hashCode(): 52594
Object2.hashCode(): 1537406
Object1.equals(Object2): false
Zwei Objekte mit identityHashCode() = 14850080
Objekt 1: "2085"
Objekt 2: "2365"
Object1.hashCode(): 1537467
Object2.hashCode(): 1540288
Object1.equals(Object2): false
2 Objekte mit gleichem identityHashCode() gefunden.

Das Ergebnis ist also, dass identityHashCode() nicht sicher bei der Vergabe von Identifizierern ist. Um wirklich allen Problemen aus dem Weg zu gehen, ist ein Zählerobjekt oder eine ID über zum Beispiel die Klasse java.util.UUID nötig.

In GWT global auf Tastenkürzel reagieren

Es setzt Strg + F den Fokus auf das Suchfeld:

Event.addNativePreviewHandler( new NativePreviewHandler() {
  @Override public void onPreviewNativeEvent( NativePreviewEvent event ) {
    NativeEvent ne = event.getNativeEvent();
    if ( event.getTypeInt() == Event.ONKEYDOWN && ne.getCtrlKey() && ne.getKeyCode() == ‚F‘) {
      ne.preventDefault();
      searchTextBox.setFocus( true );
    }
  }
} );

Jerry: ein jQuery in Java

Jerry ist eine Open-Source-Lib, die eine von jQuery bekannte Funktionalität in Java abbildet. Beispiele von der Webseite: http://jodd.org/doc/jerry/index.html.

import static jodd.lagarto.dom.jerry.Jerry.jerry;

...

Jerry doc = jerry(html);

doc.$("div#jodd p.neat").css("color", "red").addClass("ohmy");

und:

doc.$("select option:selected").each(new JerryFunction() {

public boolean onNode(Jerry $this, int index) {

str.append($this.text()).append(' ');

return true;

}

});

Statt aus einem String kann die Eingabe auch direkt aus der Datei kommen:

File file = new File(SystemUtil.getTempDir(), "allmusic.html");

NetUtil.downloadFile("http://allmusic.com", file);

// create Jerry, i.e. document context

Jerry doc = Jerry.jerry(FileUtil.readString(file));

// parse

doc.$("div#new_releases div.list_item").each(new JerryFunction() {

public boolean onNode(Jerry $this, int index) {

System.out.println("-----");

System.out.println($this.$("div.album_title").text());

System.out.println($this.$("div.album_artist").text().trim());

return true;

}

});

Apache POI 3.8 ist fertig

Nach genau einem Jahr Arbeit an dem Release 3.8 ist die neue Version von Apache POI veröffentlicht. Die Änderungen gibt es unter http://poi.apache.org/changes.html, Download unter http://poi.apache.org/download.html. Links zu den Component APIs:

MVP (Model-View-Presenter) mit GWT

Zum Thema MVP (Model-View-Presenter) gibt es schon einiges an Dokumentation, etwa https://developers.google.com/web-toolkit/doc/latest/DevGuideMvpActivitiesAndPlaces, https://developers.google.com/web-toolkit/articles/mvp-architecture, doch soll dieser Beitrag einen weiteren Zugang zu dem Thema schaffen.

In einer MVP Anwendung macht die Sicht nur das, was sie machen soll: sie zeigt an. Sie ist zudem immer technologieabhängig und es kann verschiedene Implementierungen geben, etwa für GWT, Swing oder als Mock für Tests. Der Presenter ist mit der View assoziiert, aber da die View ja immer anders aussehen kann, kennt der Presenter sie nur über eine Schnittstelle. Damit beginnen wir (alle Programme im Pseudeocode):

interface MyView extends IsWidget {
  void setName( String name );
  String getName();
}

In der Schnittstelle kommen bis auf IsWidet keine GWT-spezifischen Eigenschaften nach außen, wobei es Entwickler gibt, die hier GWT-Schnittstellen wie HasText, IsWidget oder HasClickHandlers verwenden, dazu gleich mehr. Auch werden keine View-Model-Objekte (etwa Person) verwendet, sondern einfache Datentypen.

Die Implementierung für GWT kann den UiBinder nutzen oder nicht. Ohne sieht es etwa so aus:

class MyViewImpl extends Composite implements View {

  private TextBox textbox;

  MyViewImpl() {
    initWidget( textbox );
  }

  @Override public void setName( String name ) { textbox.setValue( name ); }
  @Override public String getName() { return textbox.getValue(); }
}

So einfach ist die View (die wir später noch ausbauen).

Nun kommt der Presenter, der die View initialisiert und auch die Interaktion mit dem Backend übernimmt. Wie stark er mit der View interagiert ist Geschmacksache.

Da der Presenter die View initialisiert und auf die View Einfluss nimmt, muss folglich der Presenter ein Verweis auf die View bekommen. Der Presenter merkt sich die View in einem Attribut:

class MyPresenter
{
  private MyView myView;
  …
}

Es gibt unterschiedliche Wege, wie der Presenter zur View kommt. Wenn man die View nicht austauschen möchte (und auch sonst einige Nachteile in Kauf nicht, siehe später), kann man den Presenter selbst die View erzeugen lassen. Oder man bietet einen Setter oder man lässt den Presenter injizieren. Die letzten beiden Wege erlauben den einfachen Austausch der View, etwa im Testfall. Außerdem muss die View natürlich an höher liegender Stelle irgendwie verfügbar sein, wenn zum Beispiel die MyView auf den Root-Panel gesetzt wird. Wenn man die View injiziert oder sie dem Presenter über einen Setter setzt, gibt es eine zentrale Stelle (ClientFactory in der GWT-Doku genannt) und von dort kann man diesen Teil-View für die jeweils darüber liegende View erfragen. Oder man fragt den Presenter selbst, wenn man sich so eine zentrale Stelle sparen möchte. In meinem Beispiel deklariere ich eine Methode getView() und mein Presenter erzeugt die View auch selbst:

class MyPresenter
{
  private MyView myView = new MyViewImpl();

  MyView getView() { return myView; }
}

Wenn man die View nun auf das Root-Panel setzen möchte, sieht das so aus:

MyPresenter presenter = new MyPresenter(); 
RootLayoutPanel.get().add( new ScrollPanel( presenter.getView() ) );

Einschub: Die Methode getView() kann man natürlich über eine neue Schnittstelle Presenter vorschreiben lassen, die etwa so aussehen kann:

public interface Presenter<T extends View> { 
  T getView(); 
}

In eigenen Projekten hatte ich das ursprünglich so entworfen, darin aber keinen Nutzen gefunden, und diese allgemeine Presenter-Schnittstelle wieder verworfen. Zudem hätte das auch eine View-Schnittstelle nötig gemacht, doch diese Abstraktion brachte mir nichts.

Erzeugt der Presenter die View muss man sich natürlich der Konsequenzen bewusst werden. Damit kann die View nicht mehr so einfach ausgetauscht werden (nur, wenn sie über eine Fabrik kommt), und es bringt auch den Nachteil mit, dass eine neuer Presenter-Instanz immer eine View neu erzeugt. Die View ist dann kein Singleton, die resettet und in nachfolgenden Presenter-Instanzen wiederverwendet wird. So wird jede neue Presenter-Instanz eine neue View-Instanz bilden. In meinem Beispiel soll das Ok sein, doch sollte man sich bei performanten GWT-Anwendungen im Klaren sein, die View nicht immer wieder neu zu erzeugen. Um eine zentrale ClientFactory kommt man nicht drum herum.

Im Grunde haben wir mit den drei Typen schon ein MVP realisiert:

  • MyView: Schnittstelle mit Setter/Gettern zum Verändern/Auslesen der View
  • MyViewImpl: Implementierung der View-Schnittstelle
  • MyPresenter: Kennt die View, initialisiert sind und greift auf die Daten zurück. Kennt das wahre Model

Ist die View rein passiv reichen die drei Typen aus, doch das ist nur im Ausnahmefall so. Es fehlt ein ganz entscheidender Aspekt: Was machen wir, wenn die View Ereignisse auslöst? Drückt der Benutzer einen Button, so muss eine Logik angestoßen werden. Logik ist aber Teil des Presenters, nicht der View. Zwei Realisierungen bieten sich an.

Schaut man sich https://developers.google.com/web-toolkit/articles/mvp-architecture an, so wird ein GWT-spezifischer Typ wie HasClickHandlers von der View nach außen gereicht, sodass der Presenter Logik an diesen Handler/Listener hängen kann. Hat die View einen Button, so sieht das im Code etwa so aus:

interface MyView extends IsWidget {
  void setName( String name );
  String getName();
  HasClickHandlers getOkButton();
}

Und die Implementierung:

class MyViewImpl extends Composite implements View {

  private TextBox textbox;
  private Button okButton;

  @Override public HasClickHandlers getOkButton() { return okButton; }

  …
}

Der Presenter holt sich von der View das HasClickHandlers-Objekt und hängt seine Logik daran:

class MyPresenter
{
  private MyView myView = new MyViewImpl();

  MyPresenter() {
    myView.getOkButton().addClickHandler( new ClickHandler() { … } );
  }

  MyView getView() { return myView; }
}

Den Anmeldevorgang der Listener habe ich hier in den Konstruktor gesetzt, doch ist es empfehlenswert, sie in eine eigene (private) Methode bind() auszulagern.

Dieser Weg ist einfach und kostet verhältnismäßig wenig Code. Allerdings muss man das auch kritisch sehen, denn HasClickHandlers ist eine GWT-Schnittelle, genauso wie com.google.gwt.event.dom.client.ClickHandler. Möchte man eine View total von der Technologie unabhängig machen, so stören diese Typen, wobei es sehr angenehm ist, dass es Schnittstellen sind, und so auch von Swing/SWT/JSF im Prinzip umgesetzt werden können. Eine zweite Sache ist, dass man sich überlegen muss, wo man die Grenze bei den Typen zieht. Ein Textfeld zum Beispiel implementiert im Prinzip HasValue<String>, man kann also so weit gehen, auf View-Schnittstellen-Methoden wie

setValue(String)

String getValue()

zu verzichten und stattdessen so etwas wie

HasValue getValue()

zurückzugeben weil darüber ja ein setValue()/getValue() möglich ist.

Die neuen GWT-Beispiele etwa von https://developers.google.com/web-toolkit/doc/latest/DevGuideMvpActivitiesAndPlaces gehen einen anderen Weg und lassen die View selbst die Listener anhängen. Die View darf aber natürlich immer noch nicht die Logik ausführen, weshalb die View auf Methoden vom Presenter zugreifen kann. Damit gibt es eine bidirektionale Beziehung. Es wird mehr Arbeit als bei einer Lösung wie HasClickHandlers und das, was der View auf dem Presenter aufrufen möchte muss in einen neuen Typ fließen, denn die View soll ja nicht MyPresenter bekommen, sondern einen Basistyp.

Die Beschreibung der Presenter-Methoden kommt als innere Schnittstelle in die View-Schnittstelle und muss einen Presenter annehmen können:

interface MyView {

  void setName( String name );
  String getName();

  void setPresenter( Presenter p );

  interface Presenter {
    ok();
  }
}

Der Button selbst kommt nun nicht mehr nach außen, auf HasClickHandlers getOkButton() können wir verzichten.

Die View muss jetzt setPresenter() implementieren:

class MyViewImpl extends Composite implements View {

  private TextBox textbox;
  private Button okButton;
  private Presenter presenter;

  @Override public setPresenter( Presenter p ) { presenter = p; }
  …
}

Mit dem Verweis auf den Presenter bekommt die View Zugriff auf die Logik von ok(), die immer dann aufgerufen werden soll, wenn der Button gedrückt wird. Die View wird ergänzt um die Ereignisbehandlung:

  MyViewImpl() {
    okButton.addClickHandler( new ClickHandler() {
      presenter.ok();
    } );
  }

Das war es mit der View. Noch netter ist es natürlich, wenn man den UiBinder einsetzt, denn dann wird die Anmeldung noch etwas simpler:

@UiHandler("okButton") void onOkClick( ClickEvent e ) {
  presenter.ok();
}

Der Presenter muss nur noch ok() implementieren, hat aber mit der Anhängen eines Listeneres nichts mehr zu tun:

class MyPresenter implements MyView.Presenter
{
  private MyView myView = new MyViewImpl();

  MyView getView() { return myView; }

  @Override public void ok() { … };
}

Bewertung

Zielt man MVP voll durch, ersteht viel Code. In meinem Projekt hat eine View-Schnittstelle über 100 Methoden, die implementierende Klasse ist voll von kleinen Settern und Gettern. Schön ist das nicht. Wirklich vereinfachen kann man das nur dann, wenn man a) auf diese kleine Mini-Presenter-Schnittstelle in der View verzichtet und somit dem Presenter erlaubt, direkt die Listener anzumelden, und b) wenn man sich von dem schnittstellenorientierten Ansatz verabschiedet:

  • Statt einer Schnittstelle MyView und einer Implementierung MyViewImpl schreibt man nur die eine konkrete View-Klasse und referenziert diese im Presenter direkt. Aus “private MyView myView;” wird also “private MyViewImpl myView;”. Setter/Getter können bleiben.
  • Verschärfte Variante: Man verzichtet in der View auf die vielen Setter/Getter und greift in Presenter direkt auf die GWT-Widget zurück. Dann am Besten über die Schnittstellen um sich relativ unabhängig von den GWT-Klassentypen zu machen.

Wie weit man geht, ist jedem selbst überlassen.