Mein Umstieg auf UiBinder (GWT)

Das komplette Frontend für die tutego-Kunden/Trainer/Serminarverwaltung ist in GWT implementiert. Die Codebase ist auf dem Level von GWT 1.1 und bis zur letzten Version hat sich viel geändert. Über Updates blogge ich regelmäßig. Es wurde Zeit neu zu betrachten, welche Teile meiner Software ich vielleicht umbauen kann. Viele Themen sind spannend, aber leider gibt im Netz kaum Dokus, und da meine Lösung läuft, war die Motivation nicht sonderlich hoch, Stunden zum Rumspielen zu investieren.

Eine der großen Neuerungen in GWT 2.0 ist die Möglichkeit, deklarativ Oberflächen aufzubauen. Im Mittelpunkt steht das UiBinder-Famework, was eine XML-Dateien zur Beschreibung eines Widget-Baum nutzt und in eine initialisierte GWT-Komponente überführt. Das Feature ist relativ gut dokumentiert und die Änderungen im Code sind klein. Daher habe ich mir ein paar Stunden für die Überführung einer View genommen.

Für jede View gibt es bei mir eine Klasse, die von einer GWT-Klasse wie Composite abgeleitet ist. Sie wird vom Presenter aufgebaut. Die View-Klasse deklariert eine Reihe von Objektvariablen. Die werden später initialisiert. Vorher sahen meine Programme etwas so aus:

// Contact person

HTML contactPersonLabel = new HTML( "<b>AnsprechpartnerIn</b> (Name, EMail, Telefon)" );
contactPersonLabel.getElement().getStyle().setMarginTop( 0.5, Unit.EM );
panel.add( contactPersonLabel );
panel.add( contactPersonTextBox = new FormTextBox() );   

// tutego rate per day

HTML ratePerDayLabel = new HTML( "<b>tutego Tagessatz</b> (netto)" );
ratePerDayLabel.getElement().getStyle().setMarginTop( 0.5, Unit.EM );
panel.add( ratePerDayLabel );
panel.add( new HorizontalFlowPanel( ratePerDayTextField = new FormIntegerTextBox(), new HTML( " Euro" ) ) );
ratePerDayTextField.setWidth( "5em" );

Mit Styles arbeite ich auch, wobei ich dies nicht für das Mikrolayout verwende. Lokale Abstände initialisierte ich immer direkt.

Der UiBinder externalisiert die Beschreibung des Objektgraphen in eine XML-Datei. Es ist wichtig zu verstehen, dass der UiBinder keinen Template-Engine ist, also die XML-Datei kein XHTML ist, wo man a) einfach HTML schreiben kann und b) so etwas wie Wiederholungen/Fallunterscheidungen schreiben kann. Es ist “nur” eine Beschreibung des Objektgraphen und ausschließlich GWT-Widgets werden dort referenziert. (Auch wenn das bei einem SpanElement mit <div></div> auf den ersten Blick nicht so aussieht.)

Das GWT-Plugin für Eclipse ermöglicht gleich das Anlegen der XML-Datei und der Klasse, was für neue Views praktisch ist. Ich habe von Hand eine XML-Datei EditCustomerView.ui.xml aufgebaut, die so aussieht:

<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui=’urn:ui:com.google.gwt.uibinder‘
    xmlns:g=’urn:import:com.google.gwt.user.client.ui‘ xmlns:t=’urn:import:traida.client.web.util‘>
    <ui:with field=’res‘ type=’traida.client.resource.TraidaResources‘ />
    <ui:style>
       .bottomGap { margin-bottom: 1em; }
    </ui:style>
    <g:VerticalPanel>
        <g:Label styleName='{res.style.header1}‘ ui:field=’header‘ />

        <t:TitledPanel>
            <g:HTML>Adresse und Kontakt</g:HTML>
            <g:Anchor ui:field=’editContactAnchor‘>[Bearbeiten]</g:Anchor>
        </t:TitledPanel>
        <g:HTML styleName='{style.bottomGap}‘ ui:field=’businessCardHTML‘ />

        <t:TitledPanel>
            <g:HTML>Kurs(e)</g:HTML>
            <g:Anchor ui:field=’addNewTrainingAnchor‘>[+]</g:Anchor>
        </t:TitledPanel>
        <t:VerticalFlowPanel styleName='{style.bottomGap}‘ ui:field=’trainingsPanel‘ />

        <t:TitledPanel>
            <g:HTML>Vermerk und Logo</g:HTML>
        </t:TitledPanel>

    </g:VerticalPanel>

