Parametersammlungen im Servlet mit getParameterValues() auslesen

Da ein Parameter auch mehr als einen Wert haben kann, hilft getParameter() nicht weiter, da dieser nur jeweils einen Wert liefert. Hier führt die Methode getParameterValues() zum Ziel, die ein Feld von Strings zurückgibt. (Damit ist kein zusammengesetzter String etwa für Suchmaschinen gemeint.) Sind wir an einer vollständigen Aufzählung der Schlüssel interessiert, liefert getParameterNames() ein Objekt vom Typ Enumeration zurück, mit dem wir durch das Feld wandern und die Werte mit getParameter() erfragen können.

<%
java.util.Enumeration paramNames = request.getParameterNames();
while ( paramNames.hasMoreElements() )
{
 String param = (String) paramNames.nextElement();
 out.print( "<p>" + param + " = " );
 String[] paramValues = request.getParameterValues( param );
 if ( paramValues.length == 1 )
 {
  String paramValue = paramValues[0];
  if ( paramValue.length() == 0 )
   out.println( "unbestimmt" );
  else
   out.println( paramValue );
 }
 else
 {
 for ( int i = 0; i < paramValues.length; i++ )
  out.print( paramValues[i] + " " ) ;
  out.println( "<p>" );
 }
}
%>

Wenn wir das Programm mit der Zeile

http://localhost:8080/jt/parameterNames.jsp?a=1&b=2&c=&a=2

im Browser starten, erzeugt das Servlet folgende Ausgabe:

b = 2
a = 1 2
c = unbestimmt

Wir sehen, dass alle Parameter hier aufgeführt sind, doch in unbestimmter Reihenfolge. Dies ist aber egal. Das Programm erkennt, ob ein Wert gesetzt ist oder nicht beziehungsweise ob einem Schlüssel ein Wert zweimal zugewiesen wurde.

Wie heißt die Klasse mit der Methode main()?

In C(++) ist das erste Element des Felds der Funktion main(int argc, char **argv) der Name des Programms. Das ist in Java anders. Die Methode enthält als ersten Parameter nicht den Namen der Klasse beziehungsweise des Programms, sondern einfach den ersten Parameter – sofern einer auf der Kommandozeile übergeben wurde. Auf einem kleinen Umweg ist das auch für manche Klassen möglich.

Der zu einer Klasse gehörende Klassenlader lässt sich mit dem Class-Objekt erfragen. Mit der Methode getResource() erhalten wir von einem Klassennamen ein URL-Objekt zurück, das (unter gewissen Voraussetzungen) die Position der Klassendatei im Dateisystem anzeigt. Das folgende Programmbeispiel zeigt, wie wir von einer Klasse den vollständigen Dateipfad zurückbekommen.

package com.tutego.insel.lang;

import java.net.*;

public class FindClassLocation
{
 static String findLocation( Class<?> clazz )
 {
  ClassLoader loader = clazz.getClassLoader();

  if ( loader == null )
   return null;

  URL url = loader.getResource( clazz.getName().replace('.', '/' ) + ".class" );

  return ( url != null ) ? url.toString() : null;
 }

 public static void main( String[] args ) throws Exception
 {
  Class<?> c = Class.forName( "com.tutego.insel.lang.FindClassLocation" );

  System.out.println( "Class: " + c.getName() );
  System.out.println( "Filename: " + findLocation(c) );
 }
}

Unter meinem Dateisystem liefert das Programm die Ausgabe:

Class: com.tutego.insel.lang.FindClassLocation
Filename: file:/S:/Insel/programme/09_Funktionsbibliothek/com/tutego/insel/lang/FindClassLocation.class

Achtung! Die Lösung funktioniert natürlich nur unter gewissen Voraussetzungen. Es geht nur für Klassen, die in kein Jar-Archiv eingebunden sind und nicht den Standardbibliotheken entstammen. Auch ist eine Dateiangabe unmöglich, wenn wir etwa einen eigenen Klassenlader schreiben, der die Klassen aus einer Datenbank bezieht; dann gibt es keinen Pfad mehr.

getResourceAsStream()

Benötigen wir den Ort einer Klasse, um mit dieser Information auf weitere Dateien im Verzeichnis zuzugreifen, geht es mit der Class.getResourceAsStream(String) einfacher. Diese Methode dient insbesondere dazu, Ressourcen wie Bilder oder Musik aus einem Jar-Archiv auszulesen. Auch der ClassLoader bietet die Methode getResourceAsStream(String) an. Diese Methoden funktionieren ebenfalls für Klassen aus Jar-Archiven, wenn die Ressource im Archiv liegt.

Durch Null-Cursor Flackern des Mauszeigers bei Animationen vermeiden

Einige Betriebssysteme haben bei Java-Animationen das Problem, dass der Mauszeiger unruhig flackert. Zur Lösung kann man einen Cursor ohne Pixel an die Stelle der Grafik setzen.

Es ist Sache der grafischen Oberfläche, den Mauszeiger mit dem Hintergrund zu verbinden. Um ein unruhiges Bild zu vermeiden, greifen wir zu einem Trick und schalten den Mauszeiger einfach ab. Dazu soll createCustomCursor() einen neuen Cursor mit transparentem Image-Objekt erzeugen. Da wir kein leeres transparentes GIF-Bild nutzen wollen, legen wir einfach mit der Klasse BufferedImage ein Bild im Speicher an. Das Argument muss dabei TYPE_INT_ARGB sein, sonst ist das Bild nicht transparent. Damit ist die Arbeit getan, der letzte Schritt besteht darin, den Cursor mit setCursor() einer Komponente zuzuweisen. Im Fall einer Animation wäre das zum Beispiel ein JComponent, im folgenden Beispiel wird das eine Schaltfläche sein:

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

public class NullCursor
{
 public static void main( String[] args )
 {
  JFrame f = new JFrame();
  f.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );

  JButton b = new JButton( "Kein Cursor" );
  f.add( b, BorderLayout.PAGE_START );

  b.setCursor( Toolkit.getDefaultToolkit().createCustomCursor(
    new BufferedImage( 16, 16, BufferedImage.TYPE_INT_ARGB ),
    new Point(0,0), "" ) );

