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 );
    }
  }
} );

Osterrätsel (C, nicht Java)

Linus Torwalds stellt in seinem G+ Stream zwei C-Ausdrücke vor:

/* Modified Carl Chatfield G+ version for 32-bit */
long a = (mask-256) >> 23;
long b = mask & 1;
return a + b + 1;

/* Jan Achrenius on G+ for 64-bit case */
return mask*0x0001020304050608 >> 56;

Die Frage für Rätselfreunde ist (ohne den G+-Beitrag gelesen zu haben): Was berechnen die Ausdrücke?

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.

Eigene Linkseiten und Dokumentationen zu Java

Wie jedes System entwickelt sich Java weiter und über zentralen Neuerungen informiert der Blog. Speziell zu den neuen Java-Versionen geben dedizierte Unterseiten detaillierte Updates und eine Fülle von Links:

Nicht nur die Java SE entwickelt sich weiter, auch die Java Enterprise Edition. Einen großen Sprung gab es von J2EE 1.4 auf Java EE 5. Neues in den Enterprise JavaBeans (EJB) 3 listet die zentralen Neuerungen der Enterprise Java Beans 3 im Gegensatz zur EJB 2 auf.

In der professionellen Java-Entwicklung reicht die Java SE/Java EE nicht aus. Zu viele Lücken gibt es, und es wäre ein Fehler, wenn Entwickler-Teams Zeit aufwänden, diese Lücken selbst zu füllen. Die Seiten Open-Source Bibliotheken sowie freie und kommerzielle Swing-Komponenten geben einen Überblick, was quelloffen und kommerziell am Markt verfügbar ist.

Effektive Entwickler schreiben nicht nur wenig Code, sondern minimieren auch ihre Arbeit in Entwicklungsumgebungen, in dem sie zum Beispiel Schritte automatisieren oder Short-Cuts verwenden. Speziell für die IDE Eclipse listet die Seite über beliebte Eclipse-Plugins gute Erweiterungen auf.

Softwareentwicklung bedeutet nicht nur Software neu zu entwickeln, sondern auch bestehende Programme zu lesen und zu verändern. Aus einem misslungenen historisch gewachsenen System soll am Besten ein modernes, leicht zu pflegendes System werden. Die Refactorings von Martin Fowler auf Deutsch geben einen Eindruck, was schlecht riechender Code ist, und wie Transformationen von schlechtem Code in gutem Code aussehen.

Die Anzahl der Java-Bücher ist seit Beginn der Programmiersprache unübersichtlich geworden. Die Seite Java Bücher Hits und Flops gibt eine Übersicht über den Literaturmarkt und gibt eine Einschätzung, welche Werke für Softwareentwickler ein Gewinn darstellen.

Kritiker bemängeln, dass sich Java zu langsam entwickelt. Das ist nicht ganz falsch, doch gilt es zunächst zu unterscheiden, was damit gemeint ist. Java ist ein Dreigespann aus der Sprache, den Bibliotheken und der JVM. Änderungen an der JVM sind von der Kritik ausgenommen, es gibt kaum Eigenschaften, die bemängelt werden (Reified Generics/2 ist gewünscht). Probleme mit der Java API lassen sich durch API-Ergänzungen wie Google Guava oder Apache Commons beheben. Unter der Annahme, dass die die JVM eine solide Basis darstellt, und Interoperabilität mit den existierenden Bibliotheken gewünscht ist, lohnt sich ein Blick auf alternative Sprachen für die JVM.

JSR 357: Social Media API ist in der Entstehung

http://jcp.org/en/jsr/detail?id=357. Aufgabe:

This specification proposes to provide an API for accessing social information networks, both Public (Facebook, Twitter, Google+, LinkedIn, Xing, Yammer,…) and Corporate, e.g. within the Enterprise or Institution (University, Hospital, etc.) The primary API will build upon standards and formats specified by OpenSocial 2.0 or other relevant technologies defined by standard organizations and leading social networks.

Warum eine schlechte Webseite mich (fast) 160 € kostete und mich Air Namibia beinahe als Kunden verlor

Buchungsseiten im Internet sind kein neues Phänomen um so erstaunlicher ist es, dass es bei den Typ von Webseiten noch etwas zu optimieren gib. Als ich letztes Jahr eine weitere Reise nach Namibia plante, wählte ich Air Namibia aus, mit denen ich bisher noch nicht geflogen bin. Air Namibia fliegt von Frankfurt nach Windhoek, aber auch zu anderen Zielen. Um nach Frankfurt zu kommen kann man den Zug nehmen, denn Air Namibia bietet ein Rail & Fly Ticket an, tolle Sache!

What is Rail   Fly    Air Namibia-125410

