Java ist auch eine Insel

Sonntag, Juli 16, 2006

JDBC-Treiber für mobile Endgeräte

Mobile Endgeräte können nicht auf JDBC-Datenbanken zugreifen, da bisher keine JDBC-Schnittstelle in der J2ME vorgesehen ist. Künstliche Implementierungen kopieren daher die Schnittstellen von Sun. Für DB2 gibt es allerdings JdbcMe for DB2. Der Treiber besteht aus der kleinsten sinnvollsten Untermenge von JDBC 2.0. Der Treiber ist etwa 32 k groß.

Für die Connected Device Configuration (CDC) gibt es ein optionales Paket, welches in der JSR-169 die JDBC Optional Package for CDC/Foundation Profile API beschreibt.

Einen allgemeineren Weg geht ReqwirelessDB. Es arbeitet mit beliebigen Datenbanken zusammen, denn die Bibliothek implementiert die JDBC-Schnittstelle (die als Kopie dem Paket beiliegen) und delegiert die Anfragen zu einem Servlet. Jede Anfrage ist also entfernt, so dass nicht wirklich das mobile Endgerät die Datenverbindung zum Server aufnimmt. Der Server verbindet zum Datenbank-Server, holt von dort die Daten und schickt diese dem mobilen Endgerät zurück. Die Daten sind in XML (sic!), werden über kXML geparst und dann als ResultSet angeboten.

Das Fazit ist: JDBC ist bisher für mobile Endgeräte keine Lösung.

AddThis Social Bookmark Button

Das oberste Stack-Element duplizieren

Die Klasse Stack besitzt zwar die Basisfunktionalität, die ein Stapel besitzen sollte, aber auch nicht mehr. Hin und wieder wünschen wir uns aber eine Funktion, die das oberste Stack-Element dupliziert, kurz dup().

Bei der Implementierung treten allerdings zwei Fragen auf, mit denen zwei völlig unterschiedliche Lösungsansätze verbunden sind. Da die Klasse Stack wie die anderen Datenstrukturen auf Objekte ausgelegt ist, müssen wir uns darüber Klarheit verschaffen, wie das obere Objekt dupliziert werden soll. Soll eine Kopie der Objekt-Referenz neu auf den Stapel gelegt werden oder etwa das gesamte Objekt geklont werden?

Die einfache Lösung

Die einfachste Lösung besteht darin, das oberste Objekt einfach mittels der schon vorhandenen Stack-Methoden push() und peek() draufzulegen. Nehmen wir an, wir haben eine Unterklasse DupStack, dann sieht die erste Variante zum Clonen so aus:

void dup() /* throws EmptyStackException */
{
push( peek() );
}

peek() gibt aber lediglich eine Referenz auf das Objekt zurück. Und das anschließende push() speichert diese Referenz dann auf dem Stapel. Nehmen wir an, wir haben zwei StringBuffer-Objekte auf dem Stapel. Wenn wir nun dup() aufrufen und den String ändern, der oben auf dem Stapel liegt, so ändern wir automatisch das zweite Element gleich mit. Dies ist aber nicht unbedingt beabsichtigt, und wir müssen uns Gedanken über eine alternative Lösung machen. Wir sehen, dass dup() in der Klasse Stack fehlt, weil seine Implementierung davon abhängt, ob eine Referenz- oder eine Wertsemantik für Kellerelemente gewünscht ist.

Die kompliziertere Lösung mit Klonen

Um das oberste Stack-Element zu kopieren, bietet sich die clone()-Methode von Object an. All die Objekte, die sich klonen lassen, und das sind längst nicht alle, implementieren das Interface Cloneable. Nun ließe sich einfach folgern: Wenn das zu duplizierende Objekt ein Exemplar von Cloneable ist, dann können wir einfach die clone()-Methoden aufrufen und das zurückgegebene Objekt mittels push() auf den Stapel bringen.

void dup2() throws CloneNotSupportedException
{
try
{
Object top = peek();

if ( top instanceof Cloneable )
push( top.clone() );

}
catch ( EmptyStackException e ) { }
}

Beziehungsweise