  f.setSize( 200, 200 );
  f.setVisible( true );
 }
}

Algorithmen für Rastergrafik, 1. Linien zeichnen mit Bresenham

Der Bildschirm ist in eine Reihe von Punkten zerlegt, die alle mit verschiedenen Intensität oder Farbe gesetzt sind. Die Rastergrafik versucht, die geometrischen Beschreibungen von Objekten auf die Pixel des Bildschirmes abzubilden. Diese Umrechnung muss schnell und in einigen Fällen speichersparend sein. In den Letzen Jahren wurden eine ganze Reihe von Algorithmen entwickelt, die mittlerweile in Hardware Einzug gehalten hat. Die modernen Grafikkarten unterstützen neben grafischen Operatio­nen wie Pixel und Farbe mischen auch Funktionen zum Zeichen von Linien, Kreisen, Polygonen, gefüllten Flächen und weiteres. Daneben sind dreidimensionale Koordinatentransformationen in Hardware gegossen wie das Abbilden einer Bitmap auf eine Fläche, das Erzeugen von Nebel (engl. Fog­ging) oder auch perspektivische Korrektionen (engl. Perspective Correction). Natürlich überlassen wir das Zeichen der Linien der Grafikkarte und die dreidimensionalen Transformationen der 2D-Biblio­thek. Java ist allerdings eine einfache Sprache und die Arbeitsweise kann somit schnell erklärt werden. Notfalls können wir Funktionen, die nicht in der Grafikbibliothek vorliegen, nachbilden.

Linien zeichnen

Linien sind ein einfachen Beispiel dafür, wie Punkte auf dem Raster verteilt werden. Beachten wir dazu die Grafik. Wie zeichnen eine Linie von den Koordinaten (x1,y1) zum Punkt (x2,y2).

Um nun die Punkte auf dem physikalischen Bildschirm zu setzen, müssen die Punkte so verteilt wer­den, dass sie möglichst nah an der wirklichen Linien liegen. Gehen wir die X-Achse von xl nach x2 ab, so müssen wir die Y-Punkte wählen. Bei der Wahl können wir folgende Entscheidung fällen: An jedem Punkt müssen wir einen Pixel wählen, nämlich diesen, der den geringsten Abstand zum Pixelraster besitzt. Ist die Linie horizontal, so ist der Abstand zwischen wirklicher Linie und Rasterpunkt immer Null und die Wahl ist einfach.

Ein Algorithmus kann nun wie folgt arbeiten: Es ist der Abstand zwischen der wirklichen Linie und einem Pixel auf der Y-Achse gleich einem Fehlerwert. Es ist der Punkt auf der Y-Achse auszuwählen,
der zur wirklichen Linie den kleinsten Fehlerwert aufweist. Einen schnellen Algorithmus, der genau mit diesem Fehlerwert arbeitet, wurde zuerst von j. E. Bresenham (1965) veröffentlicht. Bresenham
betrachtet Linien im ersten kartesischen Oktanten, die Steigung der Linie ist also kleiner Eins, formal: 0 < Δy / Δx < 1. Die Rasterung ist von links nach rechts. Betrachten wir nun ein Programmsegment in
Pseudocode das, welches eine Linie mit Steigung kleiner Eins zeichnet:

Δx = x2 - x1
Δy = y2 - y1
error = - Δx / 2;
setPixel( x1, y1 );
while( x < x2 ) {
 error = error + Δy;
 if ( error >= 0 ) {
  y = y + 1;
  error = error - Δx;
 }
 x = x + 1;
 setPixel( x, y );
}

Liegen die Linien in anderen Oktanten, so müssen wir den Algorithmus nur leicht abändern. So sind durch einfache Spiegelungen an Winkelhalbierenden oder Ordinate bzw. Abszisse die Punkte zu verschieben. Liegt die Linie beispielsweise im siebten Oktanten, so rastern wir entlang der Y-Achse und vertauschen dx mit -dx und liegt die Linie im achten Oktanten, so rastern wir wir im ersten Oktanten, nur dass dy mit -dy vertauscht wird.

Bresenhams Linienalgorithmus arbeitet schnell und optimal (was bedeutet, die Fehler sind am kleinsten), weil alle Berechnungen auf Ganzzahlen ausgeführt werden. Dies war in den sechziger Jahren

noch wichtiger als heute, wo eine Fließkommaoperation schon genauso viel kostet wie eine lnteger-Operation. Da der Beweis langwierig — aber für die mathematisch geschulten nicht schwierig — ist, wollen wir an dieser Stelle auf den Formalismus verzichten. Anstelle dessen betrachten den Quellcode einer Klasse, die eine einfaches Linienmuster schreibt. Ein nicht unwesentlicher Teil wird dafür aufgewendet, den Algorithmus für die verschiedenen Lagen im kartesischen Koordinatensystem anzupassen. Da wir später noch andere grafische Objekte zeichnen, dient dieses Programm gleichzeitig als Rahmen.

import java.awt.*;
import java.awt.event.*;

public class GDV extends Frame
{
  private Graphics g;
  static final int CLASSIC = 0;
  static final int BRESENHAM = 1;
  public int lineType = CLASSIC;
  
  public GDV() {
    setSize( 400, 400 );
    addWindowListener( new WindowAdapter() {
      public void windowClosing ( WindowEvent e ) { System.exit(0); }
    });
  }

  /**
   * Draws a line with algorithm of Bresenham
   */
  private void drawBresenhamLine( int x1, int y1, int x2, int y2 )
  {
    int xIncrement = 1,
        yIncrement = 1,
        dy = 2*(y2-y1),
        dx = 2*(x1-x2),
        tmp;
  
    if ( x1 > x2 ) {      // Spiegeln an Y-Achse
      xIncrement = -1;
      dx = -dx;
    }
  
    if ( y1 > y2 ) {      // Spiegeln an X-Achse
      yIncrement= -1;
      dy= -dy;
    }
  
    int e = 2*dy + dx;
    int x = x1;           // Startpunkte setzen
    int y = y1;

    if ( dy < -dx )       // Steigung < 1
    {      
      while( x != (x2+1) )
      {
        e += dy;
        if ( e > 0) 
        {
          e += dx;
          y += yIncrement;
        }
        g.drawLine( x, y, x, y );
        x += xIncrement;
      }
    }
    else // ( dy >= -dx )   Steigung >=1
    {
      // an der Winkelhalbierenden spiegeln
      tmp = -dx;
      dx = -dy;
      dy = tmp;

      e = 2*dy + dx;

      while( y != (y2+1) )
      {
        e += dy;
        if( e > 0 ) {
          e += dx;
          x += xIncrement;
        }
        g.drawLine( x, y, x, y );
        y += yIncrement;
      }
    }
  }
  
