Das Ende vom Java-Plugin für Web-Browser

Oracle hat angekündigt, dass das Java-Plugin für Web-Browser mit dem Erscheinen von Java 9 im nächsten Jahr nicht mehr weiterentwickelt wird.

Nachdem Mozilla Firefox und auch Google Chrome ankündigten, das Java-Plugin nicht mehr weiter unterstützen zu wollen, sind das offenbar u. a. Gründe für den Abschied vom Browser-Plugin.

Der Anteil von Java-Applets ist ohnehin kaum noch „messbar“, spätestens mit dem Java 7 Update 51, mit dem die Sicherheitsmechanismen für Java-Applets noch einmal deutlich überarbeitet wurden, sind Java-Applets in „freier Wildbahn“ kaum noch zu finden.

Oracle empfiehlt als Ersatz Java WebStart, das ohne das Plugin auskommt.

Apache POI 3.14-beta1 ist erschienen

Was es für Änderungen gibt? https://poi.apache.org/changes.html

The Apache POI team is pleased to announce the release of 3.14-beta1. Featured are a handful of new areas of functionality – including basic support for the XML Visio format – and numerous bug fixes.

A summary of changes is available in the Release Notes. A full list of changes is available in the change log. People interested should also follow the dev list to track progress.

The POI source release as well as the pre-built binary deployment packages are listed below. Pre-built versions of all POI components are available in the central Maven repository under Group ID „org.apache.poi“ and Version „3.14-beta1“

Alles Gute …