void dup3() throws CloneNotSupportedException /*, EmptyStackException */
{
push( peek().clone() );
}

Dies funktioniert für die meisten Objekte, allerdings nicht für Objekte der Klasse Object. Denn clone() der Klasse Object ist protected - wir dürfen also von außen nicht dran, nur eine Unterklasse und die Klasse selbst. Hier haben wir also zwei Probleme.

  • Leider lässt sich nur mit Aufwand überprüfen, ob das Objekt auf dem Stapel auch wirklich ein pures Object ist, denn alle Objekte sind instanceof Object. Glücklicherweise gibt es kaum eine Anwendung, wo reine Object-Elemente gesichert werden müssen.
  • Was machen wir mit Objekten, die nicht klonbar sind? Leider gibt es für diese Frage keine direkte Antwort. Eine universelle Stack-Klasse mit einer uneingeschränkten dup()-Methode gibt es nicht. Wir müssen als Stack-Benutzer festlegen, dass das oberste Element Clonable ist, um zumindest eine eigene Implementierung nutzen zu können. Oder wir bleiben dabei, bei nicht klonbaren Objekten doch nur die Referenz zu duplizieren. Das wäre zumindest für eineindeutige Objekte mit Wertsemantik die ideale Lösung.

AddThis Social Bookmark Button

Mittwoch, Juli 12, 2006

Insel: Einen Webserver mit der com.sun.net.httpserver.HttpServer Klasse aus Java 6

Java bietet seit der Version 6 eine API, um Web-Services anzusprechen und auch neue Web-Services zu definieren und am eigenen Rechner anzumelden. Doch um Web-Services anbieten, und entfernten Clients Zugriff gewähren zu können, ist immer ein Web-Server nötig. Aus diesem Grund hat Sun einen einfachren HTTP-Server integriert, der sich auch eingeständig nutzen lässt; die Klassen sind zwar mehr oder weniger privat im Paket com.sun.net.httpserver deklariert, dennoch wollen wir ein Beispiel wagen.

Im Mittelpunkt steht die Klasse HttpServer – eine abstrakte Oberklasse, von der die Fabrikmethoden create() und create(InetSocketAddress, int) ein konkretes Exemplar liefern. Im nächsten Schritt verbindet die Methode createContext() einen Pfad (wie etwa /webapp1/) mit einem bestimmten HttpHandler, der die Anfrage an den Pfad übernimmt.

public class HttpServerDemo
{
public static void main( String[] args ) throws IOException
{
HttpServer server = HttpServer.create( new InetSocketAddress( 80 ), 0 );
server.createContext( "/", new DateHandler() );
server.start();
}
}


Jeder Handler implementiert die Schnittstelle HttpHandler mit der Funktion handle(HttpExchange), die über den Parameter HttpExchange Zugriff auf Header, Anfragekörper und Ergebnis ermöglicht.

Ein einfacher HttpHandler, der Anfragen mit einer HTML-Seite beantwortet, die Datum und den Anfragepfad enthält, kann so aussehen:

class DateHandler implements HttpHandler
{
public void handle( HttpExchange httpExchange ) throws IOException
{
httpExchange.getResponseHeaders().add( "Content-type", "text/html" );
String response = "<b>" + new Date() + "</b> for " + httpExchange.getRequestURI();
httpExchange.sendResponseHeaders( 200, response.length() );

OutputStream os = httpExchange.getResponseBody();
os.write( response.getBytes() );
os.close();
}
}


Die Methode getResponseHeaders() liefert ein Headers-Objekt (eine Map<String,List<String>>), zum Setzen der Response-Header. Das Beispiel setzt den Content-type auf text/html. Die Methode sendResponseHeaders schließt die Header-Angaben mit einem Statuscode (Response-Code) und der Content-Länge ab. getRequestURI() gibt den Pfad und ermöglicht uns das Zerlegen der Elemente nach Verzeichnis, Datei, Dateiendung, Ankern und Parametern.

HttpExchange liefert mit getResponseBody() einen OutputStream, dass das Ergebnisdokument formuliert. Die Methode getRequestBody() liefert einen InputStream für das, was der Client sendet. Das Beispiel schreiben die Bytes vom String in den OutputStream und schließt ihn anschließend.