  private void line( int x1, int y1, int x2, int y2 ) {
      if ( lineType == BRESENHAM )
          drawBresenhamLine( x1, y1, x2, y2 );
      else
          g.drawLine( x1, y1, x2, y2 );
  }

  public void paint( Graphics g ) {
    this.g = g;

    for ( int i=30; i < 300; i+=20 )
      line( 10+i, 40, 300-i, 100 );
  }
  
  public static void main( String[] args ) {
    GDV line = new GDV();

//    line.lineType = CLASSIC;
    line.lineType = BRESENHAM;

    line.show();
   }
}

Bresenhams Algorithmus arbeitet ohne Frage schnell. Wir können aber noch einige Erweiterungen programmieren: Wie wird eine antialiased Linie, also eine weiche Linie gezeichnet? Es gibt mehrere

Mögichkeiten, wobei aber nicht alle schnell sind. Eine Lösung besteht darin, auf jeden Punkt einen Filter loszulassen. Dies läuft aber auf viele Multiplikationen hinaus. Bekannt geworden ist ein anderer

Ansatz, ein Algoritmus von Gupta—Sproull (1981). Dieser ist in allen bekannten Grafik-Büchern nachzulesen.

Da der Algorithmus von Bresenham sehr schnell ist, wundert es einen, wenn wir noch von einer Geschwindigkeitsoptimierung sprechen. Das ist tatsächlich möglich und wurde von Angel, Edward

