Wie Google Wave das GWT antreibt
2 Kommentar(e). Veröffentlicht von Christian Ullenboom am Sonntag, Mai 31, 2009.
Wirklich interessant ist ist die Tatsache, dass die Clientseite von Google Wave in GWT implementiert ist. Das ist in meinen Augen ein unschlagbares Argument für die Ausgereiftheit von GWT, wenn Google das in einem Projekt einsetzt, was von unglaublichen vielen Menschen verwendert werden wird und definitiv performant laufen muss. Dabei sollte laut einem Blog-Eintrag http://mtwong.ning.com/profiles/blogs/google-io-conference-google-1 Wave auch überhaupt nicht in GWT entwickelt werden, da der Projektleiter Zweifel hatte, GWT würde gescheit laufen. Aber Wave hat wohl auch die Entwicklung von GWT positiv beeinflusst, etwa bei den Tools. Der Blog-Eintrag erwähnt interessante weitere Projekte und GWT-Features:
Labels: GWT
Update JDK 1.6.0_14 (6u14)
3 Kommentar(e). Veröffentlicht von Christian Ullenboom am Freitag, Mai 29, 2009.Änderungen listet http://java.sun.com/javase/6/webnotes/6u14.html auf. Interessant sind meines Erachtens: Compressed Object Pointers und Garbage First (G1) Garbage Collector, weil diese Dinge sind, die für Java 7 vorgesehen waren. Aber nun kann man die schon mal “in the wild” testen, was eine gute Sache ist. Dann noch JAX WS 2.1.6 and JAXB 2.1.10 und ein Update von JavaDB (wobei Derby schon deutlich weiter ist, komisch).
Labels: Java SE
Vorschläge für Projekt Coin (Java 7 Sprachänderungen) geht in die nächste Runde
0 Kommentar(e). Veröffentlicht von Christian Ullenboom am Freitag, Mai 29, 2009.Strings in switch,
Joe DarcyImproved Exception Handling for Java,
Neal GafterAutomatic Resource Management,
Josh BlochImproved Type Inference for Generic Instance Creation,
Jeremy MansonElvis and Other Null-Safe Operators, Neal Gafter, Stephen Colebourne
Simplified Varargs Method Invocation,
Bob Lee
Integer-Literale:
Byte and Short Integer Literal Suffixes,
Bruce ChapmanBinary Literals,
Derek FosterUnderscores in numbers,
Derek Foster
Language support for JSR 292
(wiki),
John RoseIndexing access syntax for Lists and Maps,
Shams Mahmood ImamCollection Literals,
Joshua BlochLarge arrays (revised),
James Lowden
Labels: Java 7
Initialisierung von Schnittstellenkonstanten
2 Kommentar(e). Veröffentlicht von Christian Ullenboom am Donnerstag, Mai 28, 2009.Eine Schnittstelle kann Attribute deklarieren, aber das sind dann immer initialisierte public final static Konstanten.
import java.util.Properties;
public interface PropertyReader
{
Properties DEFAULT_PROPERTIES = new Properties();
Properties getProperties();
}
Würden wir DEFAULT_PROPERTIES nicht mit new Properties() initialisieren, gäbe es einen Compilerfehler.
Nun stellt sich das Problem, wenn die statischen Attribute nicht einfach mit einem Objekt initialisiert werden können, sondern wenn zusätzlicher Programmcode zur Initialisierung gewünscht ist. Für unser Beispiel soll das Properties-Objekt unter dem Schlüssel date die Zeit speichern, an der die Klasse initialisiert wurde. Über statische Initialisierer ist dies jedenfalls nicht möglich.
import java.util.*;
public interface PropertyReader
{
Properties DEFAULT_PROPERTIES = new Properties();
static
^
// Hier gibt's den Compilerfehler
// "Interfaces can't have static initializers."
{
DEFAULT_PROPERTIES.setProperty( "date", new Date().toString() );
}
Properties getProperties();
}
Zwar sind statische Initialisierungsblöcke nicht möglich, aber mit zwei Tricks kann die Initialisierung erreicht werden. Eine innere anonyme Kasse ist eine Lösung:
import java.util.*;
public interface PropertyReader
{
Properties DEFAULT_PROPERTIES = new Properties() { {
setProperty( "date", new Date().toString() );
} };
Properties getProperties();
}
Diese Lösung funktioniert allerdings nur, wenn keinen primitiven Werte über extra Programmcode initialisiert werden, und wenn es nicht-finale Klassen sind, von den überhaupt Unterklassen erlaubt sind.
Mit einem anderen Trick lassen sich auch diese Hürden nehmen. Die Idee liegt in der Einführung einer inneren statischen Klasse, die wir $$ nennen wollen. Innerhalb dieser Klasse platzieren einen static-Block. Anschließend muss nur noch eine Dummy-Variable gesetzt werden, damit der Initialisierungsblock der inneren Klasse auch ausgeführt wird, wenn die Klasse geladen wird. Dazu definieren wir die Variable $. Sie ist static und final, also somit eine echte Konstante. Da leider innere Klassen und Konstanten von Schnittstellen nicht privat sein können, und so unglücklicherweise von außen zugänglich sind, geben wir ihnen die schönen kryptischen Namen $ und $$, sodass sie nicht so attraktiv erscheinen.
import java.util.*;
public interface PropertyReader
{
Properties DEFAULT_PROPERTIES = new Properties();
PropertyReaderInit $ = new $$();
static final class $$
{
static
{
DEFAULT_PROPERTIES.setProperty( "date", new Date().toString() );
}
}
Properties getProperties();
}
Innerhalb von static-Block lässt sich auf das Properties-Objekt zugreifen und somit auch die Werte eintragen. Ohne die Erzeugung des Objekts $ geht es nicht, denn andernfalls würde die Klasse $$ nicht initialisiert werden. Das Programm kann nun alle Elemente wie folgt nutzen:
import java.util.Properties;
public class SystemPropertyReaderDemo implements PropertyReader
{
@Override public Properties getProperties()
{
return System.getProperties();
}
public static void main( String[] args )
{
System.out.println( PropertyReader.DEFAULT_PROPERTIES ); // {date=Thu …
}
}
Hinweis: Aufzählugen über enum können einfacher initialisiert werden.
Labels: Insel
Spielerei mit Javas dynamischer Bindung und überschatteten Attributen
0 Kommentar(e). Veröffentlicht von Christian Ullenboom am Donnerstag, Mai 28, 2009.Werfen wir ein Blick auf folgendes Java-Programm:
class SuperBoaster
{
int nr = 1;
void boast()
{
System.out.println( "Ich bin die Nummber " + nr );
}
}
public class SubBoaster extends SuperBoaster
{
int nr = 2;
@Override void boast()
{
super.boast(); // Ich bin die Nummber 1
System.out.println( super.nr ); // 1
System.out.println( nr ); // 2
}
public static void main( String[] args )
{
new SubBoaster().boast();
}
}
Die Methode boast() aus SubBoaster ruft mit super.boast() die Methode der Oberklasse auf. Ein einfacher Aufruf von boast() in der Unterklasse würde in eine Rekursion führen. Die Unterklasse hat mit super.nr Zugriff auf die überschattete Objektvariable nr aus der Oberklasse. Es ist super wie this eine spezielle Referenz und kann auch genauso eingesetzt werden, nur das super in den Namensraum der Oberklasse geht.
Eine Aneinanderreihung von super-Schlüsselwörtern bei einer tieferen Vererbungshierarchie ist nicht möglich. Hinter einem super muss eine Objekteigenschaft stehen und Anweisungen wie super.super.nr sind somit immer ungültig.
Für Variablen gibt es eine Möglichkeit, die sich durch einen Cast in die Oberklasse ergibt. Setzen wir in boast() der Unterklasse folgende Anweisung
System.out.println( ((SuperBoaster) this).nr ); // 1
Die Ausgabe 1 ist also identisch mit System.out.println( super.nr ).
Die this-Referenz entspricht einem Objekt vom Typ SubBoaster. Wenn wir dies aber in den Typ SuperBoaster konvertieren, bekommen wir genau das nr aus der Basisklasse unserer Hierarchie. Wir erkennen hier eine sehr wichtige Eigenschaft von Java, nämlich, dass Variablen nicht dynamisch gebunden werden. Anders wäre es, wenn wir folgendes in die Methode boast()der Unterklasse SubBoaster setzen:
((SuperBoaster)this).boast();
Hier ruft die Laufzeitumgebung nicht boast() aus SuperBoaster auf, sondern die aktuelle Funktion boast(), aus SubBoaster sodass wir in einer Rekursion landen. Der Grund dafür liegt in der dynamischen Bindung zur Laufzeit, die ein Compiler-Typecast nicht ändert.
Labels: Insel
Bildschirmabzüge (Screenshots) und die Frage, ob der Benutzer etwas vor dem Bildschirm tut
2 Kommentar(e). Veröffentlicht von Christian Ullenboom am Donnerstag, Mai 28, 2009.Eine sehr interessante Funktion der Robot-Klasse ist createScreenCapture(). Mit ihr lässt sich ein Bildschirmabzug (engl. screenshot) ohne Mauszeiger (zumindest unter Windows, Linux, Solaris und Mac OS X) machen. Die Funktion erwartet als Argument ein Rectangle-Objekt (speichert x, y, height und width), das den zu »fotografierenden« Bereich spezifiziert. Das Ergebnis von createScreenCapture() ist ein BufferedImage-Objekt, das direkt mit ImageIO in eine Datei kommen kann.
BufferedImage bi = new Robot().createScreenCapture(
new Rectangle(Toolkit.getDefaultToolkit().getScreenSize()) );
ImageIO.write( bi, "jpg", new File("c:/screenshot.jpg") );
Mit createScreenCapture() lassen sich sehr interessante Lösungen realisieren. So lässt sich zum Beispiel überwachen, ob sich Bildschirminhalte ändern. Mit einer Bildanalyse ließe sich auch herausfinden, was der Anwender sich gerade anschaut.
In einem kleinen Beispiel wollen wir feststellen, ob der Benutzer aktiv vom dem Bildschirm ist oder nicht. Wir machen dies an der Anzahl Pixel fest, die sich jede Sekunden verändern. Ein Programm soll dazu jede Sekunde ein Bildschirmabzug nehmen und ausgeben, um wie viel Prozent sich die Pixel gegenüber dem Vorgängerbild verändert haben.
package com.tutego.insel.ui.image;
import java.awt.*;
import java.awt.image.*;
public class ImageDiffs
{
public static void main( String args[] ) throws Exception
{
Rectangle screenSize = new Rectangle( Toolkit.getDefaultToolkit().getScreenSize() );
BufferedImage image1 = new Robot().createScreenCapture( screenSize );
while ( true )
{
Thread.sleep( 1000 );
BufferedImage image2 = new Robot().createScreenCapture( screenSize );
DataBuffer dataBuffer1 = image1.getData().getDataBuffer();
DataBuffer dataBuffer2 = image2.getData().getDataBuffer();
int total = dataBuffer1.getSize(), diff = 0;
for ( int i = 0; i < total; i++ )
if ( dataBuffer1.getElem( i ) != dataBuffer2.getElem( i ) )
diff++;
System.out.printf( "Pixel total=%d, unterschiedliche Pixel=%d, Unterschied=%.2f%%%n",
total, diff, (double) 100 * diff / total );
image1 = image2;
}
}
}
Nach dem der Bildschirmabzug gemacht wurde, gilt es auf die Pixel zuzugreifen und die beiden Bilder zu vergleichen. Um das Bild abzulaufen, könnten wir entweder vom BufferedImage mit den Methode getRGB(int x, int y) arbeiten, oder uns – anders macht es getRGB() auch nicht – das Datenmodel der Grafik holen und es einfach komplett auflaufen; die Größe und Breite der beiden Bildschirmabzüge ist ja immer gleich, und so spielt die Höhe und Breite keine Rolle. Von den beiden DataBuffer-Objekten, die die Rohdaten der Grafik repräsentieren, erfragt getElem() den Farbwert und wenn es Unterschiede gibt, inkrementiert das Programm einen Zähler. Nach dem Ablaufen aller Werte gibt es eine Statistik, die etwa so beginnen kann:
Pixel total=2304000, unterschiedliche Pixel=297, Unterschied=0,01%
Pixel total=2304000, unterschiedliche Pixel=1708, Unterschied=0,07%
Pixel total=2304000, unterschiedliche Pixel=1689, Unterschied=0,07%
Pixel total=2304000, unterschiedliche Pixel=1000568, Unterschied=43,43%
Pixel total=2304000, unterschiedliche Pixel=1002525, Unterschied=43,51%
Pixel total=2304000, unterschiedliche Pixel=2087, Unterschied=0,09%
Labels: Insel
Hast du nix zu tun, schreibst du eine neue IDE …
1 Kommentar(e). Veröffentlicht von Christian Ullenboom am Mittwoch, Mai 27, 2009.Das ist zumindest das Ziel von https://gravityide.dev.java.net/. Eine “Easy-to-use Java IDE for Java and other JVM languages”. Problem nur: Was einfach mit einem Editor beginnt wird (zwangsläufig) komplexer und komplexer und komplexer. Man kann darüber streiten, was – nach einer gewissen Einarbeitungszeit – nun intuitiver ist, IntelliJ, NetBeans, Eclipse, JDevelper (oder vi?), aber dass es bei einem Editor mit farblicher Hervorhebung nicht getan ist, ist klar. Und wer mit lauten Sprüchen wie “Gravity will be the only IDE you'll need.” wirbt, und mit dem Satz “A GUI editor so you can make forms in seconds!” schreit, macht sich keine Freunde, wenn gravityide Vaporware bleibt; gravityide bleibt also vorerst das Duke Nukem Forever der Java-IDEs. Toll!
Labels: Entwicklungsumgebung
Labels: Open Source
- Unified DataBoundComponent interface which is implemented by DynamicForm, ListGrid, TreeGrid, Menu, ColumnTree, TileGrid, DetailViewer and Calendar.
- Consistent representation of a Record which can be used by any DataBoundComponent
- Support for GWT-RPC DataSource with DataBoundComponents
- Various component and skin performance improvements
- Formula and Summary fields:
built-in wizards for end users to define formula fields that can
compute values using other fields, or summary fields that can combine
other fields with intervening / surrounding text. Available in all
DataBoundComponents, easy to persist as preferences
- I18n support with resource bundles from 14 locales
- Significant improvements to Javadocs with tons of documentation on various concepts. (over 30MB on disk)
- API's to save and restore the display and selection state of various components to/from the server
- Automatic databound dragging
behaviors: grids and trees will now inspect data relations declared in
DataSources and automatically “do the right thing” for a databound drag
- Numerous enhancements. See the detailed API Changes document
- Over 50 additional enhancements and bug fixes that were logged in tracker
- Several new events and extension points on various components to allow advanced usage
- A helloworld-1.6 starter project for SmartGWT with GWT 1.6
- Several new examples in the SmartGWT Showcase (see "New Samples" side nav item in the Showcase)
Key API's added
DateUtil
- Ability to globally set the Date input and rendered formats across the application
DataBoundComponent
- ability to select / deselect records, fetch / filter data and carry out various other operations on any DataBoundComponent
DataSource and DataSourceField
- ability to specify custom field value extractor logic for a given DataSourceField
- API's to control http caching and result batch size
- DataSourceField.setCanSortClientOnly
Window
- listeners for minimize, maximize, restore and close
Calendar
- support for customizing event edit dialog fields
- event remove listener
Forms
- convenience constructors for various FormItems
- support for customizing form field and title tooltips
- TextItem : support for character casing and entry restrictions (regexp based)
- TextItem : support for hints displayed in-field
- ComboBoxItem : fetchDelay setting
- AutoFitTextAreaItem: autofits to contained text as the user types
Column Tree (Miller Columns)
- added node selected listener
ListGrid
- Grouping modes: built-in and custom grouping modes, such as the ability to group a date column by week, day, month or year
- added header click listener
- added record drop listener
- ability to customize grid groupings by record values and also group title
- support for adding custom formula and summary fields on the fly
- ability to register custom handler to control visibility of a ListGridField
- added ListGridField.setCanFreeze, ListGridField.setCanSort
SectionStack (Accordion)
- added section header click event
TreeGrid
- added folder drop listener
- ability to override getIcon() / getNodeTitle() to allow users to have custom node icon / title logic
Labels: GWT
Mit VisualVM durch den Speicher wühlen: Heap Dump
2 Kommentar(e). Veröffentlicht von Christian Ullenboom am Dienstag, Mai 26, 2009.VisualVM ist eine grafische Oberfläche mit einfachen Profiling-Möglichkeiten und einer grafischen Oberfläche etwa für das JDK-Tool jstack. VisualVM ist Teil von Java 6 (dort heißt es Java VisualVM), aber eine aktuelle Version findet sich immer unter https://visualvm.dev.java.net/; diese Version heißt dann einfach VisualVM. Nutzen wir die Version von der Webseite im Folgenden.
VisualVM wird als Zip-Archiv (etwa visualvm_111.zip) angeboten. Nach dem Auspacken befindet sich unter dem bin-Ordner das ausführbare Programm visualvm.exe, das wir starten können. (VisualVM lässt sich über ein Plugin auch über Eclipse, NetBeans oder IntelliJ einbinden.) Beim ersten Mal müssen wir noch die Lizenzen abnicken und eine Kalibrierung starten, doch dann öffnet sich schon die grafische Oberfläche. Wählen wir links im Baum Local > VisualVM aus, so schauen wir uns die Zustände, etwa den Speicherbedarf und Thread-Auslastung des Programms VisualVM selbst an.
Durch den Speicher wühlen: Heap Dump
Eine großartige Möglichkeit von VisualVM ist, sich während der Laufzeit zu einem Programm zu verbinden, und über die Objektverweise zu navigieren. Beispiel soll ein kleines Programm HeapUser sein, von dem wir später die vier Objektvariablen untersuchen wollen.
HeapUser.java
import java.util.*;
public class HeapUser
{
String string = "Hallo Welt";
Date date = new Date();
ArrayList<String> list = new ArrayList<String>( Arrays.asList( string, date.toString() ) );
HeapUser heapUser;
public static void main( String[] args )
{
HeapUser h = new HeapUser();
h.heapUser = h;
new Scanner( System.in ).next();
System.out.println( h.string );
}
}
Starten wir das Programm und VisualVM läuft noch im Hintergrund, so erkennt VisualVM automatisch das gestartete Program und aktualisiert die Baumansicht unter local.
Im Kontextmenü auf HeapUser lässt sich der HeapDump erfragen.
Nach dem Aktivieren des Schalters Classes sind alle geladenen Klassen aufgeführt, und wie viele Exemplare es von den Klassen gibt.
Unten gibt es ein Suchfeld, in dem wir HeapUser eintragen. Es bleibt eine Klasse in der Liste.
Im Kontextmenü lässt sich nun Show in Instances View aufrufen.
Die folgende Ansicht bildet den Ausgangspunkt für exploratives Arbeiten.
Links ist abgebildet die Instanz, die wir untersuchen. Das ist HeapUser, von dem es genau ein Exemplar gibt (#1). Rechts gibt es zwei Einteilungen. In der oberen Einteilung können wir die Objekteigenschaften vom links ausgewählten Objekt sehen und durch die Baumansicht tiefer reinzoomen. So enthält this, also das ausgewählte Objekt, die Variablen heapUser, list, date und string. An den auf sich selbst verweisenden Pfeilen an heapUser lässt sich – die Symbolen werden in einer Art Statusleiste kurz erklärt – erkennen, dass die Variable heapUser das eigene Objekt referenziert. Falten wir list auf, so sehen wie die Objektvariablen der ArrayList-Instanz im Baum, und unter anderem lässt sich die size ablesen, also die Anzahl Elemente in der Liste. elementData wiederum ist ein Knoten, der sich auffalten lässt, der er repräsentiert das interne Feld – die eckigen Klammern deuten den Typ „Feld“ an – der ArrayList. Wird er ausgefaltet, gelangen wir zu den beiden Strings. Im unteren Bereich der Einteilung, bei References, ist abzulesen, wer das selektierte Objekt referenziert. Es gibt zwei Stellen, an denen das untersuchte HeapUser-Objekt referenziert wird: Einmal über die lokale Variable in der main-Methode, und einmal über die Objektvariable.
Profiling von Java-Applikationen
Ein Profiler zeigt an, an welchen Stellen ein Programm Prozessorzeit verbraucht. Auf der Webseite https://visualvm.dev.java.net/profiler.html stellt Sun Dokumentation bereit, wie VisualVM als Profiler genutzt wird.
Labels: Insel
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.
Labels: Open Source
Thema der Woche: Speicherverwaltung, Memory-Leaks
0 Kommentar(e). Veröffentlicht von Christian Ullenboom am Montag, Mai 25, 2009.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?
- FinalizablePhantomReference
- FinalizableReference
- FinalizableReferenceQueue
- FinalizableSoftReference
- FinalizableWeakReference
Labels: Die wöchentliche Dosis Java
Texte umbrechen (Word-Wrap)
2 Kommentar(e). Veröffentlicht von Christian Ullenboom am Montag, Mai 25, 2009.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 …
Labels: Open Source
Pluggable Annotation Processing API
0 Kommentar(e). Veröffentlicht von Christian Ullenboom am Mittwoch, Mai 20, 2009.Annotationen lassen sich in zwei Gruppen einteilen (andere Einteilungen sind möglich):
· Compiler-Annotationen: @Override, @Deprecated, @SuppressWarnings
· Programm-Annotationen, die zur Laufzeit ausgewertet werden: @Resource, @WebService, @WebMethod, …
Zwischen dem, was Compiler sieht und zwischen dem, was ein Programm zur Laufzeit auswertet, gibt es noch eine weitere Ebene, die der Tools.[1] Wir können den Compiler schlecht erweiterten, wenn er auf Grund von eigener Annotationen gewisse Prüfungen vornehmen soll (etwa ob eine mit @Entity annotierte Klasse einen Standardkonstruktor besitzt), oder Artefakte, also etwa andere Java-Klassen oder XML-Dokumente generieren soll. Zur Laufzeit wäre das zu spät.
Aus diesem Grund können sogenannte Custom Annotation Processors eingesetzt werden. Sie werden vom Java-Compiler berücksichtigt (der mit einem Schalter auch kein Bytecode generieren muss) und haben Zugriff auf die Strukturdaten der Java-Typen, also Klassennamen, Methodennamen, usw. Das ist ein bisschen wie statisches Reflection, nur dass eben Reflection zur Laufzeit und mit Hilfe der JVM arbeitet und die Annotations-Prozessoren mit Hilfe des Compilers. Die Infrastruktur hat Sun in Java 5 vorbereitet – und damals das „Annotation Processing Tool“ (apt) in Umlauf gebracht – und noch einmal in Java 6 nachgebessert und über die JSR 269 (http://jcp.org/en/jsr/detail?id=269) genauer spezifiziert.
Der erste Annotations-Prozessor
Um einen Annotations-Prozessor in den Compiler einzuhängen sind zwei Schritte nötig:
· Zunächst wird mit Hilfe der Pluggable Annotation Processing API eine Klasse, der eigentliche Annotations-Prozessor geschrieben. Dieser kann auf die annotierten Elemente zurückgreifen und sogar in den Compilerprozess eingreifen.
· Der Annotations-Prozessor muss dem Compiler bekannt gemacht werden. Das geschieht entweder über einen Eintrag in einer besonderen Textdatei oder über den Compilerschalter -processor bei javac.
1.1.1 Einen Annotations-Prozessor schreiben
Jeder Annotations-Prozessor implementiert die Schnittstelle javax.annotation.processing.Processor. Es ist allerdings nicht nötig, diese direkt zu implementieren; Java bietet mit AbstractProcessor eine Klasse, in der nur noch eine Methode realisiert werden muss: process(). Genau diese Methode enthält den Kern der Anwendung, in dem auf die die Annotationen zurückgegriffen werden kann. Der process()-Methode übergibt der Java-Compiler alle Informationen. Geben wir diese Informationen in unserem ersten Beispiel aus:
com.tutego.insel.annotation.apt.FirstProcessor.java
package com.tutego.insel.annotation.apt;
import java.util.Set;
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic.Kind;
@SupportedAnnotationTypes( "*" )
@SupportedSourceVersion( SourceVersion.RELEASE_6 )
public class FirstProcessor extends AbstractProcessor
{
@Override
public boolean process( Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv )
{
String msg = String.format( "%n %s%n %s",
roundEnv.toString(), annotations.toString() );
processingEnv.getMessager().printMessage( Kind.NOTE, msg );
return false;
}
}
Der Annotations-Prozessor ist selbst annotiert mit @SupportedAnnotationTypes – was bestimmt, auf welche Annotationen der Annotations-Prozessor hört (mit * reagiert er auf alle Annotationen) – und mit @SupportedSourceVersion.
Beginnt der Java-Compiler den Annotations-Prozessor zu initialisieren, gibt es einen vordefinierten Lebenszyklus. Es beginnt mit dem Aufbau über den Standardkonstruktor. Dann ruft er die Methode void init(ProcessingEnvironment) auf. Praktischerweise implementiert AbstractProcessor diese Methode schon und merkt sich die übergebene ProcessingEnvironment-Referenz in der protected Variablen processingEnv. Anschließend folgt ein Aufruf von process(). Die Bedeutung der Argumente lässt sich am Besten an der Ausgabe ablesen, wenn der Annotations-Prozessor eingebunden ist und arbeitet.
1.1.2 Einen Annotations-Prozessor einbinden
Entweder wird der Annotations-Prozessor über den Schalter -processor bei javac angegeben oder in einer Datei javax.annotation.processing.Processor vermerkt, die sich im Verzeichnis META-INF/services befindet. Wählen wir diesen Weg, denn damit kann die Angabe bei jedem Compileraufruf unterbleiben.
META-INF/services/javax.annotation.processing.Processor
com.tutego.insel.annotation.apt.FirstProcessor
Da unser FirstProcessor auch etwas zu verarbeiten haben muss, setzten wir drei Typen in einer Compilation-Unit in das Default-Paket.
FullOfAnnotations.java
import javax.annotation.Resource;
import javax.jws.WebService;
import javax.xml.bind.annotation.*;
@XmlRootElement public class FullOfAnnotations
{
@Resource String string;
@XmlElement String blab;
}
@XmlRootElement @WebService interface FullOfAnnotations2
{
@Deprecated void foo( int i );
}
enum NotFullOfAnnotations { }
Unser Annotations-Prozessor wird also später drei Typen finden (auch wenn NotFullOfAnnotations keine Annotation besitzt!) und insgesamt fünf Annotationen.
Wechseln wir für einen Test auf die Kommandozeile, so verdeutlicht ein dir die Verzeichnisstruktur:
$ dir /B
com
META-INF
FullOfAnnotations.java
Im Unterverzeichnis com liegen nun die anderen Klassen und auch der Annotations-Prozessor, in META-INF gib es das Unterverzeichnis services, in dem wiederum die javax.annotation.processing.Processor liegt. Dann gibt es unsere Testklasse FullOfAnnotations, die nun kompiliert werden soll. Dabei wird sich der Annotations-Prozessor einschalten und Meldungen ausgeben:
$ javac *.java
Note:
[errorRaised=false, rootElements=[FullOfAnnotations, FullOfAnnotations2, NotFullOfAnnotations], processingOver=false]
[javax.annotation.Resource, java.lang.Deprecated, javax.jws.WebService, javax.xml.bind.annotation.XmlRootElement, javax.xml.bind.annotation.XmlElement]
Note:
[errorRaised=false, rootElements=[], processingOver=true]
[]
Drei Dinge lassen sich an der Ausgabe auslesen:
1. roundEnv.toString() in process() zeigt die rootElements, also genau die Typen, die der Annotations-Prozessor verarbeitet hat. Darunter ist auch NotFullOfAnnotations, was überhaupt keine Annotationen hat, doch der Annotations-Prozessor hat dennoch diesen Typ gesehen.
2. annotations.toString() zeigt alle Annotationen an, die während der gesamten Durchlaufs aller Klassen gefunden wurde.
3. Der Annotations-Prozessor läuft in zwei Runden. Der Grund ist einfach: In jeder Runde könnten sich Programminformationen ändern, sodass ein neuer Lauf nötig ist, auch diese Änderungen einzusammeln. Wir sehen in der Ausgabe, dass beim zweiten Lauf keine Neuerungen hinzugekommen sind, und damit keine weitere Iteration nötig ist. Die letzte Anzeige zeigt das mit „processingOver=true” an.
1.1.3 Weitere Methoden der Pluggable Annotation Processing API
Im Folgenden sollen ein paar Code-Idiome vorgestellt und weitere Methoden der Pluggable Annotation Processing API vorstellt werden.
Wenn immer eine Runde gedreht wird, gibt es etwas zu tun. Ein Annotations-Prozessor enthält in der Regel die Fallunterscheidung
if ( ! roundEnv.processingOver() )
...
um nur dann etwas zu tun, wenn die Verarbeitung noch nicht gelaufen ist.
Zugriff auf die Wurzelelemente, also alle vom Annotations-Prozessor gefundenen Typen, liefert roundEnv.getRootElements(). Das Ergebnis ist eine Menge von javax.lang.model.element.Element-Objekten.
Set<? extends Element> elements = roundEnv.getRootElements();
Element selbst ist eine Schnittstelle und von ihr gibt es diverse Unterschnittstellen. Die Methode getKind() zeigt, mit welchem konkreten Element wir es wirklich zu tun haben. Lassen wir eine Schleife über unsere Wurzelelemente laufen:
for ( Element element : roundEnv.getRootElements() )
processingEnv.getMessager().printMessage( Kind.NOTE, element.getKind().toString() );
Die Ausgabe wäre:
Note: CLASS
Note: INTERFACE
Note: ENUM
Von den Typen aus lässt es sich weiter in die Tiefe laufen. Dafür bietet die Element-Klasse die Methode getEnclosedElements(). Das Ergebnis ist vom Typ List<? extends Element>. Um alle Attribute der vom Annotations-Prozessor gefunden Klassen auszugeben, schreiben wir:
for ( Element element : roundEnv.getRootElements() )
{
processingEnv.getMessager().printMessage( Kind.NOTE, element.getSimpleName() );
for ( Element enclosedElement : element.getEnclosedElements() )
if ( enclosedElement.getKind() == ElementKind.FIELD )
processingEnv.getMessager().printMessage( Kind.NOTE,
" " + enclosedElement.getSimpleName() );
}
Die Augabe ist:
Note: FullOfAnnotations
Note: string
Note: blab
Note: FullOfAnnotations2
Note: NotFullOfAnnotations
Die Elemente könnten nun weiter untersucht werden, oder auf den konkreten Typ gecastet werden, was zu zusätzliche Methoden führt.
Interessieren uns für alle Methoden (und Konstruktoren), schreiben wir:
for ( Element enclosedElement : element.getEnclosedElements() )
if ( enclosedElement instanceof ExecutableElement )
{
ExecutableElement e = (ExecutableElement) enclosedElement;
String msg = e.getModifiers() + " " + e.getReturnType() + " " +
e.getSimpleName() + "( " + e.getParameters() + " ) " +
e.getThrownTypes();
processingEnv.getMessager().printMessage( Kind.NOTE, " " + msg );
}
Wenn sowieso nur eine beschränkte Anzahl von Annotationen in Betracht kommen, sollte @SupportedAnnotationTypes präzisiert werden. Wir wollen das in einem letzten Beispiel nutzen, um mit @Entity deklarierte Klassen zu testen, ob sie einen nicht-parametrisierten Konstruktor besitzen.
Zunächst die Annotation:
com.tutego.insel.annotation.apt.javax.persistence.Entity.java
package com.tutego.insel.annotation.apt.javax.persistence;
public @interface Entity { }
Unser Annotationstyp Entity ist eine sehr einfache Kopie vom Annotationstyp aus javax.persistence.
Eine Beispielkasse RealEntity soll korrekterweise einen Standardkonstruktor haben:
com.tutego.insel.annotation.apt.RealEntity.java
package com.tutego.insel.annotation.apt;
import com.tutego.insel.annotation.apt.javax.persistence.Entity;
@Entity
public class RealEntity
{
public RealEntity() { }
public RealEntity( String name ) { }
}
Aber die Klasse NoRealEntity nicht:
com.tutego.insel.annotation.apt.RealEntity.java, RealEntity
@Entity public class NoRealEntity
{
public NoRealEntity( String name ) { }
}
Der Annotations-Prozessor ist nicht schwierig. Bei SupportedAnnotationTypes geben wir Entity an und dann laufen wir alle Methoden ab, die <init> heißen und testen, ob die Parameterliste leer ist. (Sichtbarkeiten, Exceptions, Modifizierer betrachten wir nicht.)
package com.tutego.insel.annotation.apt;
import java.util.Set;
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.*;
import javax.tools.Diagnostic.Kind;
@SupportedAnnotationTypes( "com.tutego.insel.annotation.apt.javax.persistence.Entity" )
@SupportedSourceVersion( RELEASE_6 )
public class CheckEntityBeansForDefaultConstructorProcessor extends AbstractProcessor
{
@Override
public boolean process( Set< ? extends TypeElement> annotations,
RoundEnvironment roundEnv )
{
if ( ! roundEnv.processingOver() )
for ( Element element : roundEnv.getRootElements() )
{
boolean foundDefaultContructor = false;
for ( Element ee : element.getEnclosedElements() )
if ( ee instanceof ExecutableElement )
{
if ( "<init>".equals( ((ExecutableElement) ee).getSimpleName().toString() )
&& ((ExecutableElement) ee).getParameters().isEmpty() )
foundDefaultContructor = true;
}
if ( ! foundDefaultContructor )
processingEnv.getMessager().printMessage( Kind.ERROR,
element.getSimpleName() + " hat keinen Standardkonstruktor!" );
}
return false;
}
}
Ein kleiner Eintrag in die javax.annotation.processing.Processor-Datei und damit ist die Vorbereitung erledigt.
META-INF/services/javax.annotation.processing.Processor
# com.tutego.insel.annotation.apt.FirstProcessor
com.tutego.insel.annotation.apt.CheckEntityBeansForDefaultConstructorProcessor
Auf der Kommandozeile kann das nun getestet werden:
$ javac com/tutego/insel/annotation/apt/*.java
error: NoRealEntity hat keinen Standardkonstruktor!
1 error
Nehmen wir in RealEntity zum Beispiel den Standard-Konstruktur auch heraus, folgt:
$ javac com/tutego/insel/annotation/apt/*.java
error: NoRealEntity hat keinen Standardkonstruktor!
error: RealEntity hat keinen Standardkonstruktor!
2 errors
Um den Java-Compiler nur als Annotations-Prozessor-verarbeitende Maschine einzusetzen, wenden wir den Schalter -proc:only an.
$ javac -proc:only com/tutego/insel/annotation/apt/*.java
[1] Das wird auch klar, wenn wir uns vor Augen führen, dass die Annotation @Retention steuert, wer die Annotation sehen kann. Und dass es in RetentionPolicy drei Konstanten gibt: SOURCE, CLASS, RUNTIME.
Labels: Insel
Ext GWT Beispiel einer editierbaren Tabelle
0 Kommentar(e). Veröffentlicht von Christian Ullenboom am Dienstag, Mai 19, 2009.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() )
…
Labels: GWT, Web Frameworks
Labels: Eclipse, Entwicklungsumgebung
Ext GWT BeanBinding Beispiel
0 Kommentar(e). Veröffentlicht von Christian Ullenboom am Dienstag, Mai 19, 2009.Ext GWT bietet eine einfache Möglichkeit, JavaBeans an Formulare zu binden. Im ersten Schritt muss die Bean dazu vorbereitet werden.
- Die JavaBean implementiert das Marker-Interface com.extjs.gxt.ui.client.data.BeanModelTag.
- 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.
Labels: GWT, Web Frameworks
Wenn Captchas zu langweilig sind, wie wär’s mit Kätzchen?
2 Kommentar(e). Veröffentlicht von Christian Ullenboom am Dienstag, Mai 19, 2009.Dass Captchas wie


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.
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/.
Labels: Web Frameworks
Thema der Woche: Domain Driven Design (DDD)
0 Kommentar(e). Veröffentlicht von Christian Ullenboom am Montag, Mai 18, 2009.Lies
- http://www.infoq.com/articles/ddd-in-practice
- http://www.martinfowler.com/bliki/AnemicDomainModel.html
Wirf einen Blick auf die Tools, die das erste Dokument aufführt:
Aufgabe: Schreibe zu jedem Tool einen Satz, der das Tool definiert und das Einsatzgebiet erklärt.Labels: Die wöchentliche Dosis Java
Beans aus XML Schema-Datei generieren, xjc, JAXB Plugins
0 Kommentar(e). Veröffentlicht von Christian Ullenboom am Sonntag, Mai 17, 2009.Die Geocoding API von Yahoo!
Für ein Beispiel wählen wir die „Yahoo! Maps Web Services - Geocoding API“. Mit ihr lassen sich zu einer Adresse in den USA die Latitude (geographische Breite) und Longitude (geographische Länge) ermitteln. (Im nächsten Schritt könnte dann ein Kartendienst die Adresse mit Hilfe der Geodaten anzeigen.)
Der Online-Dienst arbeitet über den REST-Stil, das heißt, eine URL enthält die Parameter der Anfrage. Die Webseite von Yahoo! gibt in einem Beispiel für eine URL vor:
http://local.yahooapis.com/MapsService/V1/geocode?appid=YD-9G7bey8_JXxQP6rxl.fBFGgCdNjoDMACQA--&street=701+First+Ave&city=Sunnyvale&state=CA
Der Kartenserver antwortet mit einer XML-Datei (zum Verdeutlichung hübsch formatiert):
<?xml version="1.0"?>
<ResultSet xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="urn:yahoo:maps"
xsi:schemaLocation="urn:yahoo:maps
http://api.local.yahoo.com/MapsService/V1/GeocodeResponse.xsd">
<Result precision="address">
<Latitude>37.416397</Latitude>
<Longitude>-122.025055</Longitude>
<Address>701 1st Ave</Address>
<City>Sunnyvale</City>
<State>CA</State>
<Zip>94089-1019</Zip>
<Country>US</Country>
</Result>
</ResultSet>
Für unser Beispiel wollen wir das XML-Dokument, das der Yahoo! Maps Web Service liefert, nicht von Hand auseinanderpflücken, sondern JAXB soll uns eine gefüllte JavaBean mit allen Informationen liefern.
xjc aufrufen
Im ersten Schritt wechseln wir auf die Kommandozeile und testen, ob entweder das bin-Verzeichnis vom JDK im Suchpfad ist, oder wir wechseln direkt in das bin-Verzeichnis, sodass wir xjc direkt aufrufen können, und folgende Ausgabe erscheint.
$ xjc
grammar is not specified
Usage: xjc [-options ...] <schema file/URL/dir/jar> ... [-b <bindinfo>] ...
If dir is specified, all schema files in it will be compiled.
If jar is specified, /META-INF/sun-jaxb.episode binding file will be compiled.
Options:
-nv : do not perform strict validation of the input schema(s)
-extension : allow vendor extensions - do not strictly follow the
Compatibility Rules and App E.2 from the JAXB Spec
-b <file/dir> : specify external bindings files (each <file> must have its own -b)
If a directory is given, **/*.xjb is searched
-d <dir> : generated files will go into this directory
-p <pkg> : specifies the target package
-httpproxy <proxy> : set HTTP/HTTPS proxy. Format is [user[:password]@]proxyHost:proxyPort
-httpproxyfile <f> : Works like -httpproxy but takes the argument in a file to protect password
-classpath <arg> : specify where to find user class files
-catalog <file> : specify catalog files to resolve external entity references
support TR9401, XCatalog, and OASIS XML Catalog format.
-readOnly : generated files will be in read-only mode
-npa : suppress generation of package level annotations (**/package-info.java)
-no-header : suppress generation of a file header with timestamp
-target 2.0 : behave like XJC 2.0 and generate code that doesnt use any 2.1 features.
-xmlschema : treat input as W3C XML Schema (default)
-relaxng : treat input as RELAX NG (experimental,unsupported)
-relaxng-compact : treat input as RELAX NG compact syntax (experimental,unsupported)
-dtd : treat input as XML DTD (experimental,unsupported)
-wsdl : treat input as WSDL and compile schemas inside it (experimental,unsupported)
-verbose : be extra verbose
-quiet : suppress compiler output
-help : display this help message
-version : display version information
Extensions:
-Xlocator : enable source location support for generated code
-Xsync-methods : generate accessor methods with the 'synchronized' keyword
-mark-generated : mark the generated code as @javax.annotation.Generated
-episode <FILE> : generate the episode file for separate compilation
Eigentlich ist bis auf die Angabe der Schema-Quelle (aus einer Datei oder die URL) keine weitere Angabe nötig. Es ist aber praktisch, zwei Optionen zu setzen: -p bestimmt das Java-Paket für die generierten Klassen und -d das Ausgabeverzeichnis, wo der Generator die erzeugten Dateien ablegt. Yahoo! bietet die Schema-Datei unter http://local.yahooapis.com/MapsService/V1/GeocodeResponse.xsd an, die sich direkt bei xjc angeben lässt:
$ xjc -d "c:/" -p com.tutego.insel.xml.jaxb.yahoo.geocoding http://local.yahooapis.com/MapsService/V1/GeocodeResponse.xsd
parsing a schema...
compiling a schema...
com\tutego\insel\xml\jaxb\yahoo\geocoding\ObjectFactory.java
com\tutego\insel\xml\jaxb\yahoo\geocoding\ResultSet.java
com\tutego\insel\xml\jaxb\yahoo\geocoding\ResultType.java
com\tutego\insel\xml\jaxb\yahoo\geocoding\package-info.java
Das Tool generiert die Klasse ResultSet und ResultType für den komplexen Typ aus dem XML Schema, sowie package-info.java, um eine Paket-Annotation festmachen zu können und ObjectFactory, die zwei einfache Fabrikfunktionen enthält, um ein ResultSet- und ResultType-Objetk aufbauen zu können.
Die von xjc nach c:/ geschriebenen Java-Klassen müssen nun in das Java-Projekt geschoben werden. Dann kann ein Java-Programm den Service mit einer URL ansprechen, einen Unmarshaller aufbauen und sich das Ergebnis-XML in eine JavaBean konvertieren lassen.
JAXBContext context = JAXBContext.newInstance( ObjectFactory.class );
Unmarshaller unmarshaller = context.createUnmarshaller();
// http://developer.yahoo.com/maps/rest/V1/geocode.html
String url = "http://local.yahooapis.com/MapsService/V1/geocode?" +
"appid=YD-9G7bey8_JXxQP6rxl.fBFGgCdNjoDMACQA--&" +
"street=701+First+Ave&city=Sunnyvale&state=CA";
ResultSet results = (ResultSet) unmarshaller.unmarshal( new URL(url) );
ResultType result = results.getResult().get( 0 );
System.out.printf( "Longitude = %s, Latitude= %s%n", result.getLongitude(), result.getLatitude() );
Die Bildschirmausgabe zeigt die geographische Länge und Breite der Yahoo! Konzernzentrale:
Longitude = -122.025055, Latitude= 37.416397
Konflikte in der Schema-Datei
Der Yahoo! Service ist zwar nett, aber gerne hätte ich andere OX-Mapping von anderen XML-Dokumenten gezeigt.[2] Leider haben viele XML Schemata ein Problem, sodass sie nicht direkt vom Schema-Compiler verarbeitet werden können. Ein Beispiel zeigt das Dilemma.
<container>
<head><content title="Titel"/></head>
<body><content doc="doc.txt"/></body>
</container>
In der hierarchischen Struktur heißt das in <head> und <body> vorkommende XML-Element gleich, nämlich content. Die Schema-Datei kann widerspruchslos definieren, dass die beiden XML-Elemente gleich heißen, aber unterschiedliche Attribute erlauben, sozusagen dass Head-Content und das Body-Content. Allein durch ihre Hierarchie, also dadurch, dass sie einmal unter head, und einmal unter body liegen, sind sie eindeutig bestimmt. Der Schema-Compiler von Java bekommt aber Probleme, da er diese hierarchische Information in eine flache bringt. Er kann einfach eine Klasse Head und Body aufbauen, aber bei <content> steht er vor einem Problem. Da die Schema-Definitionen unterschiedlich sind, müssten zwei verschiedene Java-Klassen unter dem gleichen Namen Content generiert werden. Das geht nicht, und xjc und bringt mit Fehlern ab.
Fehler diese Art gibt es leider häufig, und sind der Grund, warum aus vielen Schemata nicht einfach JavaBeans generiert werden können. Erfolglos ohne weitere Einstellungen sind beispielsweise DocBook, Office Open XML, SVG, MathML und weitere. Doch wie sieht die Lösung aus? jxc sieht Konfigurationsdateien vor, die das Mapping anpassen können. In diesen Mapping-Dokumenten identifiziert ein XPath-Ausdruck die problematische Stelle und gibt einen Substitutionstyp an. Die Spezifikation unter https://jaxb.dev.java.net/spec-download.html weist Interessierte in die richtige Richtung.
JAXB Plugins
Auf der Webseite https://jaxb2-commons.dev.java.net/ gibt es eine Reihe nützlicher zusätzlicher Plugins für JAXB. Darunter:
· Camelcase Always Plugin. Sind die Elementnamen großgeschrieben, so wird JAXB automatisch großgeschriebene Properties umsetzen, sodass etwa aus NAME der Setter/Getter setNAME() und getNAME() entsteht. Das Plugin verhindert dies und nennt die Setter/Getter wie gewohnt setName() und getName().
· Value-Constructor Plugin. Jede JavaBean bekommt von xjc nur einen Standard-Konstruktur. Dieses Plugin gibt einen weiteren Konstruktor hinzu, der alle Attribute direkt initialisiert.
· Default Value Plugin. Ein XML Schema kann mit defaultValue vordefinierte Initialbelegungen für Attribute angeben. xjc ignoriert diese. Das Plugin wertet diese Vorbelegungen aus und initialisiert die Attribute der JavaBean gemäß den Werten.
· Property Change Listener Injector Plugin. Eine über xjc generierte JavaBean schreibt ein bei setXXX() übergebenen Wert direkt in das private Attribut durch. Mit dem Plugin wird ein VetoableChangeListener eingeführt, der gegen Wertänderungen votieren kann.
[1] Für Eclipse gibt es dazu das Zusatz-Plugin https://jaxb-workshop.dev.java.net/plugins/eclipse/xjc-plugin.html – NetBeans integriert xjc direkt.
[2] Die Google Keyhole Markup Language (KML) Version 2.1 funktioniert auch. Die Schema-Datei http://code.google.com/intl/de/apis/kml/schema/kml21.xsd macht keine Probleme. Sei dem KML aber beim Open Geospatial Consortium liegt, gibt es für der Version 2.2 und dem Schema http://schemas.opengis.net/kml/2.2.0/ogckml22.xsd Übersetzungsprobleme.
Labels: Insel
Neues Annotations-Beispiel in der der Insel
1 Kommentar(e). Veröffentlicht von Christian Ullenboom am Samstag, Mai 16, 2009.Das Kapitel über Annotationen in der Insel habe ich überarbeitet und ein (viel cooleres) Beispiel beschrieben.
Die in Java vordefinierten Annotationen haben eine besondere Semantik, wie @Override oder @SuppressWarnings gut zeigen. Andere Annotationen stammen aus Java-Bibiotheken, etwa für die Definition von Web-Services mit dem Annotationstyp @WebMethod oder Enterprise Komponenten. Neue Frameworks machen davon fleißig Gebrauch, und es lassen sich auch neue Annotationstypen deklarieren.
1.1.1 Annotationen zum Laden von Ressourcen
Im Folgenden wollen wir drei Annotationstypen deklarieren, die den Inhalt von Objektvariablen beschreiben. Zunächst werden die Annotationstypen selbst beschrieben und abschließend folgt eine Klasse, die die Annotationen ausliest und die Ressourcen initialisiert.
Möglich soll sein, mit @CurrentDateResource eine Objektvariable mit dem aktuellen Datum zu belegen:
@CurrentDateResource
public Date now;
Ist eine Variable mit @ListOfFilesResource annotiert, so sollen alle Dateien und Unterverzeichnisse aus einem gegebenen Verzeichnis aufgelistet und damit ein Feld initialisieren werden.
@ListOfFilesResource( "c:/" )
String[] files;
Die Annotation @UrlResource ist die komplexeste Annotation. Sie beschreibt im einfachsten Fall eine URL mit Daten von einem HTTP-Server (mit dem URL-Protokoll file:// auch vom lokalen Dateisystem), sodass eine Variable mit dem Inhalt initialisiert werden kann.
@UrlResource( "http://tutego.de/aufgaben/bond.txt" )
String testFile;
Der Annotation lassen sich noch einige Attribute (Element-Wert-Paare) übergeben, sodass etwa Leerraum entfernt wird, oder der String in Groß-/Kleinbuchstaben konvertiert wird.
@UrlResource( value = "http://tutego.de/aufgaben/bond.txt",
trim = true,
upperLowerCase = UpperLowerCase.UPPERCASE )
public String testFile;
Zu guter Letzt, lassen sich bei @UrlResource auch beliebige Konvertierer-Klassen angeben, die den Text der Ressource transformieren:
@UrlResource( value = "http://tutego.de/aufgaben/bond.txt",
converter = { RemoveNoWordCharactersConverter.class, SortConverter.class } )
public String testFile;
1.1.2 Neue Annotationen deklarieren
Ein Annotationstyp (engl. annotation type) wird so deklariert wie eine Schnittstelle, nur steht vor dem Schlüsselwort interface ein @-Zeichen.
Beginnen wir mit dem einfachsten Annotationstyp CurrentDateResource:
public @interface CurrentDateResource { } Die Ähnlichkeit von neuen Annotationstypen und Schnittstellen ist so groß, dass in der Java Language Specification die Annotationen auch im Kapitel über Schnittstellen behandelt werden. (Später erfahren wir den Grund dafür: Der Compiler übersetzt die Annotationstypen in Schnittstellen.)
Wo sich der Annotationstyp festmachen lässt, kann eingeschränkt werden. Im Standardfall kann er überall angeheftet werden. Das heißt, an beliebigen Typdeklarationen, Annotationen, Aufzählungen, Objekt-/Klassenvariablen, lokalen Variablen, Parametern, Pakete[1], Methoden oder Konstruktoren.
Damit ist erlaubt:
@CurrentDateResource public Date now;
1.1.3 Annotationen mit genau einem Attribut
Der Annotationstyp @CurrentDateResource kann mit keinem zusätzlichen Attribut versehen werden, da er in der bisherigen Schreibweise eine Markierungsannotation ist. Erlaubt sind zwar ein Paar runde Klammern hinter dem Namen und auch Kommentare, aber eben kein zusätzliches Attribut, wie es @ListOfFilesResource etwa wünscht.
@CurrentDateResource public Date now;
@CurrentDateResource() public Date now;
@CurrentDateRessource("gestern") public Date now; // Compilerfehler Damit zusätzliche Informationen für den Pfadnamen bei @ListOfFilesResource("c:/") möglich sind, werden im Annotationstyp Deklarationen für Attribute eingesetzt, deren Schreibweise an Operationen einer Java-Schnittstelle erinnert. (Aber die Operationen dürfen keinen Parameter besitzen, die Rückgabe darf nicht void sein und kein throws besitzen. Und Operationen, die so heißen wie die Methoden aus Object, sind nicht zugelassen.)
Damit ein zusätzliches Attribut den Pfadnamen annehmen kann, sieht die Deklaration vom Annotationstyp ListOfFilesResource so aus:
public @interface ListOfFilesResource
{
String value();
}
Damit haben wir den zweite Annotationstyp aus unserem Beispiel vorbereitet und gültig wäre:
@ListOfFilesResource( "c:/" )
String[] files;
Fehlt das erwartete Element, also der Pfad-String, gibt es einen Compilerfehler.
Attributtypen
Das, was so wie ein Rückgabetyp einer Methode aussieht, bestimmt den Typ des Attributs und ist im begrenzten Rahmen wählbar. Der Typ muss nicht immer nur String sein. Insgesamt erlaubt Java:
- alle primitiven Datentypen (byte, short, int, long, float, double, boolean), aber keine Wrapper
- String
- Class. Insbesondere mit der generischen Angabe ermöglicht er eine präzise Klassenangabe
- Enum-Typen
- andere Annotationen (was zu geschachtelten Annotationen führt)
- Felder von oben genannten Typen. Felder von Feldern (mehrdimensionale Felder) sind aber nicht gestattet.
Hinweis Die Attribute sind typisiert, und fehlerhafte Tyen lehnt der Compiler ab. null ist als Argument nie erlaubt. Mögliche Typkonvertierungen führt der Compiler automatisch durch. @ListOfFilesResource( "" ) String[] files; // OK @ListOfFilesResource() String[] files; // Compilerfehler @ListOfFilesResource( null ) String[] files; // Compilerfehler @ListOfFilesResource( 1 ) String[] files; // Compilerfehler @ListOfFilesResource( 'C' ) String[] files; // Compilerfehler @ListOfFilesResource( "C:" + '/' ) String[] files; // OK |
1.1.4 Element-Werte-Paare (Attribute) hinzufügen
Wenn der Annotationstyp ein Attribut mit dem Namen value deklariert, so muss keine Angabe über einen Schlüsselnamen gemacht werden. Möglich wäre das aber schon, und ein geschrieben würde das:
@ListOfFilesResource( value = "c:/" )
String[] files;
Eine Annotation lässt sich mit einer beliebigen Anzahl von Attributen deklarieren, und das Attribut muss auch nur dann value heißen, wenn der Schlüssel nicht ausdrücklich genannt werden soll – also @ListOfFilesResource("c:/") statt @ListOfFilesResource(value = "c:/"). Ist mehr als ein Attribut nötig, muss ohnehin immer der Attributname zusammen mit der Belegung genannt werden.
Wenn @ListOfFilesResource mit einem Attribut trim ausgestattet wird, sodass die gelesenen Texte automatisch vorne und hinten den Weißraum abgeschnitten bekommen, so könnte die Deklaration des Annotationstyps so aussehen:
public @interface UrlResource
{
String value();
boolean trim();
}
Und in der Anwendung:
@UrlResource( value = "http://tutego.de/aufgaben/bond.txt", trim = true )
String testFile;
1.1.5 Annotationsattribute vom Typ einer Aufzählung
Bisher haben wir als Attributtyp String und boolean eingesetzt. Attribute dürfen auch Aufzählungen sein. Wir wollen das für @UrlResource nutzen, damit wir beim Einlesen wählen können, ob der Text in Groß- oder Kleinbuchstaben konvertiert wird.
@UrlResource( value = "http://tutego.de/aufgaben/bond.txt",
upperLowerCase = UpperLowerCase.UPPERCASE )
String testFile;
Für die Konvertierungsart deklarieren wir eine zunächst eine Aufzählung und deklarieren das Attribut upperLowerCase dann genau mit dem Aufzählungstyp.
public @interface UrlResource
{
public enum UpperLowerCase { UNCHANGED, LOWERCASE, UPPERCASE }
String value();
UpperLowerCase upperLowerCase();
}
Die Aufzählung UpperLowerCase als inneren Typ zu deklarieren, ist daher interessant, dass sie ja nicht allgemein ist, sondern ausschließlich Sinn mit der Annotation @UrlResource ergibt.
1.1.6 Felder von Annotationsattributen
Von den unterschiedlichen Elementtypen dürfen eindimensionale Felder gebildet werden. Da es keine anderen Sammlungen gibt, stellt das Feld die einzige Möglichkeit dar, beliebig viele Elemente anzugeben.
Der @UrlResource sollen beliebig viele Konvertierungsfilter zugewiesen werden. Konvertierungsfilter sind Klassen, die die Schnittstelle ResourceConverter implementieren und den eingelesenen String transformieren. Dann heißt es in der Deklaration vom Annotationstyp:
public @interface UrlResource
{
String value();
Class<? extends ResourceConverter>[] converter();
}
Der interessante Teil ist natürlich Class<? extends ResourceConverter>[]. Der setzt sich wie folgt zusammen:
- Da Java es nicht erlaubt, dass beliebige Attributtypen verwendet werden, bleibt bei der Angabe der Konverter nur Class-Objekte und nicht etwa ResourceConverter[].
- Die Typangabe Class[] wäre nicht ausreichend, da Class mit einem generischen Typ präzisiert werden muss. Jetzt ist aber Class<ResourceConverter> auch noch nicht präzise, denn wir wollen ja nicht nur exakt den Typ RessourceConverter treffen, sondern Untertypen, also Klassen, die RessourceConverter erweitern. Damit sind wir bei Class<? extends ResourceConverter>.
- Da es eine Liste von Class-Angaben werden kann, muss das Paar eckiger Klammen an die Deklaration.
Weisen wir zum Beispiel zwei Konverter – die Klassen wurde noch nicht vorgestellt, aber das folgt – der @UrlResource zu:
@UrlResource( value = "http://tutego.de/aufgaben/bond.txt",
converter = { RemoveNoWordCharactersConverter.class, SortConverter.class } )
public String testFile;
Bei nur einem angegeben Konverter können die geschweiften Klammern sogar entfallen:
@UrlResource( value = "http://tutego.de/aufgaben/bond.txt",
converter = RemoveNoWordCharactersConverter.class )
Hinweis Da ein Attribut wieder eine Annotation sein kann, ergeben sich interessante Möglichkeiten. Neben wir an, der Annotationstyp Name speichert Vor- und Nachname: @interface Name Ein Annotationstyp Author soll Name als Elementtyp für value nutzen: @interface Author Vor Name steht nicht das @-Zeichen. Nur in der Anwendung: @Author( @Name( firstname = "Christian", lastname = "Ullenboom" ) ) Hätten wir das Element nicht value, sondern etwa name genannt, müsste die Angabe so heißen: name = @Name( firstname = "Christian", lastname = "Ullenboom" ) Und hätten wir mehrere Autoren angegeben, würden wir Folgendes schreiben: @Author( @Name( firstname = "Hansi", lastname = "Hinterweltler" ) } ) |
1.1.7 Vorbelegte Attribute
Im bisherigen Fall mussten alle Attributbelegungen angegeben werden, und wir konnten kein Element-Werte-Paar auslassen. Die Annotationstypen ermöglichen allerdings für Attribute Standardwerte, sodass ein Wert angeben werden kann, aber nicht muss. Statt
@UrlResource( value = "http://tutego.de/aufgaben/bond.txt", trim = false )
soll möglich sein, trim = false wegzulasssen, weil es Standard sein soll.
@UrlResource( value = "http://tutego.de/aufgaben/bond.txt" )
Beziehungsweise dann wieder kürzer:
@UrlResource( "http://tutego.de/aufgaben/bond.txt" )
In der Syntax für Vorbelegungen hält dafür das Schlüsselwort default her, was auch zu einer neuen Schreibweise führt, die von den Schnittstellen abweicht.
Bei unserem @UrlResource ist nur die Angabe der Textquelle von Nöten; alles andere soll mit Default-Werten belegt sein:
public @interface UrlResource
{
enum UpperLowerCase { UNCHANGED, LOWERCASE, UPPERCASE }String value();
boolean trim() default false;
UpperLowerCase upperLowerCase() default UpperLowerCase.UNCHANGED;
Class<? extends ResourceConverter>[] converter() default { };}
Nachträgliche Änderung und die Sinnhaftigkeit von Standardwerten
Standardwerte sind für Annotationen ein sehr wichtiges Instrument, denn wenn einmal ein Annotationstyp deklariert wurde, ist eine Änderung nicht immer möglich; das Phänomen ist von Schnittstellen hinlänglich bekannt. Neben dem Hinzufügen neuer Elemente stellt bei Schnittstellen das Löschen von Operationen kein Problem dar[2] – ganz im Unterschied zu Annotationen: Werden Elemente entfernt, gibt es Compilerfehler. Auch das Ändern von Elementtypen führt im Allgemeinen zu Compilerfehlern.
Werden neue Elemente in bestehende Annotationstypen eingefügt, dann müssten alle existierenden konkreten Annotationen das neue Element setzen, was eine sehr große Änderung ist, vergleichbar einem neuen Element in einer Schnittstelle. Anders als bei Schnittstellen lösen Default-Werte das Problem, da auf diese Weise für das neue Element immer gleich ein Wert vorhanden ist, der, sofern erwünscht, neu belegt werden kann. Ohne Probleme ist es möglich, einen Default-Wert hinzuzunehmen, während das Entfernen von Standardwerten kritisch ist.
1.1.8 Annotieren von Annotationstypen
Von den in Java 5 eingeführten Annotationen haben wir die drei Typen aus dem Paket java.lang schon kennengelernt. Die restlichen vier Annotationen aus dem Paket java.lang.annotation dienen dazu, Annotationstypen zu annotieren. In diesem Fall wird von Meta-Annotationen gesprochen.
Tabelle 1: Meta-Annotationen
@Target | Was lässt sich annotieren? Klasse, Methode …? |
@Retention | Wo ist die Annotation sichtbar? Nur für den Compiler oder auch für die Laufzeitumgebung? |
@Documented | Zeigt den Wunsch an, die Annotation in der Dokumentation zu erwähnen. |
@Inherited | Macht deutlich, dass ein annotiertes Element auch in der Unterklasse annotiert ist. |
@Target
Die Annotation @Target beschreibt, wo eine Annotation angeheftet werden kann. Ist kein ausdrückliches @Target gewählt, gilt es für alle Elemente.
Die Aufzählung java.lang.annotation.ElementType deklariert folgenden Ziele:
- ANNOTATION_TYPE
- CONSTRUCTOR
- FIELD
- LOCAL_VARIABLE
- METHOD
- PACKAGE
- PARAMETER
- TYPE
Soll eine Annotation etwa vor beliebigen Typen, Methoden, Paketen und Konstruktoren erlaubt sein, so setzen wir an die Deklaration der Annotation:
@Target( { TYPE, METHOD, CONSTRUCTOR, PACKAGE } )
public @interface … Für unsere drei Annotationstypen sind nur für Attribute sinnvoll. So nutzen wir FIELD, gezeigt an CurrentDateResource:
@Target( java.lang.annotation.ElementType.FIELD )
public @interface CurrentDateResource { }
Hinweis Soll statt ElementType.FIELD einfach nur FIELD verwendet werden, so muss FIELD entsprechend aus ElementType statisch eingebunden werden. Damit ist folgender Programmcode eine Alternative: import static java.lang.annotation.ElementType.*; import java.lang.annotation.Target; @Target( FIELD ) public @interface CurrentDateResource { } |
Mit ElementType.TYPE ist die Annotation vor allen Typen – Klassen, Schnittstellen, Annotationen, Enums – erlaubt. Eine Einschränkung, etwa nur auf Klassen, ist nicht möglich. Interessant ist die Tatsache, dass eine Unterteilung für Methoden und Konstruktoren möglich ist und sogar lokale Variablen annotiert werden können.
Beispiel Beim existierenden Annotationstyp @Overrides ist die Annotation @Target schön zu erkennen. @Target( value = METHOD ) Die Idee der Meta-Annotation: Es gibt nur überschriebene Methoden. |
@Retention
Die Annotation @Retention steuert, wer die Annotation sehen kann. Es gibt drei Typen, die in der Aufzählung java.lang.annotation.RetentionPolicy genannt sind:
- SOURCE. Vom Compiler verworfen. Nützlich für Tools.
- CLASS. Abgebildet in der Klassendatei, aber von der JVM ignoriert.
- RUNTIME. Annotation wird gespeichert und ist für die JVM verfügbar.
Die Unterscheidung haben die Java-Designer vorgesehen, da nicht automatisch jede Annotation zur Laufzeit verfügbar ist. (Eine Begründung: Andernfalls würde es den Ressourcenverbrauch erhöhen.) Der Standard ist RetentionPolicy.CLASS.
Beispiel Der Annotationstyp @Deprecated ist nur für den Compiler und nicht für die Laufzeit von Interesse: @Retention( value = SOURCE ) Ist ein Element mit @Target annotiertet, so soll diese Information auch zur Laufzeit vorliegen: @Retention( value = RUNTIME ) Das Beispiel zeigt, dass die Anwendung auch rekursiv sein kann (natürlich auch indirekt rekursiv, denn nicht nur @Retention annotiert @Target, auch @Target annotiert @Retention). |
@Documented
Die Annotation @Documented zeigt an, dass die Annotation in der API-Dokumentation genannt werden soll. Alle Standard-Annotationen von Java werden so angezeigt, auch @Documented selbst. In der API-Dokumentation ist für die Annotationen ein neues Segment vorgesehen.
Beispiel @Documented ist selbst @Documented: @Documented |
1.1.9 Deklarationen für die Ressourcen-Annotationen
Da unsere drei Annotationen zur Laufzeit ausgelesen werden sollen, muss die @Retention mit RetentionPolicy.RUNTIME gesetzt sein. Damit sind unsere Annotationstypen vollständig und der Quellcode soll an dieser Stelle aufgeführt werden.
Der einfachste Annotationstyp war CurrentDateResource:
@Documented
@Target( ElementType.FIELD )
@Retention( RetentionPolicy.RUNTIME )
public @interface CurrentDateResource { }
Der Annotationstyp ListOfFilesResource erwartet eine Pfadangabe, ist aber nicht deutlich komplexer als CurrentDateResource:
@Documented
@Target( ElementType.FIELD )
@Retention( RetentionPolicy.RUNTIME )
public @interface ListOfFilesResource
{
String value();
}
Und zu gute Letzt: Der Annotationstyp UrlResource hat am meisten zu bieten. Doch beginnen wir zunächst mit der Deklaration der Schnittstelle für die Konverter:
public interface ResourceConverter
{
String convert( String input );
}
Zwei Implementierungen sollen für das Beispiel genügen:
public class RemoveNoWordCharactersConverter implements ResourceConverter
{
@Override public String convert( String input )
{
return input.replaceAll( "\\W", "" );
}
}
public class SortConverter implements ResourceConverter
{
@Override public String convert( String input )
{
char[] chars = input.toCharArray();
Arrays.sort( chars );
return new String( chars );
}
}
Damit kann dann der letzte Annotationstyp übersetzt werden:
@Documented
@Target( ElementType.FIELD )
@Retention( RetentionPolicy.RUNTIME )
public @interface UrlResource
{
enum UpperLowerCase { UNCHANGED, LOWERCASE, UPPERCASE }
String value();
boolean trim() default false;
UpperLowerCase upperLowerCase() default UpperLowerCase.UNCHANGED;
Class<? extends ResourceConverter>[] converter() default { };
}1.1.10 Annotierte Elemente auslesen
Ob eine Klasse annotiert ist, erfragt ganz einfach die Methode isAnnotationPresent() auf dem Class-Objekt:
println( String.class.isAnnotationPresent( Deprecated.class ) ); // false
println( StringBufferInputStream.class.isAnnotationPresent( Deprecated.class ) ); // true
Da unterschiedliche Dinge annotierbar sind, schreibt eine Schnittstelle AnnotatedElement für die Klassen Class, Constructor, Field, Method, Package und AccessibleObject folgende Operationen vor:
interface java.lang.reflect.AnnotatedElement |
- <T extends Annotation> T getAnnotation( Class<T> annotationType ) Liefert die Annotation für einen bestimmten Typ. Ist sie nicht vorhanden, dann ist die Rückgabe null. Der generische Typ ist bei der Rückgabe hilfreich. Denn das Argument ist ein Class-Objekt, das den Annotationstyp repräsentiert. Die Rückgabe ist genau die konkrete Annotation für das annotierte Element.
- boolean isAnnotationPresent( Class<? extends Annotation> annotationType ) Gibt es die angegebene Annotation?
- Annotation[] getAnnotations() Liefert die an dem Element festgemachten Annotationen. Gibt es keine Annotation, ist das Feld leer. Die Methode liefert auch Annotationen, die aus den Oberklassen kommen.
- Annotation[] getDeclaredAnnotations() Liefert die Annotationen, die exakt an diesem Element festgemacht sind.
Um die Annotationen etwa von Variablen oder Methoden zu erfragen, ist ein wenig Reflection-Wissen nötig. Ist obj ein Objekt, so findet folgende Schleife alle mit CurrentDateResource annotierten Objektvariablen und gibt eine Meldung aus:
for ( Field field : obj.getFields() )
if ( field.isAnnotationPresent( CurrentDateResource.class ) )
System.out.println( "CurrentDateResource gesetzt" );
1.1.11 Auf die Annotationsattribute zugreifen
Um auf die einzelnen Attribute einer Annotation zuzugreifen, müssen wir etwas mehr über die Umsetzung einer Annotation von Compiler und der JVM wissen. Übersetzt der Compiler einen Annotationstyp, generiert er daraus eine Schnittstelle.
Beispiel Für den Annotationstyp ListOfFilesResource generiert der Compiler: import java.lang.annotation.Annotation; public interface ListOfFilesResource extends Annotation |
Rufen wir auf einem AnnotatedElement, etwa Field, eine Methode wie getAnnotation() auf, bekommen wir ein Objekt, das Zugriff auf unsere Element-Werte-Paare liefert. Denn zur Laufzeit werden über java.lang.reflect.Proxy Objekte gebaut, die unsere Schnittstelle – das ist ListOfFilesResource – implementiert und so die Methode value() anbietet.
Hinweis Die Annotation ist zur Laufzeit ein Proxy-Objekt und daher kann der Annotationstyp keine eigene Klasse erweitern und auch keine anderen eigenen Schnittstellen implementieren. Eine Annotationstyp kann auch keine anderen Annotationstyp erweitern. Es könnte eine eigene Klasse zwar die Schnittstelle java.lang.annotation.Annotation implementieren, doch entsteht dadurch keine echte Annotation, was den Versuch sinnlos macht. |
Testen wir die Möglichkeit, in dem wir zwei annotierte Variablen in eine Klasse setzen und dann per Reflection über alle Variablen laufen und dann alle Annotationen erfragen.
public class GetTheUrlResourceValues
{
@UrlResource( value = "http://tutego.de/aufgaben/bond.txt",
upperLowerCase = UpperLowerCase.UPPERCASE, trim = true,
converter = { RemoveNoWordCharactersConverter.class, SortConverter.class } )
public String testFile;
@XmlValue @Deprecated
public String xmlValue;
public static void main( String[] args ) throws Exception
{
for ( Field field : GetTheUrlResourceValues.class.getFields() )
for ( Annotation a : field.getAnnotations() )
System.out.println( a );
}
}
Die Ausgabe zeigt drei Annotationen:
@com.tutego.insel.annotation.UrlResource(converter=[class com.tutego.insel.annotation.RemoveNoWordCharactersConverter, class com.tutego.insel.annotation.SortConverter], trim=true, upperLowerCase=UPPERCASE, value=http://tutego.de/aufgaben/bond.txt)
@javax.xml.bind.annotation.XmlValue()
@java.lang.Deprecated()
Die Default-Werte werden zur Laufzeit gesetzt.
1.1.12 Komplettbeispiel zum Initialisieren von Ressourcen
Zusammenfassend können wir jetzt eine Klasse vorstellen, die tatsächlich die mit den Ressoucen-Annotationen Variablen mit sinnvollem Inhalt füllt. Zunächst ein Beispiel, damit es die Nutzung einer solchen Klasse aufzeigt.
Die Klasse Resources bildet den Rahmen für Objekte, die automatisch aufgebaut und korrekt initialisiert werden sollen.
class Resources
{
@CurrentDateResource()
public Date now;
@ListOfFilesResource( value = "c:/" )
public String[] files;
@UrlResource( "http://tutego.de/aufgaben/bond.txt" )
public String testFile;
}
Einer zweiten Klassen geben wir ein main() und dort die Aufforderung, ein Objekt vom Typ Resources anzulegen und zu initialisieren.
public class AnnotatedResourceExample
{public static void main( String[] args )
{Resources resources = ResourceReader.getInitializedResourcesFor( Resources.class );
System.out.println( resources.now );
System.out.println( Arrays.toString( resources.files ) );
System.out.println( resources.testFile );
}
}
Kommen wir zum Herz, der Klasse ResourceReader:
package com.tutego.insel.annotation;
import java.io.File;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.Date;
import java.util.Scanner;
public class ResourceReader
{public static <T> T getInitializedResourcesFor( Class<T> ressources )
{try
{T newInstance = ressources.newInstance();
for ( Field field : ressources.getFields() )
{if ( field.isAnnotationPresent( CurrentDateResource.class ) )
field.set( newInstance, new Date() );
else if ( field.isAnnotationPresent( ListOfFilesResource.class ) )
field.set( newInstance, new File(field.getAnnotation(
ListOfFilesResource.class ).value().toString()).list() );
else if ( field.isAnnotationPresent( UrlResource.class ) )
{String url = field.getAnnotation( UrlResource.class ).value();
String content = new Scanner( new URL(url).openStream() ).useDelimiter( "\\z" ).next();
if ( field.getAnnotation( UrlResource.class ).trim() )
content = content.trim();
switch ( field.getAnnotation( UrlResource.class ).upperLowerCase() )
{case UPPERCASE: content = content.toUpperCase(); break;
case LOWERCASE: content = content.toLowerCase(); break;
default: // Nichts zu tun
}
Class<? extends ResourceConverter>[] converterClasses =
field.getAnnotation( UrlResource.class ).converter();
for ( Class< ? extends ResourceConverter> converterClass : converterClasses )
content = converterClass.newInstance().convert( content );
field.set( newInstance, content );
}
}
return newInstance;
}
catch ( Exception e )
{return null;
}
}
}
An den folgenden Anweisungen ist das Prinzip gut ablesbar.
T newInstance = ressources.newInstance();
for ( Field field : ressources.getFields() )
if ( field.isAnnotationPresent( CurrentDateResource.class ) )
field.set( newInstance, new Date() );
Zunächst wird ein neues Exemplar, ein Behälter aufgebaut. Dann läuft eine Schleife über alle Variablen. Gibt es zum Beispiel die Annotation CurrentDateResource an einer Variable, so wird ein Date-Objekt aufgebaut und mit set() die Variable mit dem Datum initialisiert.
1.1.13 Mögliche Nachteile von Annotationen
Annotationen sind eine gewaltige Neuerung und sicherlich die wichtigste seit vielen Java-Jahren. Auch wenn die Generics auf den ersten Blick bedeutsam erscheinen, sind die Annotationen ein ganz neuer Schritt in die deklarative Programmierung, wie sie Frameworks schon heute aufzeigen. Völlig problemlos sind Annotationen allerdings nicht:
- Die Annotationen sind stark mit dem Quellcode verbunden, können also auch nur dort geändert werden. Ist der Original-Quellcode nicht verfügbar, etwa weil der Auftraggeber ihn geschlossen hält, ist eine Änderung der Werte nahezu unmöglich.
- Wenn Annotationen allerdings nach der Übersetzung nicht mehr geändert werden können, stellt das bei externen Konfigurationsdateien kein Problem dar. Externe Konfigurationsdateien können ebenso den Vorteil bieten, dass die relevanten Informationen auf einen Blick erfassbar sind und sich mitunter nicht redundant auf unterschiedliche Java-Klassen verteilen.
- Klassen mit Annotationen sind invasiv und binden auch die Implementierungen an einen gewissen Typ, wie es Schnittstellen tun. Sind die Annotationstypen nicht im Klassenpfad, kommt es zu einem Compilerfehler.
- Bisher gibt es keine Vererbung von Annotationen: Ein Annotationstyp kann keinen anderen Annotationstyp erweitern.
- Die bei den Annotationen gesetzten Werte lassen sich zur Laufzeit erfragen, aber nicht modifizieren.
- Warum werden Annotationen mit @interface deklariert, einer Schreibweise, die in Java sonst völlig unbekannt ist?
Ein Problem gibt es allerdings nur bei finalen statischen Variablen (Konstanten), das bei den Default-Werten der Annotationen nicht vorkommt: Weil die Default-Werte zur Laufzeit gesetzt werden, lassen sie sich in der Deklaration vom Annotationstyp leicht ändern, und eine Neuübersetzung des Projekts kann somit unterbleiben.
[1] Dann muss eine Datei package-info.java im jeweiligen Paket stehen. Hier stehen dann die Annotationen vor der package-Deklaration, aber die Datei enthält keine Typdeklaration.
[2] Es sei denn, es wird seit Java 6 die Annotation @Override für die implementierten Methoden verwendet.
Fail-Fast Iterator und die ConcurrentModificationException
3 Kommentar(e). Veröffentlicht von Christian Ullenboom am Mittwoch, Mai 13, 2009.Beackern zwei Programmstellen gleichzeitig eine Datenstruktur, kann es zu Schwierigkeiten kommen, wenn sich die Datenstruktur strukturell verändert, also neue Elemente hinzukommen oder wegfallen. Probleme treten leicht auf, wenn ein Verweis auf die Datenstruktur im Programm an verschiedenen Stellen weitergegeben wird. Führt nun eine Stelle strukturellen Änderungen mit Methoden wie remove() oder add() durch, und die Größe der Liste verändert sich damit, kann das zu Zugriffsfehlern führen, wenn die andere Stelle von der Änderung nichts mitbekommt.
Nehmen wir an, zwei Programmteile greifen auf eine gemeinsame Liste zurück. Ein Programmteil merkt sich eine Suchstelle und will anschießend auf das gefundene Element zurückgreifen. Zwischen diesen beiden Operationen verändert jedoch ein anderes Programmteil die Liste und die Position des Elements verschiebt sich:
List<String> list = new ArrayList<String>( Arrays.asList( "Trullo", "Zippus" ) );
int posOfZippus = list.indexOf( "Zippus" );
System.out.println( list.get( posOfZippus ) ); // Zippus
list.add( 0, "Apulien" );
System.out.println( list.get( posOfZippus ) ); // Trullo
Nach dem Einfügen von “Apulien” ist posOfZippus also veraltet. Es wäre eine Hilfe, wenn get() die strukturelle Veränderung bemerken würde.
Beim Erfragen über Listen-Iteratoren ist das anders. Die Entwickler der Java-Bibliothek haben einen Entwurf gewählt, bei dem konfliktträchtigen Änderungen über die Listen-Iteratoren auffallen und zu einer ConcurrentModificationException führen. Das funktioniert so, dass sich die Liste die Anzahl struktureller Änderungen merkt – beim JDK von Sun intern in der Variablen modCount.
List<String> list = new ArrayList<String>( Arrays.asList( "Stunden", "der" ) );
Iterator<String> iterator = list.iterator(); // modCount = 0, expectedModCount = 0
list.get( 0 ); // Anfragen modCount nicht
list.add( "Entspannung" ); // modCount = 1, expectedModCount = 0
iterator.next(); // modCount != expectedModCount => CME
Der Iterator registriert die Änderungen, da sich der modCount in der Zwischenzeit verändert hat. Wird der Iterator initialisiert, erfragt er den modCount der Liste und speichert den Wert in expectedModCount. Jede strukturelle Änderung in der Liste führt zum Inkrement vom modCount. Wird später eine Operation über den Iterator durchgeführt, testet er, ob der gemerkte expectedModCount mit dem aktuellen modCount übereinstimmt. In unserem Fall tut er das nicht, denn add() ist eine strukturelle Änderung und modCount wird 1. Beim nachfolgendem next() kommt es zu einer ConcurrentModificationException.
Labels: Insel
Ausblick auf die Logging-Zukunft
3 Kommentar(e). Veröffentlicht von Christian Ullenboom am Dienstag, Mai 12, 2009.Immer wieder gibt es Versuche, die Logging-Bibliothek von Sun und log4j unter einen Hut zu bringen. Das kann so aussehen, dass die Client-API eine andere ist als die darunterliegende Bibliothek, die das Logging wirklich durchführt. Der populärste Versuch ist eine leichtgewichtige Bibliothek Simple Logging Facade[1] for Java (http://www.slf4j.org/), abgekürzt SLF4J. Die API fühlt sich etwa so an:
import org.slf4j.*;
public class HelloMyLogger
{
private final static Logger log = LoggerFactory.getLogger( HelloMyLogger.class );
public static void main( String[] args )
{
log.info( "Hallo {}", "Welt" );
}
}
SLF4J hat sich mittlerweile als Logging-Fassade etabliert und schiebt eine alternative API, die Apache Commons Logging (http://jakarta.apache.org/commons/logging/), abgekürzt JCL, in den Hintergrund. JCL führte in der Praxis zu gewaltigen Problemen mit Klassenladern (http://www.qos.ch/logging/thinkAgain.jsp) und wurde von einigen Kritikern als größter Irrsinn beschimpft (http://www.jroller.com/page/fate/?anchor=the_apache_syndrome). Vielleicht kein Wunder, dass die letzte JCL-Version 1.1.1 von Ende 2007 ist. SLF4J hat keine Probleme mit Klassenladern. Zudem bietet SLF4J hübsche parametrisierbare Log-Nachrichten, also das, was in den geschweiften Klammern zu sehen ist und einen Platzhalter definiert.
Während SLF4J eine Fassade ist, die kein eigenes Logging implementiert, gibt es auch Veränderungen unter der Haube. Dabei ist es nicht so, dass sich Suns Logging verändert hätte, sondern Logback (http://logback.qos.ch/) tritt als Nachfolger für log4j an. Logback implementiert auch die API von SLF4J direkt. Der Autor von log4j und Logback ist in beiden Fällen Ceki Gülcü, sodass Logback von der langen Logging-Erfahrung des Autors profitiert. Zwar hat log4j immer noch mehr Nutzer als Logback und ist besser bekannt, doch es kann gut sein, das in der nächsten Auflage der Insel nicht mehr Logging am Beispiel von log4j, sondern an Logback gezeigt wird.
[1] Das Prinzip, eine einfache API vor eine komplexe Implementierung zu setzten, ist unter dem Entwurfsmuster Fassade bekannt.
Labels: Insel
Labels: Open Source
Offtopic: Content-Klau der Schöll AG
4 Kommentar(e). Veröffentlicht von Christian Ullenboom am Montag, Mai 11, 2009.In Deutschland bezieht sich das Urheberrecht auf jedes Bild und digitale Schriftstück (E-Mail, SMS, Webseite). Sie sind durch ihren Autor automatisch geschützt. Dazu zählen selbstredend auch die tutego-Seminarbeschreibungen.
Immer wieder schlagen Schulungsunternehmen illegale Pfade ein, weil ihnen meine Seminarbeschreibungen wohl gut gefallen. An dieser Stelle sollen keine Unidozenten an den Pranger gestellt werden, die Ausschnitte meines öffentlich zugänglichen Java-Buchs als Folien oder Unterlagen veröffentlichen. Mit dieser Form des Plagiatismus habe ich mich mehr oder minder abgefunden.
Auch nicht den gelegentlichen Text-Klau oder das Übernehmen einer Kapitelunterteilung beanstande ich, sondern
- dass ohne mit der Wimper zu zucken, eins-zu-eins kopiert wird und
- mit welcher Dreistigkeit sich einige Unternehmen dem Diebstahl meiner Seminarbeschreibungen verantworten, wenn sie denn darauf angesprochen werden.
Ein glänzendes Beispiel
dafür ist die Schöll AG, ein Schulungsanbieter aus Darmstadt. Gleich zwei Seminarbeschreibungen sind meiner Feder entsprungen, nur mein Name steht nicht drunter.
| Eclipse IDE (tutego) | Softwareentwicklung mit Eclipse (Schöll) |
|
|
Die Textidentität von 89 % lässt sich mittels eines Text-Similarity-Tools oder dem Quick Diff Online Tool in Sekunden nachprüfen. Wer Lust hast, kann auch gleich noch den Verwandtschaftsgrad von meinem Seminar “Enterprise JavaBeans (EJB) 3 und Persistence API” mit Schölls “Programmierung von Businesskomponenten mit Enterprise JavaBeans” ermitteln.
Es interessiert mich dabei nicht, wie schludrig Schöll sonst mit den Beschreibungen umgeht und “IEinführung in die Solaris-Verzeichnishierarchie” schreibt oder im Seminar “XML Einführung” von SOM und X-Path spricht (vermutlich Schöll-Object-Modell, XML Schema Object Model (SOM) passt hier nicht). Wer Lust hast, die Urheber der restlichen Java-, Solaris- und Oracle-Seminarbeschreibungen herauszufinden, wird bei Sun und Oracle (zum Teil unter anderem Titel) fündig.
Was tun bei Content-Klau?
Bevor ich einem Rechtsanwalt die Möglichkeit gebe, sich für eine hübsche Abmahnung ein paar Euros dazu zu verdienen, zog ich den kurzen Dienstweg vor. Also Anruf bei Schöll. Freundlich wies ich auf das Problem hin und bat um unmittelbare Entfernung meiner Inhalte und ebenso um eine Unterlassungserklärung. Hier das Protokoll der Telefonate:
- 16. April 2009 Telefonat mit dem Office. Bitte um Rückruf. Keine Reaktion.
- 21. April 2009 Telefonat mit dem Office. Bitte um Rückruf. Keine Reaktion.
- 28. April 2009 Telefonat mit dem Office, Weiterleitung zu Herr Schöll Senior. Zitat: “Macht mein Sohn”. Keine weitere Reaktion.
- 5. Mai 2009 Telefonat mit dem Office. Bitte um Rückruf. Keine Reaktion.
Bis heute warte ich auf einen Rückruf.
Liebe Rechtsanwälte, wer sich durch Abmahnung, Unterlassungserklärung, Schadensersatzanspruch etwas dazuverdienen will, ist herzlich eingeladen, sich diesem Fall anzunehmen.
Release der SpringSource Tool Suite (STS) 2.0
0 Kommentar(e). Veröffentlicht von Christian Ullenboom am Montag, Mai 11, 2009.Unter http://www.springsource.com/products/sts hat SpringSource die “SpringSource Tool Suite (STS)” veröffentlicht. Das Eclipse-Plugin greift den Entwicklern mit Tools unter den Arm, etwa bei:
Building Spring-powered applications is made easier when using the Project Creation Wizard, rich forms-based Spring Configuration Editor, Quick Fixes and Quick Assist, Bean Creation Wizard, Namespace Configuration Dialog, and many other visual tools.
For newcomers to developing Spring-powered applications or experienced developers utilizing new features of Spring, STS leverages its task-based user interface to provide tool-guided assistance through tutorials and SpringSource training materials. Developers are walked through the process of building complete applications at all stages of the development and deployment lifecycle
Weitere Neuigkeiten sind in http://www.springsource.com/files/2009-05_STS_v2.1_New_and_Noteworthy.pdf zusammengefasst. Die Quick-Fixes und Quick-Assists sind eine große Hilfe. Die Ausrichtung auf OSGi spiegeln die Tools voll wieder.
Labels: Eclipse, Entwicklungsumgebung, Spring
Thema der Woche: Interface Design
0 Kommentar(e). Veröffentlicht von Christian Ullenboom am Montag, Mai 11, 2009.Ließ von http://www.artima.com/interfacedesign/contents.html die öffentlich zugänglichen Kapitel.
Labels: Die wöchentliche Dosis Java
Nimbus Swing Look and Feel wird auch Teil von Java 7 sein
1 Kommentar(e). Veröffentlicht von Christian Ullenboom am Samstag, Mai 09, 2009.Von Java 6 wurde das Nimbus LaF auch auf das OpenJDK 7 gebracht. Es wird dann unter javax.swing.plaf liegen.
Labels: Java 7
Transparente und nicht-rechteckige Fenster
0 Kommentar(e). Veröffentlicht von Christian Ullenboom am Samstag, Mai 09, 2009.Kirill Grouchnikov gibt in seinem Blog-Eintrag http://www.pushing-pixels.org/?p=1209 den Hinweis, dass in JDK 7 (b57) transparente und nicht-rechteckige Fenster unterstützt werden. Die Sun-interne Klasse com.sun.awt.AWTUtilities wird nun vom Entwickler nicht mehr benötigt, und die Funktionalität ist in die Windows-Klasse gewandert; sie delegiert aber weiterhin an AWTUtilities.
Labels: Java 7
Google Collections 1.0 Release Candidate 1
0 Kommentar(e). Veröffentlicht von Christian Ullenboom am Samstag, Mai 09, 2009.Die Google Collections Library (http://code.google.com/p/google-collections/) Version 1.0 ist nicht mehr fern. Ab jetzt sollte sich die API nicht mehr verändern. (Im Interview sagten die Autoren, sie müssten dann von Hand jede Änderung in allen Quellen bei Google ändern, die die Google Collections einsetzen.)
Interessante Klassen aus Google Collection sind:
- http://google-collections.googlecode.com/svn/trunk/javadoc/index.html?com/google/common/collect/MapMaker.html
- http://google-collections.googlecode.com/svn/trunk/javadoc/index.html?com/google/common/collect/ImmutableSet.html
- http://google-collections.googlecode.com/svn/trunk/javadoc/index.html?com/google/common/collect/Ordering.html
- http://google-collections.googlecode.com/svn/trunk/javadoc/com/google/common/collect/HashMultimap.html
- http://google-collections.googlecode.com/svn/trunk/javadoc/com/google/common/base/Joiner.html
Bemerkenswert sind auch neue Annotationen wie http://google-collections.googlecode.com/svn/trunk/javadoc/com/google/common/annotations/GwtCompatible.html.
Labels: Open Source
gwtrpc-spring zur Einfachen Integration von Spring in GWT 1.6
0 Kommentar(e). Veröffentlicht von Christian Ullenboom am Samstag, Mai 09, 2009.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.Labels: GWT, Open Source, Web Frameworks
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.
Labels: Open Source
JavaNCSS (Source Measurement Suite) in der Version 30.51
1 Kommentar(e). Veröffentlicht von Christian Ullenboom am Mittwoch, Mai 06, 2009.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.
Labels: Open Source
Eclipse wants me to “change to ‘pussy’”
1 Kommentar(e). Veröffentlicht von Christian Ullenboom am Mittwoch, Mai 06, 2009.Labels: Eclipse, Entwicklungsumgebung
gwt-connectors – Verbindungen zwischen Formen
0 Kommentar(e). Veröffentlicht von Christian Ullenboom am Mittwoch, Mai 06, 2009.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:

Labels: GWT, Open Source, Web Frameworks
GWT Charting Bibliothek gflot und charts4j.
0 Kommentar(e). Veröffentlicht von Christian Ullenboom am Dienstag, Mai 05, 2009.Alles fängt mit http://jquery.com/ an. Darauf baut auf http://code.google.com/p/flot/, eine JavaScript-Bib. für Chars:
- Basic example
- Different graph types and setting various options
- Turning series on/off
- Selection support and zooming and zooming with overview
- Plotting time series and visitors per day with zooming and weekends
- Dual axis support
- Interacting with the data
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 °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.
Labels: GWT, Open Source, Web Frameworks
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.
Labels: Open Source
Derby Datenbank endlich auch pur In-Memory
5 Kommentar(e). Veröffentlicht von Christian Ullenboom am Dienstag, Mai 05, 2009.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.
Labels: Open Source
Thema der Woche: Micro-Benchmarks
0 Kommentar(e). Veröffentlicht von Christian Ullenboom am Montag, Mai 04, 2009.Ließ zunächst die folgenden Artikel
- http://wikis.sun.com/display/HotSpotInternals/MicroBenchmarks
- http://www.ibm.com/developerworks/java/library/j-jtp02225.html
- http://www.azulsystems.com/events/javaone_2002/microbenchmarks.pdf
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.
Labels: Die wöchentliche Dosis Java
RSS Feed Adresse ändert sich…
0 Kommentar(e). Veröffentlicht von Christian Ullenboom am Sonntag, Mai 03, 2009.Eclipse 3.5M7 ist da
0 Kommentar(e). Veröffentlicht von Christian Ullenboom am Sonntag, Mai 03, 2009.
Und ein toString()-Generator

Wer mit lokalisiertem Java-Code arbeitet wir sich freuen, dass man nun von der String-ID in das Property-File springen und die Übersetzung angehen kann.
Labels: Eclipse, Entwicklungsumgebung