Nach dem Start des Server können wir im Webbrowser URLs wie http://localhost/, oder http://localhost/webapp/bla eingeben und wir erhalten das Datum und die Pfadangabe.

AddThis Social Bookmark Button

Mittwoch, Juli 05, 2006

Insel: Der SwingWorker in Java 6

Mit dem SwingWorker ist es einfach möglich, längere Programmteile im Hintergrund von einem nicht AWT-Thread abarbeiten zu lassen und dann später die Ergebnisse wieder in die Gui über den AWT-Thread einfließen zu lassen.

Für einen eigenen SwingWorker ist zunächst eine Unterklasse von javax.swing.SwingWorker zu bilden. Wir wollen eine Klasse ClockPrecision angeben, die zwei Sekunden wartet, und dabei die Zeit misst – das Ergebnis ist durch Ungenauigkeit nicht wirklich 2 Sekunden. Wir interessieren uns hier für die Ungenauigkeit. Nach Ablauf der Zeit soll der SwingWorker das Ergebnis auf die Schaltfläche schreiben, die auch der Auslöser für die Warterei ist.




package com.javatutor.insel.ui.event;

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

public class SwingWorkerDemo extends JFrame
{
JButton button = new JButton( "Change my mind!" );

SwingWorkerDemo()
{
setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
add( button );

ActionListener al = new ActionListener() {
public void actionPerformed( ActionEvent e )
{
new ClockPrecision().execute();
}
};

button.addActionListener( al );

pack();
}

class ClockPrecision extends SwingWorker<Long, Object>
{
@Override public Long doInBackground()
{
long startNano = System.nanoTime();
try { Thread.sleep( 2000 ); } catch ( InterruptedException e ) { }
return (System.nanoTime() - startNano ) / (1000*1000);
}

@Override protected void done()
{
try
{
button.setText( "" + get() );
}
catch ( /* InterruptedException, ExecutionException */ Exception e ) { }
}
}

public static void main( String[] args )
{
new SwingWorkerDemo().setVisible( true );
}
}


done() bekommt die Rückgabe von doInBackgrund() über die get() Methode. Unser SwingWorker durchlauft mehrere Phasen, an die wir und durch Überschreiben einiger Methoden aktiv beteiligen.



  • Es beginnt mit execute(), was den SwingWorker dazu bewegt, einen so genannten Worker-Thread aufzubauen.

  • Der Worker-Thread ruft doInBackgrund() auf, in dem wir unseren im Hintergrund auszuführenden Programmteil setzen. Der Rückgabetyp ist durch die generische Verwendung frei wählbar und das Ergebnis, was get() empfängt. Sind mit addPropertyChangeListener() neue PropertyChangeListener angemeldet, können wir sie mit firePropertyChange() aufrufen und während der Verarbeitung Status-Ereignisse schicken. Es erlaubt publish() das Absenden von Zwischenergebnissen, die unter dem AWT-Event in process() sich verarbeiten lassen. Dieser Typ kann andere als der von get() sein und so bestimmt die zweite Typvariable der generischen Klasse diesen Typ.

  • Am Ende des Worker-Threads kommt es im AWT-Event Thread zu einem Aufruf von done(), wo wir unsere Swing-Operationen vornehmen können.


In der API-Dokumentation ist weiteres zu entnehmen.


AddThis Social Bookmark Button

Montag, Juli 03, 2006

Insel: JDBC-Versionen

Mit dem wachsen der Java-Versionen ist auch die Versionsnummer von JDBC gestiegen.

• Am Anfang stand JDBC 1.0, was Sun im Jahre 1997 in Java 1.1 integriert hat. JDBC 1.0 basiert auf SQL-92.