und Don Morrison in einem Aufsatz in den “IEEE Computer Graphics and Applications” auf zwei Seiten Ende 1991 beschrieben. Der Hintergrund ist einfach und kann daher kurz mit einer Programmieraufgabe umgesetzt werden: Die Pixelbewegungen wiederholen sich zyklisch. In einer Linie der Steigung 1, ist mit jedem Erhöhen des X-Zählers auch ein Erhöhen des Y-Zählers Verbunden. Eine Gerade mit der Steigung 1/8 setzt vier Punkte auf der Horizontalen, geht dann einen Pixel nach oben und setzt wieder vier Punkte. Können wir den Rhythmus erkennen, dann haben wir es einfach, denn dann müssen uns nur die Zeichentechnik merken und dann immer wieder kopieren. Für parallele Prozesse gibt es nichts schöneres. Wir berechnen dazu den Größten Gemeinsamen Teiler von Δy und -Δx. Ist dieser echt größer als Eins, dann haben wir damit den ersten Punkt, an dem die Berechnungsfolge von vorne beginnt. Der Punkt zeichnet sich durch die Koordinaten (x1 + a/ggT(Δy,  -Δx, y1 + b/ggT(Δy/-yΔx) aus.

Thema der Woche: XPath und XSLT

Refactoring mit Alt + Shift + R geht nicht mehr? RadioSure kann Schuld sein

Seit einiger Zeit gab es einen merkwürden Effekt: Zeitweilig ging das Refactoring über den Shortcut Alt + Shift + R nicht mehr. Ich dachte erst an ein komisches Eclipse-Plugin, aber selbst Eclipse wollte zu dem Shortcut gar keine Operation anzeigen, es ging bis Alt + Shift und das R schluckte das System schon. Damit war klar, dass die Tastenkombination erst gar nicht bei Eclipse ankam – es musste ein anderes Windows-Programm sein. Ich schaute auf die Liste und dann fiel mir sofort RadioSure auf, und dann jetzt verstand ich auch, warum ich immer wieder unbeabsichtigte Aufnahmen hatte: Die Recording-Funktionalität wird exakt mit Alt + Shift + R getoggelt.

RadioSureEclipseShortConflict

Nach dem Deaktivieren war dann auch mit dem Refactoring alles wieder in Ordnung.

Die SQL3-Datentypen ARRAY, STRUCT und REF

Seitdem JDBC 2.0 die SQL3-Datentypen unterstützt, sind weitere Spaltentypen über Java-Programme zugänglich:

  • ARRAY. Der Datentyp erlaubt eine Aufzählung mehrerer Werte wie ein Feld in einer Spalte.
  • STRUCT. Neue benutzerdefinierte Typen – auch UDTs (user-defined types) genannt –, die auf der Datenbankseite mit der SQL-Anweisung CREATE TYPE angelegt werden.
  • REF. Verweise auf strukturierte Typen.

Am Nützlichsten ist das Feld.

ARRAY

SQL-Felder werden in JDBC durch die Schnittstelle java.sql.Array behandelt. Nehmen wir in einer Tabelle eine Spalte Aktienverlauf an, die eine Liste (Array) von Zahlen enthält. Aus dem ResultSet gibt dann getArray() Zugriff auf die Informationen:

Array verlauf = rs.getArray( "Aktienverlauf" );

Die Variable verlauf verweist jetzt auf das Array-Objekt, was dem SQL ARRAY entspricht. Die Werte können nun entnommen werden, doch nicht mit einem Iterator, und auch nicht mit get(index) Funktionen, sondern wieder mit einem Aufruf getArray(). Es liefert ein Feld genau des passenden Typs zurück.

String[] verläufe = (String[]) verlauf.getArray();
for ( int i = 0; i < verläufe.length; i++ )
  ...

Neben dem lesenden getArray() setzt setArray() ein Feld, und updateArray() aktualisiert die Werte.

Kreditkartennummern in Java testen

Sind Kreditkartennummern korrekt aufgebaut? Wie lassen sich sich Kreditkartennummern generieren?

E-Commerce-Lösungen sind im Internet mittlerweile häufig anzutreffen. Lassen sich für kleine Beträge Sonderlösungen finden, werden für größere Beträge immer noch Kreditkarten verwendet. Grund genug für uns Java-Programmierer, die Nummern der Karten zu testen, um zu überprüfen, ob uns nicht ein Anwender täuschen wollte.

Die Nummer einer Kreditkarte setzt sich nicht willkürlich zusammen. Die Nummern von Karten eines bestimmten Herstellers bestehen aus einer festen Anzahl von meistens 14 bis 16 Ziffern. Als Kennung für einen Hersteller (Visa (Veni, Vidi, VISA: I came, I saw, I did a little shopping.) , MasterCard, American Express) ist jeder Nummer eine zusätzliche Kennung von einer bis vier Ziffern vorangestellt. Die Ziffern der Kartennummer werden durch mathematische Verfahren überprüft. Wir wollen eines dieser Verfahren auch kennen lernen; den so genannten Luhn-Algorithmus. Dieser Algorithmus testet die Korrektheit des Aufbaus einer Nummernfolge. Die letzte Ziffer ist oft eine berechnete Checksummen-Ziffer.

Die folgende Tabelle gibt eine Übersicht über einige Kartenhersteller. Sie führt die Kennung, die Länge der Kartennummer und ein gültiges Beispiel auf:

Hersteller

Anfang

Gesamtlänge

Beispiel

Visa

4

13,16

4111 1111 1111 1111

Master

51,52,53,54,55

16

5500 0000 0000 0004

Diner’s Club11

30,36,38

14

3000 0000 0000 04

American Express12

34,37

15

3400 0000 0000 009

Discover

6011

16

6011 0000 0000 0004

en Route

2014,21

15

2014 0000 0000 009

JCB

3088,3096,3112,3158,3337,3528

16

3088 0000 0000 0009

Neben den Herstellernummern sind auch folgende Nummern von den ausgebenden Ban­ken im Umlauf: Manufacturers Hanover Trust (1033), Citibank (1035), Chemical Bank (1263), Chase Manhattan (1665), Bank of America (4024), Citicorp (4128), New Era Bank (4209), HHBC (4302), Imperial Savings (4310), MBNA (4313), California Federal (4317), Wells Fargo (5282), Citibank (5424), Wells Fargo (5410), Bank of New York (5432), MBNA (6017). Carte Blanche und Diner’s Club haben die gleichen Nummern.

Einen Abend im Februar 1950 vergaß Frank MacNamara seine Brieftasche. Er kam auf die Idee, eine Kredit­karte aus Karton anzubieten. Mit seinen Freunden gründete er am 28.2.1950 den Diner’s Club, der im Grün­derjahr mehr als 10.000 Mitglieder und 1.000 Vertragspartner hatte. So war die erste Kreditkarte geboren. Im Jahre 1958 entschloss sich das internationale Transport-, Reise-, und Finanzierungsunternehmen American Express, eine eigene Karte herauszugeben.

Die Überprüfung mit dem Luhn-Algorithmus

Der Luhn-Algorithmus (auch modulus 10 oder mod 10-Algorithmus genannt) basiert auf dem ANSI-Vorschlag X4.13. Er wurde Ende 1960 von einer Gruppe Mathematiker ausgearbeitet und veröffentlicht. Danach nutzten Kreditkartenhersteller dieses Verfahren zur Prüfung der Kreditkartennummern. Auch die Versichertennummer in Kanada, die Canadian Social Insurance Number (CSIN), wird über das Luhn-Verfahren geprüft.

Der Algorithmus testet, ob die letzte Ziffer der Kreditkartennummer korrekt zu den angegebenen Zahlen passt. Die Testziffer wird von allen Ziffern außer der letzten Ziffer berechnet und anschließend mit der angegebenen Testziffer verglichen. Stimmt sie überein, ist die Karte seitens der Prüfnummer in Ordnung. Wir wollen das Verfahren hier nicht näher vertiefen, sondern einfach den Algorithmus angeben:

class LuhnTest
{
  static boolean luhnTest( String s )
  {
    int len = s.length();

    int digits[] = new int[len];

    for ( int i = 0; i < len; i++ )
    {
      try {
        digits[i] = Integer.parseInt( s.substring(i,i+1) );
      }
      catch ( NumberFormatException e ) {
        System.err.println( e );
        return false;
      }
    }

    int sum=0;

    while ( len > 0 )
    {
      sum += digits[len-1];
      len--;

      if ( len > 0 )
      {
        int digit = 2*digits[len-1];
        sum += ( digit>9) ? digit-9 : digit;

        len--;
      }
    }

    return ( sum%10 == 0 );
  }


  static boolean isVisa( String s )
  {
    if ( ( (s.length() == 16) || (s.length() == 13) ) &&
          (s.charAt(0) == '4') )
      return luhnTest( s );

    return false;
  }

  public static void main( String args[] )
  {
    System.out.println( luhnTest( "4111111111111111" ) );
    System.out.println( luhnTest( "5500000000000004" ) );
    System.out.println( luhnTest( "340000000000009" ) );
    System.out.println( luhnTest( "30000000000004" ) );
    System.out.println( luhnTest( "601100000000000" ) );
    System.out.println( luhnTest( "201400000000009" ) );
    System.out.println( luhnTest( "3088000000000009" ) );
    System.out.println( luhnTest( "9238475098787444" ) );

    System.out.println( isVisa( "4111111111111111" ) );
    System.out.println( isVisa( "5500000000000004" ) );


    // Böse: Visa-Nummer generieren

    char[] c = "4123456789123456".toCharArray();

    while ( !isVisa(new String(c)) )
      c[(int)(Math.random()*c.length-1)+1] = (char)('0'+Math.random()*9.9);

    System.out.println( c );
  }
}

Im Quelltext ist eine zusätzliche Methode eingebaut, die testet, ob die Karte von Visa ist. Dazu müssen wir nur überprüfen, ob die erste Ziffer eine 4 und die gesamte Zahl nach dem Luhn-Verfahren gültig ist. Andere Tests sind genauso einfach durchzuführen. Eine mögliche Erweiterung wäre, die Methode fehlertoleranter zu gestalten, indem Trennzeichen herausgefiltert werden. Dies und die Implementierung der übrigen Tests überlasse ich als Übung den Lesern.

Wir beginnen mit einer vorgegebenen, unsinnigen Kartennummer, deren erste Stelle "4" ist, wie für eine Visa-Karte erforderlich. Anschließend ändern wir in einer Schleife eine zufällig ausgewählte Stelle der Kartennummer (außer der ersten) in eine ebenfalls zufällig bestimmte Ziffer aus dem Bereich "0" bis "9". Das wiederholen wir so lange, bis die abgewandelte Zahl irgendwann passt.

Beispiel Mit diesen Methoden ist es natürlich leicht möglich, Nummern zu erzeugen. Betrachten wir Folgendes:

char c[] = "4123456789123456".toCharArray();
while ( !isVisa(new String(c)) )
  c[(int)(Math.random()*c.length-1)+1] = (char)('0'+Math.random()*9.9);
System.out.println( c );

JDBC LOBs (Large Objects)

Auf der Datenbankseite gibt es zwei Typen für besonders große Daten: BLOB und CLOB. »B« steht für Binary und »C« für Character, also steht ein SQL BLOB für beliebig große Binärdaten und ein CLOB für beliebig große Textdaten.

Die LOBs unterscheiden sich von den anderen Datentypen dadurch, dass der Treiber erst dann die Daten überträgt, wenn sie auch angesprochen werden: Wird eine Zeile mit einer Zahl übertragen, so wird der Treiber auch diese Zahl immer mitschicken. Bei den LOBs sieht das anders aus: Intern steckt dort eine Art Verweis (LOCATION), die mitgeschickt wird, aber nicht die Daten selbst. Durch die Kapselung im Treiber fällt das allerdings nicht auf.

Für den BLOB gibt es in JDBC die Schnittstelle Blob und für CLOB Clob. In beiden Fällen können die großen Daten erfragt, aktualisiert und angelegt werden. Außerdem lässt sich die Länge erfragen und lassen sich ab einer bestimmten Position Daten auslesen.

Einen BLOB besorgen

Einem BLOB/CLOB steht genau, wie es für andere Datentypen entsprechende getXXX()-Funktionen gibt, eine getBlob()/getClob()-Funktion zur Verfügung. Der Unterschied besteht nur darin, dass getInt() direkt ein int zurückgibt, während getBlob() nur eine Referenz auf ein Blob-Objekt liefert, über die im zweiten Schritt die Daten zu beziehen sind.

Beispiel: Eine Tabelle Persons weist eine Spalte Image auf, die eine Grafik einer Person speichert. Die Grafik ist in einer Spalte vom Typ BLOB.

ResultSet rs = stmt.executeQuery( "SELECT * FROM Persons" );
rs.first();
Blob data = rs.getBlob( "Image" );

data bezieht sich jetzt auf das Blob-Objekt. Es enthält noch nicht die Daten. Sie lassen sich zum Beispiel über die Methode data.getBinaryStream() beziehen. Damit lässt sich dieser InputStream toll im Konstruktor von ImageIcon() einsetzen, der aus den Daten dann gleich eine Swing-taugliche Grafik konstruiert. Teile des Datenfeldes werden mit byte[] getBytes(start, ende) angefordert. data.length() liefert die Anzahl der Elemente des LOBs.

Nostalgia: Das JavaBeans Development Kit (BDK)

Das BDK ist (war) eine Testumgebung für anzeigeorientierte Beans und Komponenten ohne visuelle Repräsentation wie Datenstrukturen und Container. Es ist nicht mehr im Original, sondern nur noch in der Version unter https://java.net/projects/bean-builder erhältlich.

Die Bean-Box bietet eine Arbeitsfläche, auf der die Komponenten platziert und verbunden werden können. Wird in der Bean-Box eine Komponente auf dem Bildschirm gesetzt, so liest Java aus der Bean die Informationen (die Properties genannt werden) aus und stellt sie dar. Nun können mehrere Beans miteinander verbunden werden, sodass etwa eine Komponente ein Ereignis auslöst, auf das die zweite Komponente reagiert. Alle Aktionen können direkt ohne eine Zeile Programmcode entworfen werden. Wir entwerfen zunächst im Design-Modus die Verbindungen, um sie dann im Laufzeit-Modus zu testen. Damit reiht sich die Bean-Box in die Reihe der grafischen Entwicklungsumgebungen wie etwa IBMs Visual Age ein. Ganz so komfortabel ist die Bean-Box dann aber auch nicht. Sie ist von Sun lediglich zum Testen und Anzeigen von Beans entworfen worden. Dies zeigt sich auch daran, dass beim Verlassen der Umgebung nicht einmal nach dem Speichern gefragt wird.

Die Bean-Box starten

In der BDK-Version 1.1 wechseln wir in das bdk/beanbox/bin-Verzeichnis und rufen die Skripte run.bat (Windows) oder run.sh (Unix) auf. Dann startet die Umgebung.

Eine Beispielsitzung im BDK

Das BDK ist nach dem Start in drei Fenster unterteilt. Dies sind die ToolBox, die Bean-Box und das Eigenschaften-Feld. Das ToolBox-Fenster enthält einige Beispielkomponenten, die wir verwenden können. Die Beispiele werden zur Laufzeit aus dem bdk/jars/-Verzeichnis genommen. Eigene Komponenten können wir als Jar-Dateien einfach dort hineinkopieren. Die eigentliche Bean-Box stellt die Zeichenfläche dar. Sie ist der wichtigste Teil des Pakets, weil auf ihr Komponenten platziert und getestet werden können. Wir wollen dies anhand der Komponente Molecule zeigen. Wird sie in der ToolBox angeklickt, so verwandelt sich der Cursor in einen Positionszeiger. Klicken wir in der Bean-Box auf eine Stelle, wird ein Molekül angezeigt. Es lässt sich verschieben und interaktiv in der Größe ändern. Die Eigenschaften jeder Komponente lassen sich im Property-Sheet ändern. Sie bestimmen das Aussehen und das Verhalten. Ist das Molekül aktiviert, erscheint im Eigenschaften-Dialog eine Auswahlliste mit einigen Molekülen. Eine Auswahl wirkt sich direkt in der Anzeige aus.

Hinweis: Leider können selektierte Komponenten nicht mit der Entfernen-Taste von der Oberfläche beseitigt werden. Hier ist nur der Gang über das Edit-Menü erfolgversprechend.

Das Zusammenspiel der Komponenten lässt sich testen, indem wir den Design-Modus verlassen. Dazu aktivieren wir im Menüpunkt View den ersten Punkt Disable Design Mode (der sich dann in Enable Design Mode umbenennt). Jetzt kann das Molekül mit der Maus verschoben werden.

Verknüpfungen zwischen Komponenten

Die hervorragendste Eigenschaft der grafischen Programmierung ist das visuelle Setzen von Beziehungen zwischen Komponenten. Dabei werden Ereignisse an Methoden anderer Beans gebunden. Wir wollen das mit der Komponente JellyBean und dem Molekül testen. Wenn wir nun mit der Maus auf die JellyBean klicken (also ein Mouse-Event auslösen), dann soll eine bestimmte Methode des Bean-Moleküls aufgerufen werden, sodass es sich auf dem Bildschirm dreht. Wir verbinden hier also einfach eine Ereignisquelle mit einer Ereignisbehandlung einer anderen Komponente. Die Bedienung der Bean-Box ist dabei so intuitiv, dass sie zum Spielen und Ausprobieren einlädt. Wir aktivieren die erste Schaltfläche und wählen den Menüpunkt Edit > Events > mouse > mousePressed.

Jetzt erscheint eine rote Linie, die mit der Maus bewegt wird. Nun klicken wir auf unser Molekül. Daraufhin erscheint eine Liste mit Funktionen, die bei der Mausbewegung ausgelöst werden können. Wir entscheiden uns für rotateOnX.

Beans speichern

In der Bean-Box lässt sich unter dem File-Menü die Bean in ein Jar-Archiv für ein Applet einwickeln. Der Speicherdialog schlägt standardmäßig ein Unterverzeichnis der Bean-Box vor. Zusätzlich zur serialisierten Klasse befinden sich im Archiv noch die vom BDK erstellten Metainformationen, die Quellcodes zu der Bean in einem Extra-Verzeichnis, eine HTML-Datei sowie eine Readme-Datei mit den Informationen darüber, welche Dateien zur Komponente gehören. Die HTML-Datei erhält eine Referenz auf die Klassendatei, sodass ein Applet-Viewer oder ein Browser direkt die Bean darstellen kann. Alte Browser unterstützen das Jar-Format im Applet-Tag der HTML-Datei nicht.

Die kleinste Bohne der Welt

Wir wollen uns nicht lange mit theoretischem Schnickschnack aufhalten – dafür bleibt noch genügend Zeit. Beginnen wir mit einer Komponente, die wir FirstBean nennen wollen.

package com.javatutor.insel.bean;

import java.applet.Applet;
import java.awt.*;

public class FirstBean extends Applet
{
 @Override
 public void paint( Graphics g )
 {
  g.drawString( "Hui; so kurz?", 20, 20 );
 }
}

Zweifellos wird die Kürze dieser Lösung verwundern. Das kommt daher, dass sich die Beans gut in die vorhandene Technologie einfügen und schon viele Komponenten als Beans gebaut sind.

Beginnen wir nun, das schon in der Einführung als einfaches Beispiel für Komponenten identifizierte Applet in unseren Application-Builder einzubauen. Dazu muss die Softwarekomponente in einem speziellen Format vorliegen: in Jar-Dateien.

Jar-Archive für Komponenten

Eine Jar-Datei ist ein Archivformat, das Sun zum Verbund von Klassen und Mediendateien einführte. Nach dem Compilieren der Klasse FirstBean wird die Datei mit einem zusätzlichen Manifest in eine Jar-Datei gepackt. Damit der Application-Builder erkennen kann, dass es sich dabei um eine Bean handelt, erwartet er eine bestimmte Information in einer Manifest-Datei, die automatisch beim Zusammenpacken erzeugt wird. Stünden diese Informationen nicht dabei, könnte der Application-Builder die Bean nicht erkennen.

Wir wollen die Jar-Datei mit dem Dienstprogramm jar von Hand erzeugen. Zunächst legen wir eine Manifest-Datei an, die wir nach der Komponente benennen.

FirstBean.mf

Manifest-Version: 1.0
Name: FirstBean.class
Java-Bean: True

Hinweis: Vor den Doppelpunkten dürfen keine Leerzeichen stehen, sonst erkennt Jar die erste Option nicht und bemängelt den fehlerhaften Header. Leider ist Jar nicht sehr fehlertolerant.

Befinden sich im Jar-Archiv mehrere Klassen, sollten ihre Dateinamen mit Return getrennt sein. Bei Paketnamen ist eine Trennung mit / vorgesehen, nicht mit dem Windows-\.

Im nächsten Schritt packen wir mit jar die Klassen sowie die Manifest-Datei zu einem Archiv zusammen. Hier müssen wir die Optionen cfm nutzen. Merken wir uns die Option im englischen Satz »Create a File with a Manifest«, übersetzt »Lege die Datei mit Manifest an« .

$ jar cfm FirstBean.jar FirstBean.mf FirstBean.class

Für eine genauere Beschreibung des Dienstprogramms jar sei auf Kapitel in der Insel verwiesen.

Wurde das Jar-Archiv erfolgreich erstellt, muss dem Application-Builder die Bean bekannt gemacht werden. Hier unterscheiden sich die Produkte der einzelnen Hersteller voneinander. Im BDK von Sun müssen alle Beans im Unterverzeichnis jars liegen, damit sie das BDK automatisch erkennt. Kopieren wir unsere Bean dort hinein. Unter Windows schreiben wir:

$ copy FirstBean.jar c:\bdk\jars

Wenn wir jetzt die Bean-Box starten, erscheint unsere Komponente mit in der Liste. Im jars-Verzeichnis stehen auch die anderen Beans.

Tipp: Eine etwas schnellere Vorgehensweise besteht darin, die Jar-Datei nicht in das jars-Verzeichnis, sondern in ein eigenes Verzeichnis zu legen. Das hat den Vorteil, dass die Bean-Box schneller startet, weil sie dann nicht die zusätzliche Bean auswerten muss, was immer Zeit kostet. Wenn wir Beans nicht benötigen, können wir sie auch verschieben oder löschen, was die Ladezeit zusätzlich erhöht. Damit in diesem Fall das eigene Jar-Archiv eingebunden wird, laden wir es unter dem Menüpunkt File > Load Jar.

Wenn wir wiederholt Beans in ein Archiv packen und keine integrierte Entwicklungsumgebung diese Arbeit erledigt, kann uns ein kleines Skript helfen. Ein Beispiel für Windows wäre:

a.bat

javac zzz.java
jar cfm zzz.jar zzz.mf zzz.class
copy zzz.jar c:\bdk\jars

Bean-Eigenschaften anpassen

In einem visuellen Entwicklungswerkzeug lassen sich alle ausgezeichneten Eigenschaften einer Bean anpassen. Dazu bietet die Umgebung oft ein zweigeteiltes Fenster an. Auf der einen Seite befinden sich die Eigenschaften und auf der anderen Seite die Editoren für jede Eigenschaft. Die folgende Abbildung zeigt einige Properties und deren Anzeigen.

Haben wir zum Beispiel als Eigenschaft eine Zeichenkette (wie der angezeigte Text eines Label-Objekts), können wir diese einfach in das Feld eintragen. Mit einem eigenen Property-Editor sind wir jedoch nicht auf die einfachen Datentypen beschränkt. Was ist, wenn etwa eine Komponente die Auswahl zwischen DM und Euro anbieten will? Wir können dem Benutzer nicht zumuten, dies in Zahlen einzugeben. Auch bei einem Label gibt es zum Beispiel die Möglichkeit, einen Cursor aus einer Liste auszuwählen.

Für ungewöhnliche Eigenschaften können wir einen eigenen Property-Editor definieren. Dazu kann der Editor den Anzeigebereich als Grafikfläche beschreiben.

Customizer

Reicht auch der Editor nicht aus, zum Beispiel bei einer Farbe, die wir gerne aus einem Farbkreis auswählen wollen, lässt sich zudem ein Customizer definieren, der noch einen Schritt weiter geht, denn für einen Customizer ist ein eigenes Fenster vorgesehen. Er soll mit einer einfachen Benutzerführung den internen Zustand ändern. IBM sieht für seine Beans zum Beispiel ein Hilfe-Feld vor. In WebGain wird dieser Customizer durch eine Schaltfläche mit drei Punkten angezeigt.

Property-Editoren

Für jede sichtbare Eigenschaft einer Bean gibt es einen Property-Editor, der den Wert darstellen und ihn bei einer veränderbaren Eigenschaft auch editieren kann. Bekommt die Eigenschaft den Fokus, wird automatisch der passende Editor ausgewählt. Für alle primitiven Datentypen gibt es standardmäßig Editoren, und zusätzlich kommen Auswahlmöglichkeiten für String, Color und Font hinzu. Ein Editor gilt für jeweils eine Property. Die Editoren werden an einer zentralen Stelle, dem Property-Editor-Manager, registriert und dann beim Fokus ausgewählt und aktiviert. Das ist Aufgabe der Entwicklungsumgebung.

Damit die Einstellmöglichkeit nicht auf diese einfachen Typen beschränkt bleibt, können wir für eine Eigenschaft einen eigenen Property-Editor konstruieren. Der kann eine ganz einfache Textzeile darstellen oder auch eine eindrucksvolle Multimedia-Komponente. Der Editor übernimmt die Anzeige und ist gleichzeitig das Modell (also gibt es keine Trennung von Daten und Visualisierung an dieser Stelle). Der Wert ist als Object in unserem Editor gespeichert. Außerdem muss der Editor die Schnittstelle java.beans.PropertyEditor implementieren oder alternativ die Klasse java.beans.PropertyEditorSupport erweitern, was meist einfacher ist.

BeanInfo

Durch Introspection/Reflection existiert ein leistungsfähiger Mechanismus, um die Eigenschaften und Ereignisse zur Laufzeit auszulesen. In der Regel nimmt die Entwicklungsumgebung dafür Methoden, die sich an die Namenskonvention halten. Es gibt aber noch eine zweite Möglichkeit, und die lässt sich über eine Bean-Information-Klasse nutzen. Sie bietet folgende Funktionalität:

  • explizites Auflisten der freigegebenen Leistungen, die nach außen sichtbar sein sollen
  • Zuordnung eines mit der Bean verwendeten Icons
  • Anmeldung einer Customizer-Klasse

Sind die freigegebenen Leistungen aufgelistet, wird damit eine Untersuchung der Bean-Klassen auf die Namensgebung verhindert. Es gibt daher zur Freigabe der Eigenschaften und Ereignisse spezielle Methoden, die von uns gefüllt werden, indem wir jede Eigenschaft auflisten. Jede Methode nutzt zur Beschreibung der Leistungen so genannte Deskriptoren. Gibt es keinen Deskriptor, wird die jeweilige Eigenschaft, Methode oder das Ereignis nicht veröffentlicht. Gibt die Anfrage-Methode aus der Bean-Information-Klasse null zurück, wird für die jeweilige Eigenschaft/Event/Methode Reflection genutzt. Der Java-Code für dieses Doktor-Spielchen liegt in java.beans.Introspection. Ein kleines Beispiel, das alle get-Methoden der Klasse Point ausgibt:

BeanInfo beanInfo = Introspector.getBeanInfo( Point.class );
for ( PropertyDescriptor p : beanInfo.getPropertyDescriptors() )
System.out.println( p.getReadMethod() );

Die Ausgabe liefert vier Zeilen:

public final native java.lang.Class java.lang.Object.getClass()
public java.awt.Point java.awt.Point.getLocation()
public double java.awt.Point.getX()
public double java.awt.Point.getY()

Beliebte Fehler

Das Programmieren von Beans ist zwar einfach, doch schleichen sich immer wieder Fehler ein. Dieser Abschnitt soll auflisten, welche Fehler für eine Bean letal sind, sodass sie von einer Umgebung nicht erkannt werden kann.

  • Die Klasse muss öffentlich (public) und darf nicht abstrakt sein. Abstrakte Klassen können keine Exemplare bilden, was für eine Bean-Box aber unumgänglich ist.
  • Die Klasse muss vom Klassenlader ohne Folgefehler geladen werden können. Der Vorgang wird während des Ladens durch Fehler im static-Block oder durch fehlende Klassen möglicherweise abgebrochen.
  • Die Bean muss einen öffentlichen Standard-Konstruktor anbieten.
  • Die Klasse muss Serializable implementieren, muss also serialisierbar sein. Das hat zur Konsequenz, dass alle Attribute, die nicht transient sind, ebenso serialisierbar sein müssen. Eine Verletzung dieser Regel liegt beispielsweise vor, wenn eine grafische Komponente eine Referenz auf ein Image-Objekt hält, denn Image-Objekte sind nicht serialisierbar. Wir müssen daher entweder ImageIcon nutzen oder eigene Methoden wie writeObject(), readObject() implementieren. Wenn schon eine Oberklasse serialisierbar ist, dann muss unsere Klasse nicht noch einmal Serializable implementieren, weil wir dann selbst automatisch instanceof Serializable durch die Oberklasse sind. Es bietet sich jedoch zum Zweck der Lesbarkeit an, implements Serializable zu schreiben, damit das auf den ersten Blick sichtbar ist.

Pack200-Format

Jar-Dateien enthalten in der Regel eine Mischung aus Klassendateien und Ressourcen. Die Jar-Archive sind normale Zip-Archive und somit steht der bekannte Zip-Algorithmus hinter der Kompression. Das liefert eine befriedigende Kompression über alle Formate, bietet aber keine besonderen Kompressionsverfahren für gewissen Dateitypen. Eine optimale Kompression ist aber gerade bei Jar-Bezug über das Internet wünschenswert. Während JPG-Dateien von Zip auch nicht besser komprimiert werden können, verkürzen sich Klassendateien schon etwas, erreichen mit Zip aber immer noch nicht das Maximum einer möglichen Komprimierung. Das liegt daran, dass Zip eine .class-Datei wie normalen Binärcode betrachtet und den Aufbau der Java-Klassendateien eben nicht kennt. Hier kommt ein spezielles Format zum Zuge, was den Aufbau von Klassendateien berücksichtigt, das Pack200-Format. Neben dem Dienstprogramm jar bietet das JDK im bin-Verzeichnis auch entsprechende Kommandozeilenwerkzeuge pack200 und unpack200 zum Komprimieren und eben Dekomprimieren.

Beispiel: Die Eclipse-Installation bringt genug Jar-Dateien mit, an denen Pack200 ausprobiert werden kann. Unter eclipse\plugins\org.apache.ant_1.8.4.v201303080030\lib nehmen wir uns ant.jar vor, ein Jar-Archiv von 1.941.731 Bytes.

$ pack200.exe -O ant.jar.pack.gz ant.jar

Der Schalter -O optimiert noch etwas und am Ende steht eine Datei ant.jar.pack.gz von 502.721 Bytes, also eine fast viermal kleinere Version.

Das pack200-Tool arbeitet im Prinzip so, dass es eine Jar-Datei nimmt, die Einträge umsortiert, Redundanzen erkennt und in einer Art neue spezielle Mega-Klassendatei verpackt[1], die dann mit dem Standard-GZip-Verfahren komprimiert wird.[2] Pack200- Dateien tragen in der Regel die Dateiendung *.jar.pack.gz und ein Web-Server serviert sie unter dem MIME-Typ application/x-java-pack200. Bezieht ein Programm über Java Web Start oder ein Java Plug-in die Dateien, so werden diese direkt ausgepackt und wie ein Jar eingebunden.


[1] Details und Anwendung zum Verfahren gibt Die Oracle-Seite http://download.java.net/jdk8/docs/technotes/guides/jweb/networking/compression_formats.html#pack200_compression. Intern ist das Verfahren komplex und die Beschreibung über die internen Vorgänge lang, siehe http://docs.oracle.com/javase/7/docs/technotes/guides/pack200/pack-spec.html.

[2] Während jede normale Klassen-Datei mit der Hex-Kennung 0xCAFEBABE beginnt, beginnt der Container der Pack-Datei mit 0xCAFED00D (Cafe Dude) – sympathisch.

Wiederholbare Annotationen: @Repeatable

Normalerweise nutzen Entwickler Annotationen wie einmalige Modifizierer, und es ergibt keinen Sinn, etwa @Override @Override String toString() zu schreiben, genauso wenig wie es keinen Sinn ergibt final static final double PI zu deklarieren. Doch da es durchaus Meta-Daten gibt, die mehrmals auftauchen können, gibt es seit Java 8 eine Erweiterung, dass Annotationen wiederholt werden dürfen. Allerdings müssen die Annotationstypen dieser wiederholbaren Annotationen selbst mit einer besonderen Meta-Annotation @Repeatable ausgezeichnet werden. Damit ist es aber noch nicht getan, denn @Repeatable muss als Element einen Typ bekommen, der den Container angibt.

Beispiel: Der Annotationstyp für Autoren kann so aussehen:

public @interface Author { String name(); }

Soll nun die Annotation mehrfach verwendet werden, ist die Meta-Annotation nötig und mit ihr die Angabe eines Containers:

@Repeatable( Authors.class )
public @interface Author { String name(); }

Der Container ist selbst ein Annotationstyp mit einem Feld als Element. Der Typ des Feldes ist exakt der wiederholbare Annotationstyp.

public @interface Authors {
  Autor[] value;
}

Ohne @Repeatable am Annotationstyp wird eine mehrmalige Verwendung einer Annotation zu einem Compilerfehler führen. Im Java SE 8 gibt es bisher keine Verwendung dieses Annotationstyps, also auch keine wiederholbaren Annotationen.