Die HTML-Warning ist zwar peinlich, und auch, dass der Text englisch ist, obwohl “Deutsch” als Sprache voreingestellt ist, aber das ist nicht mein Problem.

Alle Flüge gehen in Deutschland von Frankfurt aus, daher ist in der Flugsuche bei “From” der Flughafen Frankfurt nur logisch:

FromAuswahl

Von dort geht es weiter, man gibt den Zielflughafen an, macht die Buchung, fertig. Einfache Sache.

Aber was ist mit dem Zugticket? Bei der Buchung wunderte mich schon, warum ich nicht auswählen konnte, dass ich Rail & Fly mit dazu buchen möchte. Wenn man bei Fluglinien wie Emirates einen Flug bucht, dann bietet diese eine schön Checkbox. Da mir das bei Air Nambia fehlt, schickte ich gleich nach der Buchung eine EMail mit der Frage ab, ob mein Ticket nun automatischer Bestandteil wäre, und wenn, wo meine IDs sind. Falls ich etwas falsch gemacht hätte, bat ich, mir das Ticket dazuzubuchen, da es für mich sehr wichtig wäre. Mehrfach frage ich nach, es ging hin und her, keine klare Antwort, immerhin eine EMail von Sylvia S.:

Dear Mr Ullenboom

As your request, rail ticket has been booked accordingly you have to pay at our office in advised.

Kind regards

“rail ticket has been booked accordingly“ Ok, gut. “Pay”? Hm, was soll das? Und “in advised”? Naja, und wo waren die Ticket-Nummern?  Ich rief in Deutschland an und man gab mir die beiden IDs für den Ticket-Automaten. Das war also mein Rail & Fly. Dachte ich.

Einen Tag vor Abflug ging ich zum Bahnhof, wollte mir dir Ticket ausdrucken, doch der Automat nahm die ID nicht an. Also rief ich wieder Air Namibia an, schilderte meiner Gesprächsperson die Situation, sie gab mir noch einmal die IDs durch. Die IDs waren die gleichen; es funktionierte nicht. Ich fragte am Telefon, was ich machen muss, aber sie sagte nur, die Daten kämen von der Bahn und sie könne mir nicht weiter helfen. Ich hatte also IDs bekommen, die warten jedoch ungültig.

Es kam, wie es kommen musste: ich musste zwei Zugtickets für insgesamt über 160 € selbst kaufen. Ich flog nach Namibia, nach ein paar Wochen wieder zurück und schickte eine EMails mit den Scan der Rechnungen, Bildschirmfotos vom Automaten mit der Zurückweisung. Ich bat um die Begleitung meiner Unkosten. Eine simple EMail reicht Air Namibia nicht, ich musste das ganze in einen Brief verpacken.

Ein paar Tage später kommt die Antwort, ich hätte Rail & Fly nicht mitgebucht. (Das hätte man auch direkt in der EMail sagen können…) Aber ich hatte doch die IDs bekommen?! Ich rief an und es stellte sich heraus, das ich tatsächlich nicht das Zugticket gekauft habe — mein Fehler. Und es stimmt, denn eine Checkbox zum Dazubuchen gibt es Air Nambia nicht. Stattdessen – und vielleicht ist es einigen Lesern schon aufgefallen – ist eben nicht der Flughafen auszuwählen, sondern ein “magischer” “Ort bei “From:”.

FromAuswahl2

Auf diese Idee bin ich nie gekommen! Also kostete mich dieser Fehler 160 €. Ist Air Nambia an dieser miesen Webseite Schuld? Im Prinzip nicht, denn das habe ich mir selbst zuzuschreiben, dass ich gewisse Annahmen über grafische Oberflächen und Buchungen als Standard voraussetzte.

Kann Air Nambia ihr Produkt verbessern? Aber klar! An vier Dingen könnte Air Nambia in meinen Augen arbeiten:

  • Auf der “Hilfe”-Seite sollte klar vermerkt werden, dass man diesen “magischen” ersten Punkt für das inkludierte Zugticket auswählen muss. Die Hilfe kannte ich, und hätte ich den Hinweis dort gelesen, wäre mir der Fehler nicht unterlaufen.
  • Diesen “magischen” Punkt absetzen und durch eine Checkbox im Buchungsverlauf ersetzen. Das würde es Kunden auch ermöglichen von London abzufliegen und nach Frankfurt zurückzukommen und dann ein Zugticket zu haben. Aber vielleicht ist diese Gabelung sowieso nicht vorgesehen …
  • Mir auch nach mehrfacher Nachfrage immer wieder die falschen IDs zu geben, anstatt klar zu formulieren, ich hätte Rail & Fly nicht gebucht. Auch: Die EMail-Kommunikation verbessern und mich nicht im Glauben lassen “rail ticket has been booked accordingly“.
  • Kunden die Option anzubieten, Rail & Fly einfach später dazuzubuchen. Das geht aber nur indirekt, in dem man den “Flug” umbucht. Die Umbuchungsbebühr betragen 165 € — die könnte Air Nambia reduzieren, da man bei dieser “Umbuchung” überhaupt nichts am Flug ändern muss. Später beim Telefonat kam es zu der Aussage, das ich mit dem eigenen Zugtickets ja noch günstiger gekommen bin. Ja super.