</ui:UiBinder>

Am Anfang werden zwei Namensräume g und t aufgebaut, einmal für die Google-Komponenten, einmal für eigene. Eigene Komponenten können einfach verwendet werden, auch die Tastaturvervollständigung klappt, ist allerdings (nur bei mir?) ziemlich träge.

Wichtig sind die ui:field-Elemente. Sie entsprechen Variablen, die in einer Java-Klasse deklariert werden. Parallel zu der XML-Datei EditCustomerView.ui.xml gibt es eine andere zugehörige Java-Klasse EditCustomerView. Sie beginnt so:

class EditCustomerView extends Composite
{
  interface EditCustomerViewUiBinder extends UiBinder<Widget, EditCustomerView> {}  // 2
  private static EditCustomerViewUiBinder uiBinder = GWT.create(EditCustomerViewUiBinder.class);

  @UiField Label header;    // 1

  …

  EditCustomerView()
  {
    initWidget( uiBinder.createAndBindUi( this ) );  // 3
    setWidth( "800" ); 
     …

  }

}

Im Rumpf ist etwas Magie, und drei Details sind wichtig:

  1. Die Klasse hat für alle in der XML-Datei deklarierten ui:field-Dinge eine Objektvariable mit der entsprechenden Annotation @UiField. Die Typen müssen zusammenpassen, also Label zu <g:Label> usw. Der UiBinder instanziiert selbständig die Typen durch den Standardkonstruktor. Ist keiner vorhanden, muss man tricksen und eine Factory einführen.
  2. Zu den Generics beim UiBinder: das erste Typargument (hier Widget) ist das, was später als Ergebnis “rauskommt”. In der XML-Datei nutze ich ein VerticalPanel, sodass der Typ hätte eigentlich auch spezieller sein können, doch den spezielleren Typ brauche ich nicht. Das zweite Typargument ist die Klasse, die die @UiField-Variablen deklariert, also die eigene Klasse selbst.
  3. Der Aufruf createAndBindUi() baut den Objektbaum auf. Das Ergebnis der Methode ist vom Typ, der im ersten Generics zugewiesen wurde, also im Beispiel Widget.

Das zur Technik. Die Frage ist, ob sich die Umstellung gelohnt hat. Bisher habe ich eine View umgestellt, doch das Ergebnis sieht gut aus. Die hierarchische Gliederung ist übersichtlich, wobei mir die XML-Ansicht reicht und ich den Designer nicht brauche. Allerdings fände ich es besser, das Layout einfacher verändern zu können. Soll zum Beispiel der Abstand zur nächsten Zeile erholt werden, so kann man nicht einfach beim Element selbst ein CSS-Syle setzen, sondern muss ein <style> Element einführen, ihm einen Namen geben und dann zuweisen. Das ist mehr Schreibarbeit als nötig. Die anderen Views werde ich nun auch umstellen.

Ob ich die UiBinder für jede View einsetzen werde wird sich zeigen, da einige Views viel stäter aus HTML aufgebaut werden und die View im Prinzip nur einen Titel setzt und der Rest ist HTML.

Ein paar Links:

Enumerate every new type and attribute in Java 7 API with a homebrewed doclet

This doclet searchs for “@since 1.7” and reports the places. Dont forget to put JDK/lib/tools.jar in the classpath.

package com.tutego.tools.javadoc;

import java.io.*;
import java.util.Formatter;
import com.sun.javadoc.*;
import com.sun.tools.javadoc.Main;

public class SinceJava7FinderDoclet
{
  private final static Formatter formatter = new Formatter();

  public static boolean start( RootDoc root )
  {
    for ( ClassDoc clazz : root.classes() )
      processClass( clazz );
    return true;
  }