… zum Weihnachtsfest und einen guten Rutsch ins neue Jahr.

          *             ,
                       _/^\_
                      <     >
     *                 /.-.\         *
              *        `/&\`                   *
                      ,@.*;@,
                     /_o.I %_\    *
        *           (`'--:o(_@;
                   /`;--.,__ `')             *
                  ;@`o % O,*`'`&\ 
            *    (`'--)_@ ;o %'()\      *
                 /`;--._`''--._O'@;
                /&*,()~o`;-.,_ `""`)
     *          /`,@ ;+& () o*`;-';\
               (`""--.,_0 +% @' &()\
               /-.,_    ``''--....-'`)  *
          *    /@%;o`:;'--,.__   __.'\
              ;*,&(); @ % &^;~`"`o;@();         *
              /(); o^~; & ().o@*&`;&%O\
        jgs   `"="==""==,,,.,="=="==="`
           __.----.(\-''#####---...___...-----._
         '`         \)_`"""""`
                 .--' ')
               o(  )_-\
                 `"""` `

Setzen des Java Klassenpfades

Wo die JVM die Klassen findet muss ihr mitgeteilt werden, und das ist in der Praxis elementar für die Auslieferung, auch englisch Deloyment genannt. Java wartet mit dem Laden der Klassen so lange, bis sie benötigt werden. Es gibt zum Beispiel Programmabläufe nur zu besonderen Bedingungen und wenn dann erst spät ein neuer Typ referenziert wird, der nicht vorhanden ist, fällt dieser Fehler erst sehr spät auf. Der JVM müssen folglich nicht nur die Quellen für Klassen und Ressourcen der eigenen Applikation mitgeteilt werden, sondern alle vom Programm referenzierten Typen aus zum Beispiel quelloffenen und kommerziellen Bibliotheken.

Sollen in einem Java-Projekt Dateien aus einem Verzeichnis oder einem externen Java-Archiv geholt werden, so ist der übliche Weg, diese Verzeichnisse oder Archive in einem Klassenpfad anzugeben. Diese Angabe ist für alle SDK-Werkzeuge notwendig – am Häufigsten ist sie beim Compiler und bei der Laufzeitumgebung zu sehen.

Schalter -classpath

Die Suchorte lassen sich flexibel angeben, wobei die erste Variante einem SDK-Werkzeug über den Schalter -classpath (kurz -cp) die Klassendateien bzw. Archive liefert:

$ java -classpath classpath1;classpath2 MainClass

Der Klassenpfad enthält Wurzelverzeichnisse der Pakete und JAR-Dateien, also Archive von Klassendateien und Ressourcen.

Beispiel: Nimm ein Java-Archiv library.jar im aktuellen Verzeichnis, die Ressourcen unter dem bin-Verzeichnis und alle JAR-Dateien im Verzeichnis lib in den Klassenpfad mit auf:

$ java -cp "library.jar;bin/.;lib/*" com.tutego.MainClass

Unter Windows ist der Trenner ein Semikolon, unter Unix ein Doppelpunkt! Das Sternchen steht für alle JAR-Dateien, es ist kein üblicher Wildcard, wie z.B. parser*.jar.[1] Sehen Kommandozeilen der Betriebssysteme ein *, beginnen sie in der Regel eine eigenen Verarbeitung; daher muss die gesamte Pfadangabe in doppelten Anführungszeichen stehen.

Umgebungsvariable CLASSPATH

Eine Alternative zum Schalter -cp ist das Setzen der Umgebungsvariablen CLASSPATH mit einer Zeichenfolge, die Pfadangaben spezifiziert:

$ SET CLASSPATH=classpath1;classpath2
$ java MainClass

Problematisch ist der globale Charakter der Variablen, sodass lokale -cp-Angaben besser sind. Außerdem „überschreiben“ die -cp-Optionen die Einträge in CLASSPATH. Zu guter Letzt: ist weder CLASSPATH noch eine -cp-Option gesetzt, besteht der Klassenpfad für die JVM nur aus dem aktuellen Verzeichnis, also „.“.

[1]      Weitere Details unter https://docs.oracle.com/javase/8/docs/technotes/tools/windows/classpath.html.

Klassenlader (Class Loader) und Klassenpfad

Ein Klassenlader ist dafür verantwortlich, eine Klasse zu laden. Aus der Datenquelle (im Allgemeinen einer Datei) liefert der Klassenlader ein Byte-Array mit den Informationen, die im zweiten Schritt dazu verwendet werden, die Klasse ins Laufzeitsystem einzubringen; das ist Linking. Es gibt vordefinierte Klassenlader und die Möglichkeit, eigene Klassenlader zu schreiben, um etwa verschlüsselte und komprimierte .class-Dateien aus Datenbanken zu laden.

Klassenladen auf Abruf

Nehmen wir zu Beginn ein einfaches Programm mit zwei Klassen:

class Person {
   static String NOW = java.time.LocalDateTime.now().toString();
   public static void main( String[] args ) {
     Dog wuffi = new Dog();
   }
 }
 
 class Dog {
   Person master;
 }

Wenn die Laufzeitumgebung das Programm Person startet, muss sie eine Reihe von Klassen laden. Das tut sie dynamisch zur Laufzeit. Sofort wird klar, dass es zumindest Person sein muss. Wenn aber die statische main(String[])-Methode aufgerufen wird, muss auch Dog geladen sein. Und da beim Laden einer Klasse auch die statischen Variablen initialisiert werden, wird auch die Klasse LocalDateTime geladen.

Zwei weitere Dinge werden nach einiger Überlegung deutlich:

  • Wenn Dog geladen wird, bezieht es sich auf Person. Da Person aber schon geladen ist, muss es nicht noch einmal geladen werden.
  • Unsichtbar stecken noch andere referenzierte Klassen dahinter, die nicht direkt sichtbar sind. So wird zum Beispiel Object geladen, da implizit in der Klassendeklaration von Person steht: class Person extends Object. Auch String muss geladen werden, weil String einmal in der Signatur von main(String[]) vorkommt und es der Typ von now Intern ziehen die Typen viele weitere Typen nach sich. String implementiert Serializable, CharSequence und Comparable, also müssen diese drei Schnittstellen auch geladen werden. Und so geht das weiter, je nach dem, welche Programmpfade abgelaufen werden. Wichtig ist aber zu verstehen, dass diese Klassendateien so spät wie möglich geladen werden.

Im Beispiel mit den Klassen Person und Dog lädt die Laufzeitumgebung selbstständig die Klassen (implizites Klassenladen). Klassen lassen sich auch mit Class.forName(String) über ihren Namen laden (explizites Klassenladen).

Hinweis. Um zu sehen, welche Klassen überhaupt geladen werden, lässt sich der virtuellen Maschine beim Start der Laufzeitumgebung ein Schalter mitgeben -verbose:class. Dann gibt die Maschine beim Lauf alle Klassen aus, die sie lädt.

JAR-Dateien

Große Sammlungen von Java-Klassendatein und Ressourcen werden in sogenannten Java-Archiven, kurz JAR-Dateien, zusammengefasst. Diese Dateien sind im Grunde ganz normale ZIP-Archive mit einem besonderen Verzeichnis META-INF für Meta-Dateien. Das JDK bringt im bin-Verzeichnis das Werkzeug jar zum Aufbau und Extrahieren von JAR-Dateien mit.

JAR-Dateien behandelt die Laufzeitumgebung wie Verzeichnisse von Klassendateien und Ressourcen. Wenn Java-Software ausgeliefert wird, dann bieten sich JAR-Dateien an, denn es ist einfacher, nur ein komprimiertes Archiv weiterzugehen als einen großen Dateibaum. Zudem haben Java-Archive den Vorteil, dass sie signiert werden können und illegale Änderungen auffallen.

Woher die kleinen Klassen kommen: Die Suchorte und spezielle Klassenlader

Die Laufzeitumgebung nutzt zum Laden nicht nur einen Klassenlader, sondern mehrere. Die Java-Laufzeitumgebung nutzt diese verschiedenen Klassenlader um unterschiedliche Orte festzulegen. Ein festes Schema bestimmt die Suche nach den Klassen:

  1. Klassen Typen wie String, Object oder Point stehen in einem ganz speziellen Archiv. Wenn ein eigenes Java-Programm gestartet wird, so sucht die virtuelle Maschine die angeforderten Klassen zuerst in diesem Archiv. Da es elementare Klassen sind, die zum Hochfahren eines Systems gehören, werden sie Bootstrap-Klassen Das Archiv mit diesen Klassen heißt oft rt.jar (für Runtime). Andere Archive können hinzukommen – wie i18n.jar, das Internationalisierungsdaten beinhaltet. Die Implementierung dieses Bootstrap-Klassenlader ist nicht öffentlich und wird von System zu System unterschiedlich sein. Ab Java 9 wird sich das grundlegend ändern.
  2. Ist eine Klasse keine Bootstrap-Klasse, beginnt der System-Klassenlader Applikations-Klassenlader die Suche im Klassenpfad (Classpath). Diese Pfadangabe besteht aus einer Aufzählung einzelner Klassendateien, Verzeichnisse, Klassen oder JAR-Archive, in denen die Laufzeitumgebung nach den Klassendateien sucht. Standardmäßig ist dieser Klassenpfad auf das aktuelle Verzeichnis gesetzt („.“), er lässt sich aber beliebig setzen.

Kompakte Strings nun in Java 9 (JEP 254)

Wenn ein String nur aus Latin-1 Buchstaben besteht, dann kann die Zeichenfolge aus einem byte- statt einem char-Feld bestehen. Diese Änderung wurde in Java 9 nun umgesetzt: http://hg.openjdk.java.net/jdk9/jdk9/jdk/rev/4f6e52f9dc79. Gerade bei IDs lässt sich so 1/2 vom Speicher sparen. Und da Strings immutable sind, muss man nicht konvertieren.

Teurer wird es beim „Mischen“ wie:

String /*intern char[]*/ s  =  "λφα";
String /*intern char[]*/ t  =  " " /*intern byte[]*/
                                +
                                s  /*intern char[]*/;

Intern wird hier eine inflate()-Methoden aufgerufen:

If the coder is "isLatin1", this inflates the internal 8-bit storage to 16-bit <hi=0, low> pair storage.

Inselraus: Swings JLayeredPane

Die JLayeredPane nimmt JComponent-Objekte auf und stellt sie in einer geschichteten Reihenfolge (auch Z-Order genannt) dar. Die Layered Pane besteht selbst wieder aus zwei Objekten, einer Menüzeile und der Inhaltsfläche Content-Pane. Container vom Typ JLayeredPane platzieren ihre Kinder in Ebenen (engl. layers). Jedem Kind wird eine Ebene zugewiesen, und beim Zeichnen werden die Kinder von unten nach oben gezeichnet. Damit werden die Komponenten, die unter anderen Komponenten liegen, unter Umständen verdeckt. Standardmäßig hat die JLayeredPane keinen zugewiesenen Layoutmanager, und Objekte müssen mit setBounds(…) positioniert werden.

Wird ein JLayeredPane-Container verwendet, ist die add(…)-Methode so implementiert, dass sie die Komponenten auf einer Standardebene (JLayeredPane.DEFAULT_LAYER) platziert. Um Komponenten auf eine eigene Ebene zu setzen, sodass sie vor oder hinter anderen Komponenten liegen, wird ihnen eine eigene Ebene zugewiesen, und zwar mit einem Wert relativ zu DEFAULT_LAYER. Kleinere Werte bedeuten, dass die Komponenten unten liegen, und hohe bedeuten, dass sie oben liegen. Ein Beispiel:

layeredPane.add( component, new Integer(5000) );

Für einige Ebenen sind Werte als Konstanten deklariert. Dazu zählen zum Beispiel JLayered-Pane.DEFAULT_LAYER (0), PALETTE_LAYER (100), MODAL_LAYER (200), POPUP_LAYER (300) und DRAG_LAYER (400).

Neben der Möglichkeit, die Ebenen festzulegen, lässt sich die Reihenfolge innerhalb der Ebene später durch die Methode moveToFront() oder moveToBack() verändern.

Inselraus: Swings JRootPane und JDesktopPane

Unter den JComponent-Objekten gibt es einige ausgezeichnete, die als Container für andere Kinder fungieren.

Wurzelkomponente der Top-Level-Komponenten (JRootPane)

Die Komponenten JFrame, JDialog, JWindow, JApplet und JInternalFrame enthalten als einziges Kind den leichtgewichtigen Container JRootPane. Die Methode getRootPane() liefert dieses JRootPane-Objekt. Die JRootPane verwaltet eine Layered Pane, die wiederum Content-Pane und Menü aufnimmt, und eine Glass-Pane, die wie eine Glasscheibe über allen anderen Komponenten liegt. Sie kann Ereignisse abfangen oder in einer paint(…)-Methode etwas über alle Komponenten zeichnen.

Beispiel: Weise der Glass-Pane einen wartenden Cursor zu:

Component c = getRootPane().getGlassPane();
 if( c != null )
   c.setCursor( Cursor.getPredefinedCursor( Cursor.WAIT_CURSOR ) );

 JDesktopPane und die Kinder von JInternalFrame

Die JDesktopPane ist eine Unterklasse von JLayeredPane und als Container für interne Fenster – also Objekte vom Typ JInternalFrame – gedacht. Mit internen Fenstern (engl. internal frames) lassen sich MDI-Applikationen implementieren, also GUI-Anwendungen, bei denen nicht das grafische Betriebssystem die Fenster verwaltet, sondern die eigene Anwendung.

Bevor ein JInternalFrame sichtbar wird, muss der Container erzeugt und sichtbar gemacht werden:

JDesktopPane desktop = new JDesktopPane();
 container.add( desktop );

Jetzt können beliebig viele JInternalFrame-Objekte erzeugt und auf der JDesktopPane platziert werden. Der einfachste Konstruktor ist der Standard-Konstruktor, der einen nicht vergrößerbaren, nicht schließbaren, nicht maximierbaren und nicht zum Icon verkleinerbaren JInternal-Frame ohne Titel erzeugt. Der ausführlichste Konstruktor erlaubt eine genaue Parametrisierung:

JInternalFrame iframe = new JInternalFrame( title, resizable, closeable,
   maximizable, iconifiable );

Zwar gibt es nun ein Exemplar, doch wäre es nach dem Aufsetzen auf den Container noch nicht sichtbar:

iframe.setVisible( true );

Bis zur Vollständigkeit fehlen aber noch die Maße:

iframe.setSize( /* width = */ 200, /* height = */ 100 );

Nun kann der iframe dem Container hinzugefügt werden:

desktop.add( iframe );

In einem kompletten Programm kann das so aussehen:

package com.tutego.insel.ui.swing;
 
 import javax.swing.*;
 import static java.lang.Math.random;
 
 public class JInternalFrameDemo {
 
   static void addInternalToDesktop( JDesktopPane desktop )
   {
     JInternalFrame iframe;
     iframe = new JInternalFrame( "Ein internes Fenster",  // title
                                  true,                    // resizable
                                  true,                    // closeable
                                  true,                    // maximizable
                                  true );                  // iconifiable
 
     iframe.setBounds( (int)(random() * 100), (int)(random() * 100),
                       100 + (int)(random() * 400), 100 + (int)(random() * 300) );
     iframe.add( new JScrollPane(new JTextArea()) );
     iframe.setVisible( true );
 
     desktop.add( iframe );
   }
 
   public static void main( String[] args ) {
     JFrame f = new JFrame();
     f.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
     JDesktopPane desktop = new JDesktopPane();
     f.add( desktop );
     f.setSize( 500, 400 );
     addInternalToDesktop( desktop );
     addInternalToDesktop( desktop );
     addInternalToDesktop( desktop );
     f.setVisible( true );
   }
 }

Hinweis: Die Schnittstelle von JInternalFrame erinnert an JFrame, doch ist die Ereignisbehandlung anders. So besitzt JInternalFrame eine Methode addInternalFrameListener(…) an Stelle von addWindowListener(…). Ein JInternalFrame empfängt keine WindowEvents, daher darf es addWindowListener(…) – wie es JFrame von java.awt.Window erbt – auch nicht besitzen.

Inselraus: Swings Farbauswahldialog JColorChooser

Mit einem JColorChooser lassen sich Farben über drei unterschiedliche Reiter auswählen. Der Benutzer hat die Auswahl zwischen vordefinierten Farben, HSB-Werten und RGB-Werten. Um den Farbauswahldialog auf den Bildschirm zu bekommen, genügt ein Aufruf von JColorChooser.showDialog(Component, String, Color) mit drei Argumenten: einem Component-Objekt (dem Vater des Dialogs), dem Titel und einer Anfangsfarbe. Beendet der Benutzer den Dialog, wird als Rückgabewert die ausgewählte Farbe geliefert. Wird der Dialog abgebrochen, so ist der Rückgabewert null:

JFrame f = new JFrame();
 f.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
 JButton b = new JButton( "Farbe ändern" );
 f.add( b );
 b.addActionListener( new ActionListener() {
   @Override public void actionPerformed( ActionEvent e ) {
    Component comp = (Component) e.getSource();
    Color newColor = JColorChooser.showDialog( null,
                                               "Wähle neue Farbe",
                                               comp.getBackground() );
    comp.setBackground( newColor );
  }
 } );
 f.pack();
 f.setVisible( true );

Den Aufruf mit showDialog(…) einzuleiten, ist nicht der einzige Weg. Wir können auch den Konstruktor nutzen und dieses Exemplar später mit JColorChooser.createDialog(…) übergeben und anzeigen.

class javax.swing.JColorChooser
extends JComponent implements Accessible

  • JColorChooser()
    Erzeugt einen neuen Farbauswahldialog.
  • JColorChooser(Colorc)
    Erzeugt einen neuen Farbauswahldialog mit einer vordefinierten Farbe.
  • staticColorshowDialog(Componentc,Stringtitle,ColorinitialColor)
    Zeigt einen modalen Farbauswahldialog.
  • staticJDialogcreateDialog(Componentc,Stringtitle,booleanmodal,
    JColorChooser chooserPane, ActionListener okLis, ActionListener cancelLis)
    Erzeugt einen neuen Dialog aufgrund des JColorChooser-Objekts mit Standardschaltflächen zum Bestätigen und Abbrechen.

JColorChooser-Objekte als spezielle Komponenten

Neben der statischen Methode showDialog(…) lässt sich auch der Konstruktor nutzen, um ein JColorChooser als spezielles JComponent-Objekt aufzubauen. Das bringt den Vorteil mit sich, dass die Farbauswahl nicht zwingend in einem eigenständigen Dialog stattfinden muss, sondern dass im Fall einer Komponente diese zusammen mit anderen Komponenten auf einen Container gesetzt werden kann. Änderungen an der Auswahl registriert ein ChangeListener, der etwa so angewendet wird:

chooser.getSelectionModel().addChangeListener( new ChangeListener() {
   @Override public void stateChanged( ChangeEvent e ) {
     Color c = ((ColorSelectionModel) e.getSource()).getSelectedColor();
   }
 } );

Weitere Beispiele finden sich unter http://www.java2s.com/Code/Java/Swing-JFC/Color-Chooser.htm. Wie ein neuer Reiter mit über 50 Schattierungen in der Graustufenanzeige eingebracht wird, zeigt etwa http://www.java2s.com/Code/Java/Swing-JFC/JColorChooserdialogwiththecustomGrayScalePanelpickertab.htm.

Inselraus: Die Windows-Optik von Swing mit JGoodies Looks verbessern

Zwar bemüht sich das JDK bestmöglich das Windows-Look-and-Feel zu emulieren, doch das gelingt nicht an allen Stellen. Selbst bei dem aktuellen JDK ist das noch nicht perfekt, wobei die Implementierung unter Windows auf den nativen Windows-Renderer zurückgreift. Und kleine Verbesserungen lassen sich auch nicht so einfach publizieren, da die Release-Zyklen vom JDK lang sind. In diesem Fall hilft JGoodies Looks (http://www.jgoodies.com/freeware/libraries/looks/), ein freies Look-and-Feel unter der BSD-Lizenz. Das Ziel von Looks ist die perfekte Nachbildung des Aussehens für alle Windows-Versionen. Gegenüber dem Windows-Look-and-Feel vom JDK korrigiert es viele Feinheiten, wie passende Insets bei Eingabefeldern, Icons, Farben, Rahmen, den richtigen Font, Menüeigenschaften, Auflösungen von 96 und 120 dpi und vieles mehr. Gesetzt wird Looks wie jedes andere LAF:

try {
   String laf = Options.getSystemLookAndFeelClassName();
   UIManager.setLookAndFeel( laf );
 } catch ( Exception e ) {
   System.err.println( "L&F lässt sich nicht initialisieren: " + e );
 }

Inselraus: Undo unter Swing durchführen

Gute Benutzerschnittstellen zeichnen sich dadurch aus, dass dem Benutzer Fehler unterlaufen dürfen. Die Änderungen müssen jedoch wieder zurückgenommen werden können. Um dies in Java zu realisieren, gibt es Unterstützung durch ein Paket javax.swing.undo. Mit ihm lassen sich Undo- und Redo-Operationen mit relativ wenig Aufwand realisieren. Unser Beispiel soll ein Textfeld zeigen, dessen Änderungen auf Knopfdruck rückgängig gemacht werden. Zentrales Objekt ist dabei ein UndoManager. Dieser sammelt einzelne Aktionen, im Fall von Benutzereingaben jedes Zeichen. Die Anzahl der zu speichernden Aktionen ist beschränkt, lässt sich aber anpassen. Wir wollen in einem Beispiel ein JTextField mit einem Standardtext erzeugen. Anschließend wird ein UndoManager mit dem Document-Objekt des Textfeldes verbunden. Das Document informiert den UndoManager über Änderungen, der UndoManager speichert diese. Wenn eine Schaltfläche aktiviert ist, wird dem UndoManager befohlen, die Aktion rückgängig zu machen. Sichtbar wird dann wieder der Text, so wie er am Anfang in der Textbox stand:

package com.tutego.insel.ui.undo;
 
 import java.awt.BorderLayout;
 import java.awt.event.*;
 import javax.swing.*;
 import javax.swing.event.UndoableEditEvent;
 import javax.swing.undo.*;
 
 public class IComeUndone {
   public static void main( String[] args ) {
     JFrame f = new JFrame();
     f.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
 
     final JTextArea textarea = new JTextArea( 20, 40 );
     textarea.setText( "Hier zurück" );
     f.add( new JScrollPane(textarea) );
 
     //    final UndoManager undomanager = new MyUndoManager();
     final UndoManager undomanager = new UndoManager();
     textarea.getDocument().addUndoableEditListener( undomanager );
     undomanager.setLimit( 1000 );
 
     JButton undoB = new JButton( "Undo" );
     undoB.addActionListener( new ActionListener() {
       @Override public void actionPerformed( ActionEvent e ) {
         undomanager.end();
 
         if ( undomanager.canUndo() )
           undomanager.undo();
         textarea.requestFocus();
       }
     } );
 
     f.add( undoB, BorderLayout.PAGE_END );
     f.pack();
     f.setVisible( true );
   }
 }

Um genauer zu sehen, was das UndoManager-Objekt rückgängig macht, schreiben wir eine Unterklasse von UndoManager und überschreiben die Methode undoableEditHappened(UndoableEdit-Event) (diese Methode implementiert UndoManager von der Schnittstelle UndoableEditListener). Geben wir in unserer Realisierung auf dem Bildschirm aus, was bei jeder Aktion in den UndoManager kommt:

class MyUndoManager extends UndoManager {
   @Override
   public void undoableEditHappened( UndoableEditEvent e ) {
     UndoableEdit ue = e.getEdit();
     System.out.println( ue );
     addEdit( ue );
   }
 }

Die Methode undoableEditHappened(UndoableEditEvent) bekommt ein Ereignisobjekt, in dem der Verweis auf eine zurücknehmbare Operation abgelegt ist. An diese kommen wir mit get-Edit(). Die Rückgabe ist ein UndoableEdit-Objekt, also genau eine Operation, die zu einem Undo und Redo fähig ist. Der UndoManager speichert diese mit dem Aufruf von addEdit() in einer Datenstruktur ab. Wenn wir das UndoableEdit-Objekt auf dem Bildschirm ausgeben, sehen wir unsere durchgeführte Operation, zum Beispiel bei einem Einfügen:

[javax.swing.text.GapContent$InsertUndo@497934 hasBeenDone: true alive: true]

Beim Löschen erkennen wir ein:

[javax.swing.text.GapContent$RemoveUndo@ca470 hasBeenDone: true alive: true]

Inselraus: Die Zusatzkomponentenbibliothek SwingX

Für ernsthafte grafische Oberflächen mit Swing sind die Standardkomponenten der Java SE etwas mau. Wichtige Komponenten wie ein Datumsauswahldialog fehlen, zum Teil sind die angebotenen Komponenten nicht leistungsfähig genug. So kann beispielsweise die Swing-Textkomponente zwar einfaches HTML darstellen, scheitert aber an komplexeren Dokumenten mit CSS. Da die Java Foundation Classes in den letzten Jahren sehr stabil geblieben sind – es gab keine großen Änderungen oder nennenswerte neue Komponenten –, bleibt Entwicklern nichts anderes übrig, als entweder

  • die benötigten Swing-Komponenten selbst zu entwickeln oder
  • sich im Open-Source-Umfeld umzuschauen bzw.
  • eine kommerzielle Swing-Komponentenbibliothek einzukaufen.

Wie üblich ist die Eigenentwicklung immer nur die letzte Option. Im Laufe der letzten Jahre haben sich einige sehr gute quelloffene Swing-Zusätze etabliert. Hervorzuheben ist SwingX, ein Projekt, von dem lange Zeit gedacht wurde, dass die Komponenten in das javax.swing-Paket aufgenommen würden. Das ist aber bisher nicht passiert, nur eine Komponente hat es – wen auch abgewandelt – in die Standardbibliothek geschafft.

Hinweis: Aktiv wird SwingX nicht mehr weiterentwickelt.

Im Angebot: Erweiterte und neue Swing-Komponenten

Die SwingX-Bibliothek erweitert Swing auf zwei Arten:

  • Einige SwingX-Komponenten erweitern die Standard-Swing-Komponenten und fügen Funktionalität hinzu. So ist JXFrame eine Unterklasse von JFrame und kann zum Beispiel eine Statuszeile haben. Oder JXTable ist eine Unterklasse von JTable mit zusätzlicher Suchfunktionalität und dem Vorteil, dass sich Spalten automatisch ein-/ausblenden lassen.
  • SwingX definiert ganz neue Swing-Komponenten, etwa eine Mischung aus Tabelle und Baum (JXTreeTable), ausfaltbare Container oder einen Login-Dialog.

Um einen ersten Überblick über SwingX zu bekommen, lohnt es sich, einen Blick auf das SwingX-Demo zu werfen. Auf der SwingX-Homepage http://swingx.java.net/ wird auf die Web-Startapplikation verlinkt (http://swinglabs-demos.java.net/demos/swingxset6/swingxset.jnlp).

Überblick über erweiterte Standard-Swing-Klassen

SwingX nimmt einige Klassen als Basisklassen und fügt Funktionalität hinzu. Die Standardkomponenten lassen sich problemlos gegen die neuen Klassen austauschen.

SwingX-Klasse Swing-Basisklasse Aufgabe
JXButton JButton Schaltfläche mit Unterstützung für Painter
JXLabel JLabel Beschriftung mit Unterstützung für Painter
JXTable JTable stark erweiterte Tabelle mit Suche, Sortierung, Filterung, …
JXList JList stark erweiterte Liste mit Suche, Sortierung, Filterung, …
JXTree JTree stark erweiterter Baum mit Suche, Filterung, …
JXFrame JFrame Fenster mit optionaler Statuszeile, Warte-zustand, …
JXDialog JDialog Vereinfacht den Aufbau von Dialogen.
JXPanel JPanel Panel mit Transparenz und Painter
JXEditorPane JEditorPane verbesserte Editor-Funktionalität, Undo/Redo und Suche
JXComboBox JComboBox Verbessert den Edit-Modus beim Einsatz in einer Tabelle.
JXFormattedTextField JFormattedTextField Unterstützt Prompts und Buddies in formatierten Textzeilen.
JXTextArea JTextArea Unterstützt Prompts und Buddies in Text-bereichen.
JXTextField JTextField Unterstützt Prompts und Buddies in Text-zeilen.
JXRootPanel JRootPanel Wurzelkomponente mit Symbolleiste und Statuszeile

Erweiterte Standard-Swing-Klassen

Die Klassen JXTable, JXList, JXTreeTable sind sehr leistungsfähig und daher mit einem kleinen Satz kaum zu beschreiben. Die API-Dokumentation gibt mehr Informationen. Wichtig sind dabei Klassen aus dem Paket org.jdesktop.swingx.decorator, die für JXTable, JXTreeTable, JXTree und JXList Sortierung, Filterung und Hervorhebung unterstützen.

In den Zellen tauchen zudem die Begriffe Painter, Prompt und Buddy auf:

  • Ein Painter ist ein Objekt, das an die SwingX-Komponente gesetzt wird. Zum Zeichnen greift die SwingX-Klasse auf das externe Zeichenobjekt zurück. Der Vorteil ist, dass so keine Unterklassen nötig sind, etwa von JButton oder JLabel, nur wenn in den Zeichenprozess eingegriffen werden soll.
  • Prompts und Buddies zeigen bei JXTextField, JXFormattedTextField und JXTextArea Eingabehinweise oder Fehler.

Neue Swing-Klassen

Zusätzlich zu den Erweiterungen der Standard-Swing-Komponenten bietet SwingX weitere Komponenten. Manche sind dringend nötig, etwa zur Auswahl eines Datums, einige sind sehr speziell, etwa zum Zeichnen eines 2D-Graphen.

SwingX-Klasse Swing-Basisklasse Aufgabe
JXDatePicker JComponent Datumseingabe im Textfeld mit Popup-Menü
JXHyperlink JButton Hyperlink
JXBusyLabel JLabel Animation für Ladeprozesse
JXTreeTable JXTable Verschmilzt Tabelle und Baum.
JXStatusBar JComponent Statuszeile, in der Regel unten am Fenster
JXErrorPane JComponent Darstellung von Fehlermeldungen
JXMultiThumbSlider JComponent Slider mit zwei Knöpfen
JXMonthView JComponent Zeigt Monatskalender und erlaubt Monats-auswahl.
JXSearchField JXTextField Textfeld mit kleinem Such-Icon
JXRadioGroup JPanel Vereinfacht die ButtonGroup.
JXMultiSplitPane JPanel geschachtelte Split-Panels
JXCollapsiblePane JXPanel animierte ausfaltbare Panels
JXTaskPane JXPanel Titel mit Icon zum Ausfalten der Container-elemente
JXTaskPaneContainer JXPanel Container für mehrere JXTaskPanes
JXTipOfTheDay JXPanel Gibt den „Tipp des Tages“ aus.
JXTitledPanel JXPanel Panel mit Überschrift
JXTitledSeparator JXPanel Trennlinie mit Text
JXImageView JXPanel Zeigt ein Bild, ein neues kommt mit Drag & Drop.
JXColorSelectionButton JButton Zeigt bei Aktivierung einen Farbauswahldialog.
JXGradientChooser JXPanel Panel zum Aufbau eines Farbverlaufs (-Gradient)
JXGraph JXPanel Zeichnet 2D-Graphen.

Neue Klassen, die SwingX bietet

Zur Suche gehören noch ein paar andere Komponenten, die nicht aufgeführt sind. Für die JXTable gibt es weiterhin JXTableHeader, was eine Spezialisierung von JTableHeader ist.

Weitere SwingX-Klassen

Des Weiteren gibt es ein paar neue Layoutmanager, etwa HorizontalLayout, VerticalLayout oder StackLayout (setzt alle Komponenten aufeinander). SwingX unterstützt ebenfalls die Tastaturvervollständigung bei Textfeldern und der Combo-Box. Beispiele finden sich im Paket org.jdesktop.swingx.autocomplete.

SwingX-Installation

Wer SwingX einsetzen möchte, der lädt von http://java.net/downloads/swingx/releases/ das Java-Archiv swingx-all-1.6.4.jar (etwa 1,4 MiB) herunter und bindet es in den Klassenpfad ein. Auf den Seiten sind auch die Quellen und die Java-API-Dokumentation verlinkt. Nach dem Einbinden stehen die neuen Klassen unter dem Paket org.jdesktop.swingx zur Verfügung.

Inselraus: Details zur OpenJDK-Geschichte

Obwohl OpenJDK unter der GPL stand, enthielt es doch Teile wie den Font-Renderer, Sound-Unterstützung, Farbmanagement oder SNMP-Code, die als binäre Pakete beigelegt wurden, weil etwa die Rechte zur Veröffentlichung fehlten. Sun nennt diese Teile, die etwa 4 % vom JDK 6 ausmachen, belasteten Code (engl. encumbered code)[1]. Das hinderte puristische Linux-Distributoren daran, OpenJDK auszuliefern. RedHat startete im Juni 2007 das Projekt IcedTea, um diese binären Teile auf der Basis des OpenJDK durch GPL-Software zu ersetzen. So basiert der Font-Renderer zum Beispiel auf FreeType[2] und das Farbmanagement auf little CMS[3]. Mit diesen Ersetzungen erfüllte das OpenJDK mit IcedTea im Juni 2008 die Anforderungen des Technology Compatibility Kit (TCK) von Sun und ist in der Öffentlichkeit seither unter dem Namen OpenJDK 6 bekannt. Daraufhin floss das OpenJDK 6 plus der Ersetzungen unter der GPLv2 in Linux-Distributionen wie Fedora und Debian ein.

Das OpenJDK bildet die Basis von Java 8, und jeder Entwickler kann sein eigenes Java zusammenstellen und beliebige Erweiterungen veröffentlichen. Damit ist der Schritt vollzogen, dass auch Java auf Linux-Distributionen Platz finden darf, die Java vorher aus Lizenzgründen nicht integrieren wollten.

Auch wenn es sich so anhört, als ob das Oracle JDK bzw. OpenJDK das Gleiche sei, ist das nicht ganz richtig: Zwar basieren Oracle JDK und OpenJDK auf den gleichen Quellen (bei der Version 7 etwa zu 95 %), doch sind beim Oracle JDK immer noch proprietäre Dinge enthalten, und nicht alles ist hundertprozentig quelloffen und GPL. Das gilt für die Version 7 wie für die Version 6. Das Oracle JDK steht unter der Binary Code License; genau die muss jeder abnicken, der das JDK von der Webseite laden möchte.

Bei der 6er-Reihe kommt noch eine Besonderheit dazu, wie es die Versionsnummern[4] ganz gut zeigen. Während das Oracle JDK zum Beispiel im Juni 2011 bei Versionsnummer 1.6.0_26-b03 steht, ist das OpenJDK bei Version 6 b22. Die Versionsnummern sind deshalb völlig unabhängig, weil beide Projekte auch unabhängig voneinander laufen. Das hat mit der Geschichte zu tun. Nach der Entwicklung des JDK 6, das nicht unter der GPL steht, ging es mit dem JDK 7 logisch weiter. Aus dem JDK 7 (Build 10) entstand dann OpenJDK, das heute mit der Versionsnummer OpenJDK 7 genannt wird. OpenJDK 7 und JDK 7 entwickeln sich Hand in Hand, und Code-Änderungen gehen mal in die eine Richtung und mal in die andere.

Jetzt kommt die Besonderheit: Das OpenJDK 6 entstand nicht, wie vermutet werden könnte, aus dem Oracle JDK 1.6, sondern aus dem OpenJDK 7 (Build 20). Es wurden nur Java 7-Eigenschaften entfernt. So läuft das auch bis heute: Die meisten Änderungen am OpenJDK 6 sind Backports von OpenJDK 7. Änderungen am OpenJDK 7 stammen überwiegend von Oracle, und häufig ist es die Firma RedHat, die diese Änderungen in OpenJDK 6 portiert. Zwischen dem OpenJDK 6 und dem JDK 1.6 gibt es einen Quellcodeaustausch bei Bug-Fixes, doch die Codebasis ist unterschiedlich. Oracle JDK 6 ist im Wartungsmodus, und großartige Veränderungen passieren bis auf Fehlerbereinigungen nicht.

Oracle JDK 8 ist die Version, die die Download-Seite von Oracle anbietet; das OpenJDK 8 liegt auf einem eigenen Server http://openjdk.java.net/projects/jdk8/. Das OpenJDK bildet die Referenzimplementierung für Java SE, nicht das Oracle JDK.

[1]  http://www.sun.com/software/opensource/java/faq.jsp#h

[2]  http://www.freetype.org/

[3]  http://www.littlecms.com/

[4]  http://gist.github.com/925323