Schlussendlich hat Air Namibia nach meinen Telefonat noch einmal reagiert:

Wir haben Ihren Fall noch einmal intern geprüft und müssen leider mitteilen, dass sich aufgrund der Online-Buchung, die über das Air Namibia IT-Buchungssystem in Windhoek verarbeitet wurde, eine exakte Konstruktion des Buchungsvorgangs nicht mehr möglich ist. Da ein Fehler von Seiten der Air Namibia trotzdem nicht ausgeschlossen werden kann, wird Ihnen Air Namibia aus Kulanz die zusätzlich gekauften Bahntickets erstatten, denn laut Buchung ist R&F mit den zugehörigen Nummern, die Ihnen telefonisch mitgeteilt wurden, zwar hinterlegt, wurde aber eben nicht bestätigt und ausgestellt.

Dafür musste ich “nur” eine Einverständniserklärung abgeben:

Ich, C. U., erkläre mit meiner Unterschrift keine weiteren Regressansprüche gegenüber der Airline Air Namibia, deren Vertragspartnern und Handlingsagenten sowie weiteren Fluggesellschaften geltend zu machen. Mit dem Beitrag […] verzichte ich auf jegliche Schadensersatzansprüche gleich welcher Art.

Nach dem das ein paar Tage nach Valentinstag zu Air Namibia ging, erstattete mir die Airline über die AVIAREPS AG das Geld am 24.02 zurück. Schön. Ende gut.

Apache Ant 1.8.3 freigegeben

Änderungen unter http://svn.apache.org/repos/asf/ant/core/trunk/WHATSNEW aufgeführt:

Changes from Ant 1.8.3 TO Ant 1.9.0
===================================

Changes that could break older environments:
-------------------------------------------

Fixed bugs:
-----------

 * External XML catalog resolver failed to use project basedir when given an
   unmentioned relative path like the internal resolver does.
   Bugzilla Report 52754.

 * Fixed some potential stream leaks.
   Bugzilla Reports 52738, 52740, 52742, 52743.

Other changes:
--------------

Changes from Ant 1.8.2 TO Ant 1.8.3
===================================

Changes that could break older environments:
-------------------------------------------

 * The Enumeration returned by AntClassLoader#getResources used to
   return null in nextElement after hasNextElement would return false.
   It has been changed to throw a NoSuchElementException instead so
   that it now adheres to the contract of java.util.Enumeration.
   Bugzilla Report 51579.