  private static void processClass( ClassDoc clazz )
  {
    for ( Tag tag : clazz.tags( "since" ) )
      if ( "1.7".equals( tag.text() ) )
          formatter.format( "Neuer Typ %s%n", clazz );

    for ( MethodDoc method : clazz.methods() )
      for ( Tag tag : method.tags( "since" ) )
        if ( "1.7".equals( tag.text() ) )
          formatter.format( "Neue Methode %s%n", method );

    for ( ConstructorDoc constructor : clazz.constructors() )
      for ( Tag tag : constructor.tags( "since" ) )
        if ( "1.7".equals( tag.text() ) )
          formatter.format( "Neuer Konstruktor %s%n", constructor );

    for ( FieldDoc field : clazz.fields() )
      for ( Tag tag : field.tags( "since" ) )
        if ( "1.7".equals( tag.text() ) )
          formatter.format( "Neues Attribut %s%n", field );
  }

  public static void main( String[] args )
  {
    PrintStream err = System.err, out = System.out;
    System.setErr( new PrintStream( new OutputStream() {
      @Override public void write( int b ) { }
    } ) );
    System.setOut( System.err );

    String[] params = { "-quiet", // ignored!?
                        "-doclet", SinceJava7FinderDoclet.class.getName(),
                        "-sourcepath", "C:/Program Files/Java/jdk1.7.0/src/",
//                        "java.lang"
                        "-subpackages", "java:javax"
                        };
    Main.execute( params );

    System.setErr( err );
    System.setOut( out );

    System.out.println( formatter );
  }
}

The result:

Weiterlesen

enum für Singleton nutzen

Ein Singleton ist ein Objekt, was es in der Applikation nur einmal gibt.[1] Javas enum ist dafür perfekt geeignet, denn die Aufzählungsobjekte gibt es in der Tat nur einmal und die Bibliothek implementiert einige Tricks, um das Objekt auch möglichst nur einmal zu erzeugen, etwa dann wenn die Aufzählung serialisiert über die Leitung geht.

Ein Beispiel dazu. Ein enum MainFrame soll genau eine Konstante INSTANCE deklarieren. Da enum-Typen Attribute deklarieren können, soll unser MainFrame eine Objektvariable JFrame bekommen. Das bedeutet dann, dass mit dem Exemplar INSTANCE ein Swing-Fenster assoziiert ist. Das es nur ein Aufzählungselement in der enum gibt, kann auch nur ein JFrame-Exemplar gebildet werden:

public enum MainFrame

{

INSTANCE;

private JFrame f = new JFrame();

public JFrame getFrame()

{

  return f;

}

}

Damit ist INSTANCE ein Exemplar vom Typ MainFrame und hat ein privates Attribut und eine öffentliche Zugriffsmethode. Da es nur eine Konstante gibt, gibt es auch nur ein Fenster. Eine Anwendung sieht etwa so aus:

com/tutego/insel/enumeration/MainFrameDemo.java, main()

MainFrame.INSTANCE.getFrame().setTitle( "Singleton" );

MainFrame.INSTANCE.getFrame().setBounds( 100, 100, 300, 400 );

MainFrame.INSTANCE.getFrame().setVisible( true );

Aus jedem Teil der Anwendung ist MainFrame.INSTANCE zugänglich und repräsentiert dieses eine Exemplar. Auch kann dieses Exemplar übergeben werden, weil es ein Objekt ist, wie jedes andere auch.


[1] Pro Klassenlader, um das etwas genauer auszudrücken.

Java 7 RC1 ist da

Build 147 bildet den ersten Release Kandidaten wie http://mreinhold.org/blog/jdk7-rc berichtet. Dann mal schnell die neue Version besorgen (http://jdk7.java.net/download.html) und alles testen! Mein anfänglichen Probleme mit irgendwelchen internen Sortierproblemen in der Collection-API sind auch nicht mehr aufgetreten. Für mich läuft Java 7 damit ohne Probleme.

Für die 10. Auflage der Insel muss ich eigentlich nur noch mein Kapitel über ARM-Blöcke schreiben, sonst ist die Auflage für den Band 1 soweit fertig.

Hinweis:

UI-Design: Wähle immer gültige Default-Werte

Gerade möchte ich auch Auto mieten und ich wundere mich, warum die Gui sich über das Datumsformat beschwert. Erst auf der nächsten Seite wird klar, dass das Datum OK ist, aber die Zeit nicht. Statt eine gültige Zeit einzutragen wann ich das Auto abholen kann, hat Budget hier die aktuelle Tageszeit eintragen (23 Uhr hier in Guam), an der natürlich kein Büro offen ist.