• Die nächste Spezifikation ist die JDBC 2.0 API und berücksichtigt SQL-99 (SQL-3). Die Spezifikation der Version 2 setzt sich aus zwei Teilen zusammen: Einer JDBC 2.0 core API und einer JDBC 2.0 Optional Package API. Die Core API im Paket java.sql erweitert das Ur-JDBC um Batch-Updates, SQL-3 Datentypen. Das JDBC Optional Package liegt im Paket javax.sql und definiert unter anderem Data-Source, Connection-Pooling, Verteilte Transaktionen. Während das Core-Paket fester Teil von Java 1.2 war, ist das optionale Paket in Java 1.2 noch echt optional und erst in Java 1.3 fest integriert. Für fast alle Datenbanken gibt es JDBC 2.0 Treiber.

• JDBC 3.0 ist Teil von Java 1.4. Es integriert die JDBC 2.1 core API, das JDBC 2.0 Optional Package und nimmt neu unter anderem hinzu: Savepoints in Transaktionen, Wiederverwendung von PreparedStatements, JDBC-Datentypen BOOLEAN und DATALINK, Abrufen automatisch generierter Keys, Änderungen von LOBs und mehrere gleichzeitig geöffnete ResultSets.

• In Java 5 hat sich nicht viel an JDBC passiert. Es ist immer noch JDBC 3.0, doch JDBC RowSet Implementierungen sind hinzugekommen.

• JDBC 4.0 ist Teil von Java 6 mit einigen Änderungen: Annotationen für SQL-Queries, Treiber werden – wenn vorbereitet – automatisch angemeldet, XMLDatentypen aus SQL:2003, Zugriff auf die SQL ROWID.

Hinweis Eine große Anzahl JDBC 4 Treiber ist so schnell nicht zu erwarten. http://developers.sun.com/product/jdbc/drivers zählt bislang keinen JDBC 4 Treiber auf – obwohl ihre eigene Datenbank Java DB durchaus JDBC 4 spricht. Für JDBC 3 gibt es immerhin 38 Treiber; 99 sind JDBC 2 Treiber.


Grad der SQL-Unterstützung

Auch wenn der neueste Treiber einer Datenbank vor uns liegt, heißt das nicht, dass er auch alle JDBC-Möglichkeiten ausschöpft. Zum einen kann das daran liegen, dass die Datenbank diese Möglichkeiten gar nicht bietet – etwa Savepoints, oder der Treiber nicht hinreichend aktuell ist.

Einige Möglichkeiten lassen sich über die Metadaten einer Datenbank erfragen. Dazu zählt zum Beispiel, ob ein Treiber/Datenbank den vollen ANSI-92 Standard unterstützt. Die Metadaten liefern über die Methoden supportsANSI92XXXSQL() ob die Datenbank ANSI 92 Entry Level (Gilt immer), Intermediate SQL oder Full SQL unterstützt. Auch für ODBC gibt es unterschiedliche Level: Mimimum SQL Grammar, Core SQL Grammar, Extendes SQL Grammar. Weitere Informationen gibt die Sun-Webseite http://java.sun.com/products/jdbc/driverdevs.html.

AddThis Social Bookmark Button

Sonntag, Juli 02, 2006

Insel: Fließkommazahlen im Hashcode

Anhängig von den Datentypen sehen die Berechnungen immer etwas unterschiedlich aus. Während Ganzzahlen direkt in einen Ganzzahlausdruck für den Hashcode eingebracht werden können, ist im Fall von double die Konvertierungsfunktion Double.doubleToLongBits() beziehungsweise Float.FloatToIntBits() im Einsatz.

Die Datentypen double und float habe eine weitere Spezialität, da NaN und das Vorzeichen der 0 zu beachten sind, wie das Kapitel 5 näher ausführt. Zusammenfassend gesagt: Sind x = +0.0 und y = -0.0, gilt x == y, aber Double.doubleToLongBits(x) != Double.doubleToLongBits(y). Sind x = y = Double.NaN gilt x != y aber Double.doubleToLongBits(x) == Double.doubleToLongBits(y). Wollen wir die beiden Nullen nicht unterschiedlichen behandeln, sondern als gleich werten, ist ein übliches Idiom:

x == 0.0 ? 0L : Double.doubleToLongBits( x )

Es liefert Double.doubleToLongBits(0.0) die Rückgabe 0, aber Double.doubleToLongBits(-0.0) würde -9223372036854775808 geben.

AddThis Social Bookmark Button