Fixed bugs:
-----------

 * Removed buggy duplicate JAR list in RPM mode.
   Bugzilla Report 52556.

 * Launcher fixed to pass the right class loader parent.
   Bugzilla Report 48633.

 * <junitreport> mishandled ${line.separator}.
   Bugzilla Report 51049.

 * <junitreport> did not work in embedded environments on JDK 7.
   Nor did <xslt> when using Xalan redirects.
   Bugzilla Report 51668, 52382.

 * Encoding of unicode escape sequences by the property file task
   Bugzilla Report 50515.

 * The code that implicitly sets the -source switch if only -target
   has been specified in <javac> was broken for Java 5 and 6.
   Bugzilla Report 50578.

 * MailLogger ignore the Maillogger.starttls.enable property.
   Bugzilla Report 50668.

 * Delete task example does not work
   Bugzilla Report 50816.

 * <splash>'s proxy handling has been delegated to <setproxy>
   internally so the two tasks are consistent.  <splash>'s way of not
   setting a proxy caused problems with other Java libraries.
   Bugzilla Report 50888.

 * Include task breaks dependencies or extension-points for multiple
   files.
   Bugzilla Report 50866.

 * Read on System.in hangs for forked java task.
   Bugzilla Report 50960.

 * FileResource specified using basedir/name attributes was non-functional.

 * Resource collection implementation of mapped PropertySet returned
   unusable resources.

 * The hasmethod condition failed with a NullPointerException when
   ignoresystemclasses is true and Ant tried to load a "restricted
   class" - i.e. a class that the Java VM will only accept when loaded
   via the bootclassloader (a java.* class).
   It will now fail with a more useful error message.
   Bugzilla Report 51035.

 * Exec task may mix the stderr and stdout output while logging it
   Bugzilla Report 50507.

 * Missing space between "finished" and timestamp in task/target 
   finish message from ProfileLogger.
   Bugzilla Report 51109.

 * Redirecting the output of a java, exec or apply task could print in the
   error output stream some "Pipe broken" errors.
   Bugzilla Report 48789.

 * ZipFile failed to clean up some resources which could lead to
   OutOfMemoryException while unzipping large archives.
   A similar problem in ZipArchiveOutputStream has been fixed as well.
   Bugzilla Report 42696.

 * quiet attribute added to the copy and move tasks, to be used together
   with failonerror=false, so warnings won't get logged 
   Bugzilla Report 48789.

 * System.in was closed and not readable anymore by the DefaultInputHandler 
   when Ant is used via its Java API.
   Bugzilla Report 51161

 * <sync> only supported a single non-fileset resource collection even
   though the manual said it could be multiple.

 * <sync> didn't work properly when working on resource collections.
   Bugzilla Report 51462.

 * <augment> cause a NullPointerException if it was used in a target
   that was invoked by multiple targets from the command line.
   Bugzilla Report 50894.

 * The ZipFile class could read past the start of the file if the
   given file is not a ZIP archive and it is smaller than the size of
   a ZIP "end of central directory record".

 * <javac> would create the empty package-info.class file in the wrong
   directory if no destdir was specified.  Note it may still pick the
   wrong directory if you specify more than one source directory but
   no destDir.  It is highly recommended that you always explicitly
   specify the destDir attribute.
   Bugzilla Report 51947.

 * packagemapper now honors the handleDirSep attribute.
   Bugzilla Report 51086.

 * the attributes of macrodef tasks had their values run through
   property expansion twice. Still true by default, but can be disabled.
   Bugzilla Report 42046.

 * jvc doesn't like it if source file names in argument files are
   quoted.
   Bugzilla Report 31667.

 * ZipFile didn't work properly for archives using unicode extra
   fields rather than UTF-8 filenames and the EFS-Flag.

 * Access to DirectoryScanner's default excludes wasn't synchronized.
   Bugzilla Report 52188.

 * When a Project instance was created by a custom tasks its
   createTask method didn't work.
   Bugzilla Report 50788.

Other changes:
--------------

 * -f/-file/-buildfile accepts a directory containing build.xml.

 * The <javacc>, <jjtree> and <jjdoc> now support a new maxmemory
   attribute.
   Bugzilla Report 50513.

 * the documented inputstring attribute of sshexec has been
   implemented and the actually existing attribute inputproperty
   documented.
   Bugzilla Report 50576.

 * The concat task now permits the name of its exposed resource
   by means of its 'resourcename' attribute.

 * The expandproperties filter now accepts a nested propertyset
   which, if specified, provides the properties for expansion.
   Bugzilla Report 51044.

 * <junit filtertrace="true"/> will no longer filter out the very
   first line of the stacktrace containing the original exception
   message even if it matches one of the filter expressions.

 * Upgraded to Apache AntUnit 1.2

 * Provide read access to Mkdir.dir.  Bugzilla Report 51684.

 * <delete> and <move> have a new attribute performGCOnFailedDelete
   that may - when set to true - help resolve some problems with
   deleting empty directories on NFS shares.
   Bugzilla Report 45786.

 * <loadfile> and <loadresource> used to log at level INFO to signal a
   property hasn't been set when the resource was empty even if the
   quiet attribute was set to true.  They will now use VERBOSE
   instead.
   Bugzilla Report 52107.

 * <javac> has a new attribute createMissingPackageInfoClass that can
   be set to false to prevent Ant from creating empty dummy classes
   used for up-to-date-ness checks.
   Bugzilla Report 52096.

 * URLResources#isExists has become less noisy.
   Bugzilla Report 51829.

 * The <retry> task has a new optional attribute retryDelay that can
   be used to make the task sleep between retry attempts.
   Bugzilla Report 52076.

 * <signjar> has new attributes that control the signature and digest
   algorithms.
   Bugzilla Report 52344.

 * Initial support for Java 8.

 * <sshexec> can optionally create a pseudo terminal (like ssh -t)
   Bugzilla Report 52554.

Erster Milestone von Jersey 2, implementiert JAX-RS 2

Weitere Details im Blog http://marek.potociar.net/2012/02/22/first-milestone-build-of-jersey-2-0/. Für Clients gibt es vielleicht die größte Änderung: Die Einführung der JAX-RS Client API. Die API ist allerdings noch nicht verabschiedet, kann sich daher also ändern. Die bisherige Spezi von JAX-RS liegt unter http://jcp.org/aboutJava/communityprocess/edr/jsr339/index.html.