Fazit: Wähle immer gültige Default-Werte, nie falsche.

Ein paar Tage Eclipse 3.7, was (immer noch) nervt

Installiert Eclipse IDE 3.7 for Java EE Onkels

  1. Immer wieder bekomme ich beim Starten von Apps die Meldung “Selection does not contain a main type”. Sehr ärgerlich!
  2. Schreibt man eine Klasse wie “class A impl” kann man beim “impl” Strg+Space drücken und man bekommt “implements” vervollständigt. Schreibt man jedoch “enum A impl” und drückt dann Strg+Space passiert nichts. Wie blöd, denn enums können Schnittstellen implementieren.
  3. Ein Refactoring “Extract Interface…” führt in der Schnittstelle zu public abstract-Methoden. Gar nicht schön.
  4. In JSP-Dateien wird der EL-Operator eq immer noch nicht erkannt, es gibt bei “${(10*10) ne 100} == false” ein “Syntax error on token "ne100", delete this token”. Das ist totaler Blödsinn.
  5. JSP-Daten mit EL führen zu Warnungen: ${10 mod 4} == 2 führt zu “The declared exception IOException is not actually thrown by the method _elExpression69() …”
  6. Nach dem Starten bekommt die Konsole nicht mehr automatisch den Fokus?
  7. Nimm die Deklaration

    public class Application
    {
      public static void main( String[] arguments )
      {
        int MAX = 0;
      }
    }

    und aktiviere den Quick-Assist auf MAX, um aus der lokalen Variablen eine Attribut zu machen. Eclipse nennt die Variable mAX.

  8. Refactoring funktioniert plötzlich nicht mehr. Nach dem Aufruf von Alt+Shirt+R macht Eclipse einfach nichts. Gar nichts. Bei einem anderen Refactoring bleibt Eclipse plötzlich in der Mitte stehen, benannt in meinem Fall eine Schnittstelle selbst um, aber die Nutzer nicht. Ein Neustart behob das Problem.

Kann das bitte jmd. fixen 🙂

Was sind eure Erfahrungen?

JComboxBox mit Separator nutzen

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 benutzt sie zum Beispiel, um die zuletzt vom Benutzer ausgewählten Zeichensätze prominent oben in der Liste zu haben (Excel dagegen nicht).

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

Weiterlesen

Open Symphony Projekt hat sich erledigt. Was sagt uns das?

Open Symphony versammelte Open-Source Projekte unter sich, wie die Apache Group, The Codehaus oder Eclipse Foundation. Nun haben sich die erfolgreichen Open Symphony-Projekte im Laufe der Zeit verselbständigt und Open Symphony selbst machte dicht:

Die verbleibenden Projekte sind auf neuen Domänen

Und andere sind auf java.net gewandert:

Die Frage leitet sich ab, ob Organisationen überhaupt noch nötig sind, da Projekt-Hosting so einfach geworden ist. Ich würde sagen, dass man diesen Trend nicht unbedingt ablesen kann. Denn WebWork ist zu Struts 2 geworden, und das ist ganz klar Apache und eben nicht solo, und Jetty geht den Weg von codehaus zu Eclipse. Was man vielleicht sehen kann, das gewissen Organisationen an Gewicht gewinnen und andere verlieren.

Besonders Eclipse hat viele Projekte angezogen. Das reine Sammeln ist aber an sich nicht Gutes, denn es sagt nicht aus, dass die Projekte eine hohe Priorität genießen und nicht mehr sterben – viele Projekte sind schon im Eclipse-System gestorben, TPTP ist ein Beispiel. Eclipse zeigt einen ganz neuen Weg im Release-Management auf, etwas, was bisher in der Software-Welt noch fehlt: Gewisse Zeitpunkte auszumachen und Software-Releases zu synchronisieren, sodass sie die Teilprojekte zusammen harmonisieren. Das ist gewinnbringend für OS-Software aber für kommerzielle Software. Nicht umsonst sind daher Zeitpunkte für Releases so wichtig. Sun war nicht für die Einhaltung dieser Zeiten bekannt, Oracle schon. Mit der Einführung der Java 7-Kandidaten kann jeder seine Software auf Java 7 vorbereiten und wenn Java 7 dann im Juli erscheint können auch alle anderen Produkte prinzipiell zeitgleich auf den Markt gehen.

Diverse Google-APIs werden dichtgemacht, Translate gehört dazu

In den kommen drei Jahren sollen laut http://googlecode.blogspot.com/2011/05/spring-cleaning-for-some-of-our-apis.html eine Reihe von APIs dichtgemacht werden:

Das alles fand ich nicht so tragisch, aber Translate ist heftig in der Diskussion, und Google wird das wohl noch einmal überdenken müssen. Die Google-Antwort (bisher) darauf:

This was a tough decision for us to make; we’re sorry to hear that it’s been a tough one for you to read. Thank you all for your comments, and for reminding us of the passion and energy that you bring to building great products that use our APIs. We launch a lot of APIs, many of them experimental or in Labs. This round of spring cleaning is designed to let us do a better job by focusing more effort on fewer APIs, so that you can continue to count on them. Deprecating the Translate API was the hardest choice for us to make — we’re excited about the global web, and about helping developers and webmasters anywhere reach audiences everywhere. We continue to invest in our Translate offerings, including the Google Translate web element. But the Translate API was subject to extensive abuse — the vast majority of usage was in clear violation of our terms. The painful part of turning off this API is that we recognize it affects some legitimate usage as well, and we’re sorry about that; we hope that our other offerings will cover many of those legitimate use cases.
We are listening, and we really appreciate your thoughtful responses to this post.

Nicht weiter gepflegt werden weiterhin:

Dafür kommen neue APIs dazu:

In 4 Tagen nach der Ankündigung beschäftigen sich über 150 Kommentare mit den Endscheidungen. Ein Kommentar dazu bringt es vielleicht auf den Punkt: “Your new APIs are either pure bullshit or specially aimed to promote your own affiliates.”

Eine Übersicht aller Google-APIs gibt http://code.google.com/intl/de-DE/more/.

Warten auf Eclipse 3.7.1 für Java 7

Es ist schön zu sehen, das Compiler/AST im Eclipse-Repository schon Java 7 können: http://wiki.eclipse.org/JDT_Core/Java7. Das einzige was jetzt noch fehlt ist Formatter, Indexer, Code-Assistenten, also eher UI-Stuff.

Im Moment ist Eclipse 3.6 (Hellios) aktuell und Eclipse 3.7 (Indigo) wird laut Foundation kommen: “Our target is to complete 3.7 in late June 2011”. Das heißt also, dass Eclipse 3.7 vor Java 7 fertig sein wird. (Ja, wäre schön gewesen, wenn sich Oracle und IBM/Eclipse abgesprochen hätten. Und die Versionsnummer hätten auch so gut gepasst.) Die Java 7 Funktionalität wird in Version 3.7.1 nachgeliefert.

Startausgaben vom embedded Jetty unterdrücken

Log-Ausgaben von Jetty sind speziell”. Wie http://docs.codehaus.org/display/JETTY/Debugging schreibt:

Jetty logging looks for a slf4j jar on the classpath. If found, slf4j is used to control logging otherwise stderr is used.

Dinge einfach nach System.err zu schreiben ist nicht so tolle und Jetty nutzt kein JUL. Daher bekommt man, wenn man slf4j nicht im Pfad hat, eine lästige Ausgabe wie diese:

2011-05-27 18:18:57.898:INFO::jetty-7.4.2.v20110526
2011-05-27 18:18:59.658:INFO::started o.e.j.w.WebAppContext{/,file:/C:/Users/Christian/Documents/My%20Dropbox/Development/TraidaWorkspace/Traida/war/},C:\Users\Christian\Documents\My Dropbox\Development\TraidaWorkspace\Traida\war
2011-05-27 18:19:00.053:INFO::Started SelectChannelConnector@0.0.0.0:8080 STARTING

Was tun, wenn man auf slf4j verzichten möchte? Es bleibt einem nichts anders übrig, als beim Starten temporär den System.err-Strom auf’s Nirwana zu lenken:

PrintStream err = System.err;
System.setErr( new PrintStream( new OutputStream() { public void write( int b ) { } } ) );
// Jetty hier starten
System.setErr( err );

Dann ist Ruhe im Karton.