Quality First SoftwareQuality First Software
qflib
Beispiele
EinführungPackagesBeispieleAPI DokumentationProjekte
Auf dieser Seite
Package de.qfs.lib.log
Erzeugen von Logmeldungen
Konfiguration des Logging Systems
Einstellen der level für die Logger
Einbinden des Logfensters von qflog
Logging und Applets
Ein Wiedersehen mit dem "Hello world" Applet
Package de.qfs.lib.util
Der ArgsParser
Logging System und Kommandozeile
Lokalisierung mit dem MapResourceBundle
Package de.qfs.lib.gui
Eine Tabelle mit automatischer Sortierung
English version
Package de.qfs.lib.log
Creating log messages
Configuration of the logging system
Setting the level for a Logger
Embedding the log window from qflog
Logging and applets
The "Hello world" applet revisited
Package de.qfs.lib.util
The ArgsParser
The logging system and the command line
Localization via a MapResourceBundle
Package de.qfs.lib.gui
Creating a sorted table
Package de.qfs.lib.log

Auch wenn die API Referenz zum de.qfs.lib.log Package komplex wirkt, ist sein Einsatz sehr einfach. Er reduziert sich auf folgende drei Punkte:

  • Das Erzeugen von Log Meldungen an beliebigen Stellen im Programm.
  • Die Konfiguration des Logging Systems beim Programmstart.
  • Die Steuerung des Logging Systems während des Programmlaufs.

Beginnen wir mit einigen Grundlagen: Jeder Logmeldung ist ein Level zugeordnet, der ein Maß für ihre Wichtigkeit darstellt. Es gibt fünf Stufen mit jeweils einer Unterstufe für Details, für die folgende Konstanten in der Log Klasse definiert sind:

ERR, ERRDETAIL Fehlermeldungen
WRN, WRNDETAIL Warnungen
MSG, MSGDETAIL Allgemeine Meldungen
MTD, MTDDETAIL Methodenaufrufe
DBG, DBGDETAIL Debugging

Neben dem Level und der eigentlichen Mitteilung enthält eine Logmeldung ausserdem einen Zeitstempel, den laufenden Thread sowie die absendende Klasse und Methode. Die Ausgabe einer Logmeldung hat folgendes Format:

Level (Uhrzeit) Thread Klasse.Methode: Mitteilung

Uhrzeit, Thread und Klasse werden automatisch generiert. Leider gibt es in Java keine Möglichkeit die umgebende Methode zu ermitteln, so dass diese immer von Hand angegeben werden muß.

Erzeugen von Logmeldungen

Kommen wir zum eigentlichen Erzeugen einer Meldung. Dies sollte immer mit Hilfe eines Loggers geschehen. Jede Klasse deren Methoden Logmeldungen erzeugen, sollte ihren eigenen Logger verwenden:

 1  package mypackage;
 2
 3  import de.qfs.lib.log.Log;
 4  import de.qfs.lib.log.Logger;
 5
 6  public class MyClass {
 7
 8      private final static Logger logger = new Logger (MyClass.class);
 9
10      public void myMethod(int var)
11      {
12          if (logger.level >= Log.MTD) {
13                logger.log(Log.MTD, "myMethod(int)",
14                         logger.level < Log.MTDDETAIL ? "" :
15                         "var: " + var);
16          }
17
18          if (logger.level >= Log.DBG) {
19              logger.log(Log.DBG, "myMethod(int)", "Debug message");
20          }
21
22          try {
23              // ...
24          } catch (UnimportantException ex) {
25              if (logger.level >= Log.MSG) {
26                  logger.log(Log.MSG, "myMethod(int)", ex);
27              }
28          } catch (Exception ex) {
29              if (logger.level >= Log.ERR) {
30                  logger.log("myMethod(int)", ex);
31              }
32          }
33      }
34  }
Beispiel 1: Verwendung eines Loggers

Der Selbstbezug auf die Klasse beim Erstellen des Loggers (Beispiel 1, Zeile 8) ist für die Angabe der Klasse in den Meldungen notwendig. Ausserdem erlaubt er die Kontrolle des level Attributs auf Klassen- und Packageebene. Dazu später mehr.

Beispiel 1 zeigt drei typische Aufrufe der log Methode des Loggers. Der einfachste Fall ist die Ausgabe einer Debugmeldung in Zeile 19. Parameter sind der Level der Meldung, die aktuelle Methode und die eigentliche Mitteilung.

Dieser Aufruf ist durch die Abfrage von logger.level (Zeile 18) geschützt. Dadurch wird die Logmeldung nur erzeugt und verarbeitet, wenn der level des Loggers mindestens den Wert Log.DBG hat. Dies ist der wirksamste Schutz gegen Performanceeinbußen durch Logging, da im negativen Fall weder die log Methode aufgerufen, noch die Parameter des Aufrufs zusammengesetzt werden. Bei komplexeren Meldungen hat aber gerade der Zusammenbau der Mitteilung den größten Einfluß. Der Mechanismus, über den die Erzeugung von Meldungen zur Laufzeit des Programms gesteuert wird, erfordert zwingend diese Konstruktion.

Im Prinzip ist die Ausgabe des Methodenaufrufs (Zeilen 12-16) völlig identisch. Sie enthält jedoch einen zusätzlichen Kniff: Der Wert des Parameters var wird nur ausgegeben, wenn der level des Loggers mindestens Log.MTDDETAIL beträgt. Ist sein Wert genau Log.MTD, wird die Meldung zwar erzeugt, aber ohne die Parameter. Dieses Konstrukt ist nicht zwingend, ermöglicht es aber, Methodenaufrufe zu verfolgen, ohne den hohen Performancepreis für den Zusammenbau des Mitteilungsstrings zu bezahlen, ausser wenn die Information über die Parameter wirklich benötigt wird.

Exceptions müssen leider besonders häufig protokolliert werden. Zeile 30 zeigt die einfache Variante der Ausgabe. Hierbei wird die Exception selbst mit Level Log.ERR ausgegeben, ihr Stacktrace mit Level Log.ERRDETAIL. Alternativ kann auch die Form von Zeile 26 verwendet werden. Damit wird die Exception mit Level Log.MSG, ihr Stacktrace mit Log.MSGDETAIL ausgegeben.

Konfiguration des Logging Systems

Betrachten wir als nächstes die Konfiguration des Logging Systems. Was passiert eigentlich mit den generierten Meldungen? Es würde an dieser Stelle zu weit führen, die mehrstufigen Filter im einzelnen zu erläutern. Details hierzu finden Sie in der API Referenz zur Log Klasse.

Mit der Version 0.98 der qflib wurde im de.qfs.lib.util Package die Klasse LogSetup eingeführt, die die Initialisierung aller Bestandteile des Logging Systems über die Kommandozeile drastisch vereinfacht. Sie wird in einem Beispiel weiter unten vorgestellt.

Die folgenden Beispiele illustrieren die typischen Anwendungsfälle und erläutern die Grundlagen, auf denen LogSetup aufbaut:

 1  package mypackage;
 2
 3  import java.io.IOException;
 4
 5  import de.qfs.lib.log.Log;
 6  import de.qfs.lib.log.FileLogWriter;
 7  import de.qfs.lib.logrmi.RemoteLogWriter;
 8
 9  public class MyMainClass {
10
11      public static void main(String[] args)
12      {
13          Log.setOutputLevel(Log.WRNDETAIL);
14
15          try {
16              FileLogWriter.logToFile("myApp", "myapp.log", FileLogWriter.MODE_CREATE, true);
17          } catch (IOException ex) {
18              // ...
19          }
20
21          try {
22              RemoteLogWriter.logRemote("qflog", "myApp", -1, false, true);
23          } catch (Exception ex) {
24              // ...
25          }
26
27          // ...
28
29          // get ready to quit
30          Log.setQueueing(false);
31          FileLogWriter.stopLogging();
32          RemoteLogWriter.stopLogging();
33
34          System.exit(0);
35      }
36  }
Beispiel 2: Konfiguration des Logging Systems

Am einfachsten ist die Ausgabe auf System.err. Standardmäßig werden alle erzeugten Meldungen dorthin ausgegeben. Wird eine Logdatei oder der Logserver qflog verwendet, kann es sinnvoll sein, die Ausgaben auf System.err z.B. auf Fehler und Warnungen zu beschränken, wie in Beispiel 2, Zeile 13.

Das Erstellen einer Logdatei geschieht am einfachsten mittels der Methode FileLogWriter.logToFile, die alle Details übernimmt (Zeile 16). Die Details entnehmen Sie bitte der API Referenz zu FileLogWriter.logToFile.

Zeile 22 zeigt, wie die Verbindung mit dem Logserver qflog hergestellt werden kann. Hierzu finden Sie die Details in der API Referenz zu RemoteLogWriter.logRemote.

Vor dem Beenden des Programms ist es sinnvoll, das Logging sauber zu beenden (Zeilen 30-32). Zunächst wird mit Hilfe von Log.setQueueing(false) sichergestellt, dass alle noch gepufferten Meldungen weitergeleitet und verarbeitet werden. Anschliessend wird die Logdatei geschlossen und qflog das Ende der Verbindung mitgeteilt.

Einstellen der level für die Logger

Wenn Sie das Logging System wie im Beispiel 2 konfigurieren, werden Sie feststellen, dass nicht nur auf System.err, sondern auch in Ihrer Logdatei keine Meldungen mit einem höheren Level als Log.WRNDETAIL ankommen. Dies liegt daran, dass der oben erwähnte Mechanismus zur Steuerung der Generierung von Meldungen standardmäßig so eigestellt ist, dass alle Klassen nur bis zu diesem Level Meldungen erzeugen. Das läßt sich ändern:

 1  package mypackage;
 2
 3  import de.qfs.lib.log.Log;
 4  import de.qfs.lib.log.Logger;
 5  import de.qfs.lib.util.ArgsParser;
 6  import de.qfs.lib.util.MalformedArgumentListException;
 7
 8  public class MyMainClass {
 9
10      public static void main(String[] args)
11      {
12          Logger.setDefaultLogLevel(Log.MSGDETAIL);
13          Logger.setLogLevel("de.qfs.", Log.ERRDETAIL);
14          Logger.setLogLevel("mypackage.", Log.MTD);
15          Logger.setLogLevel("mypackage.NewClass", Log.DBGDETAIL);
16
17          ArgsParser parser = new ArgsParser (new String[] {"+log-*"});
18          try {
19              parser.parse(args);
20              Logger.setLogLevels(parser.getOptions());
21          } catch (MalformedArgumentListException ex) {
22              // ...
23          }
24      }
25  }
Beispiel 3: Einstellen der level für die Logger

Beispiel 3 erläutert zwei der Möglichkeiten, die Generierung der Meldungen zu konfigurieren: Durch direkte Angabe der Level im Quellcode und über die Kommandozeile. In Zeile 12 wird zunächst der Level für alle Klassen auf Log.MSGDETAIL angehoben. Zeile 13 beschränkt die Meldungen aus der qflib auf Fehlermeldungen. Zeilen 14 und 15 erlauben den Klassen aus mypackage Meldungen bis zum Level Log.MTD, für die Klasse mypackage.NewClass sogar bis Log.DBGDETAIL. Wichtig ist der Punkt ('.') am Ende der Packageangabe in Zeilen 13 und 14. Die Reihenfolge dieser Aufrufe ist übrigens völlig unerheblich.

Diese Methode ist wenig elegant, da Änderungen an den Einstellungen nur über eine Anpassung des Programmcodes und Neucompilierung erfolgen können. Besser ist es, die Einstellungen in eine Property Datei zu stellen, diese beim Programmstart zu laden und die Properties direkt an Logger.setLogLevels zu übergeben.

Des weiteren kann der ArgsParser aus dem de.qfs.lib.util Package der qflib verwendet werden, um die Einstellungen beim Aufruf des Programms über die Kommandozeile vorzunehmen. Zeilen 17-23 demonstrieren den Einsatz des ArgsParsers. Den zu Zeilen 12-15 identischen Effekt erzielt man dann mit einem Aufruf von

java MyMainClass -log-defaults:6 -log-de.qfs.:2 -log-mypackage.:7 -log-mypackage.NewClass:10

Am komfortabelsten ist der Einsatz des Logservers qflog, mit dessen Hilfe die Einstellungen über eine grafische Oberfläche selbst noch zur Laufzeit des Programms vorgenommen werden können. Was von Ihrer Seite für seinen Einsatz zu tun ist, wurde bereits im Beispiel 2 gezeigt. Genaueres zu Installation und Bedienung von qflog finden Sie im qflog Handbuch.

Einbinden des Logfensters von qflog

Das Logfenster von qflog läßt sich auch direkt in Ihr Programm integrieren, und zwar sehr einfach, wie das folgende Beispiel zeigt:

 1  package mypackage;
 2
 3  import de.qfs.lib.log.Log;
 4  import de.qfs.apps.qflog.logview.LogView;
 5
 6  public class MyMainClass {
 7
 8      public static void main(String[] args)
 9      {
10          Log.setOutputLevel(Log.WRNDETAIL);
11
12  	    LogView logview = LogView.logToView("myApp");
13  	    logview.getFrame().show();
14
15          // ...
16
17          // get ready to quit
18          Log.setQueueing(false);
19
20          System.exit(0);
21      }
22  }
Beispiel 4: Einbinden der LogView Komponente

In Zeile 12 wird die LogView Komponente initialisiert und damit ist auch schon alles getan. Das Öffnen des Logfensters wie in Zeile 13 werden Sie in einer echten Anwendung vermutlich nicht generell beim Programmstart durchführen wollen, sondern z.B. von einem Menü aus aufrufen.

Logging und Applets

Ein Applet zu schreiben, das in allen gängigen Browsern läuft ist ungefähr so schwierig, wie eine interessante Webseite zu designen, die überall gleich aussieht. Es gibt einige versteckte Inkompatibilitäten zwischen den VM Implementationen im Netscape Navigator, Microsofts Internet Explorer und dem Java Plugin von SUN, das zudem noch in Versionen von 1.1 bis 1.3 verfügbar ist.

Ein weiteres Problem ist die Sandbox, die die Möglichkeiten eines Applets aus Sicherheitsgründen einschränkt. Ein signiertes Applet kann zwar aus der Sandbox ausbrechen, aber das Signieren ist nicht gerade einfach und natürlich ist das Vorgehen für jede VM anders.

Ein Wiedersehen mit dem "Hello world" Applet

All das macht das Schreiben und Debuggen von Applets nicht einfacher, so dass Logging eine große Hilfe wäre. Leider litt auch die qflib unter diesen Problemen (und leidet z.T. immer noch), aber wir haben mit Version 0.97.0 versucht, Lösungen zu finden.

Das größte Problem bestand darin, dass Microsoft ihren Internet Explorer bis vor kurzem ohne RMI Unterstützung ausgeliefert haben. Da es eine indirekte RMI-Abhängigkeit in der Logger Klasse gab, war es nicht möglich, das de.qfs.lib.log Package in einem Applet zu nutzen, das im Internet Explorer laufen sollte, selbst wenn man Logmeldungen nur auf die Java Konsole ausgeben wollte. In Version 0.97.0 sind daher alle RMI-abhängigen Klassen aus dem de.qfs.lib.log Package entfernt worden.

Trotzdem sind die RMI Klassen nötig um Verbindung zu qflog aufnehmen zu können. Zum Glück stellen Microsoft wenigstens ein Paket zum Herunterladen auf ihrer Java Resources Seite zur Verfügung, mit dessen Hilfe sich die RMI Funktionalität im Internet Explorer nachrüsten läßt. Es reicht aus, dieses Paket auf dem Entwicklungsrechner zu installieren, für den späteren Einsatz eines Applets ist es nicht erforderlich.

Das nächste Problem unter dem qflib leidet ist die Einschränkung der Kommunikation über das Netzwerk durch die Sandbox. Ein unsigniertes Applet darf lediglich Verbindungen zu dem Rechner aufnehmen, von dem es heruntergeladen wurde und es darf nicht selbst auf Verbindungen von aussen warten. Dadurch muss qflog auf dem Rechner gestartet werden, auf dem der Webserver für die Applets liegt und RMI Callbacks, die für die Kontrolle der Level der Logger im Applet nötig wären, funktionieren nicht. Letzteres gilt allerdings nicht für den Internet Explorer, sofern die RMI Klassen installiert sind, der einen Workaround für diesen Zweck implementiert hat. Trotzdem planen wir für die Zukunft einen alternativen Polling Mechanismus, der nicht mehr von Callbacks abhängig sein wird.

Es folgt ein Beispiel Applet, das alles Nötige beinhaltet, um Logging wenigstens so weit wie möglich zu nutzen:

  1 import java.applet.Applet;
  2 import java.awt.Graphics;
  3 import java.util.StringTokenizer;
  4
  5 import de.qfs.lib.log.Log;
  6 import de.qfs.lib.log.Logger;
  7
  8 // do NOT import any RMI dependent stuff, otherwise the applet won't initialize
  9 // import de.qfs.lib.logrmi.RemoteLogFilter;
 10
 11 public class HelloWorldApplet
 12     extends Applet
 13 {
 14     private final static Logger logger = new Logger (HelloWorldApplet.class);
 15
 16     private final static String[][] pInfo = {
 17         {"outputlevel", "string", "log level for Java console"},
 18         {"loglevels",   "string", "log levels for the Loggers"}
 19     };
 20
 21     public void init()
 22     {
 23         String outLevel = getParameter("outputlevel");
 24         System.err.println("outLevel: " + outLevel);
 25         if (outLevel != null) {
 26             try {
 27                 Log.setOutputLevel(Integer.parseInt(outLevel));
 28             } catch (NumberFormatException ex) {
 29             }
 30         }
 31
 32         String logLevels = getParameter("loglevels");
 33         System.err.println("logLevels: " + logLevels);
 34         if (logLevels != null) {
 35             setLogLevels(logLevels);
 36         }
 37
 38         String host = getCodeBase().getHost();
 39         try {
 40             de.qfs.lib.logrmi.RemoteLogFilter.logRemote("//" + host + "/qflog", "applet");
 41         } catch (Throwable ex) {
 42             // ignore
 43         }
 44
 45         if (logger.level >= Log.MTD) {
 46             logger.log(Log.MTD, "init()", "");
 47         }
 48     }
 49
 50     public void destroy()
 51     {
 52         if (logger.level >= Log.MTD) {
 53             logger.log(Log.MTD, "destroy()", "");
 54         }
 55         Log.setQueueing(false);
 56         try {
 57             de.qfs.lib.logrmi.RemoteLogFilter.stopLogging();
 58         } catch (Throwable ex) {
 59             // ignore
 60         }
 61     }
 62
 63     public void paint(Graphics g)
 64     {
 65         if (logger.level >= Log.MTD) {
 66             logger.log(Log.MTD, "paint(Graphics)",
 67                        logger.level < Log.MTDDETAIL ? "" :
 68                        "g: " + g);
 69         }
 70         System.err.println("paint");
 71         g.drawString("Hello world!", 50, 25);
 72     }
 73
 74     public String[][] getParameterInfo()
 75     {
 76         return pInfo;
 77     }
 78
 79     private void setLogLevels(String levels)
 80     {
 81         StringTokenizer st = new StringTokenizer (levels, "=,");
 82         while(st.hasMoreTokens()) {
 83             String name = st.nextToken();
 84             System.err.println("name: " + name);
 85             if (st.hasMoreTokens()) {
 86                 String level = st.nextToken();
 87                 System.err.println("level: " + level);
 88                 try {
 89                     int lvl = Integer.parseInt(level);
 90                     System.err.println("lvl: " + lvl);
 91                     if (name.equals("default")) {
 92                         Logger.setDefaultLogLevel(lvl);
 93                     } else {
 94                         Logger.setLogLevel(name, lvl);
 95                     }
 96                 } catch (NumberFormatException ex) {
 97                 }
 98             }
 99         }
100     }
101 }
Beispiel 1: "Hello world" Applet

Wie Sie in Zeile 9 sehen, dürfen Sie keine RMI-abhängigen Klassen direkt importieren, da sonst die Initailisierung des Applets mit einer ClassNotFoundException scheitert, wenn das Applet in einem Internet Explorer ohne RMI läuft. Stattdessen müssen Sie beim Aufruf den vollen Klassennamen wie in den Zeilen 40 und 57 verwenden. Dadurch kann die ClassNotFoundException abgefangen und ohne Gefahr ignoriert werden.

Der Rest des Applets ist ziemlich offensichtlich und enthält keine neuen Konzepte. Ein Blick auf die Zeilen 32-36 und die setLogLevels Method ab Zeile 79 kann dennoch ganz interessant sein. Sie demonstrieren eine Möglichkeit, wie man den Ausgabelevel für die Java Konsole und die initialen Level der Logger mittels Applet Parametern festlegen kann. Das folgende HTML Beispiel macht hiervon gebrauch (Zeilen 11-12):

 1  <html>
 2    <head>
 3      <title> Hello world applet </title>
 4    </head>
 5    <body>
 6      Here is the output of my program:
 7      <applet code="HelloWorldApplet.class"
 8              codebase="Your codebase here"
 9              archive="qflib_11.jar,collections.jar"
10              width="150" height="25">
11        <param name="outputlevel" value="4">
12        <param name="loglevels" value="default=5,HelloWorldApplet=8,de.qfs.=2">
13      </applet>
14    </body>
15  </html>
Beispiel 2: HTML Seite für das "Hello world" Applet
Package de.qfs.lib.util

Im Folgenden wollen wir den Einsatz einiger wichtiger Klassen aus dem de.qfs.lib.util Package anhand von Beispielen erläutern.

Kommandozeilenoptionen auswerten mit dem ArgsParser

Eine immer wiederkehrende Aufgabenstellung ist das Auswerten von Optionen, die einem Programm auf der Kommandozeile übergeben werden. Mit dem ArgsParser ist es sehr einfach möglich, festzulegen, welche Optionen zugelassen sind und welche davon Parameter erlauben oder benötigen. Nach der Auswertung stellt der ArgsParser die Optionen in einer Hashtable zur Verfügung.

Darüber hinaus ist es möglich, weitere Optionen aus einer Property Datei zu lesen, mehrmaliges Auftreten der gleichen Option mit verschiedenen Parametern auszuwerten und beliebige ähnliche Optionen anhand eines simplen Wildcard Mechnismus zuzulassen.

Das folgende Beispiel zeigt den Einsatz eines ArgsParsers für ein Programm, das einen Aufruf der Form

java MyMainClass [-version] [-dir <Verzeichnis>] [-option <opt>]* [-log-<name> <level>]* <file>

erwartet, d.h. mit den Optionen version, dir und option, wobei dir und option Parameter erwarten und option mehrfach auftreten darf. Darüber hinaus soll die Konfiguration des Logging Systems über Optionen, die mit log- beginnen möglich sein (siehe auch Beispiel 3 des de.qfs.lib.log Packages). Nach den Optionen wird die Angabe eines Dateinamens erwartet.

 1  package mypackage;
 2
 3  import java.util.Hashtable;
 4
 5  import de.qfs.lib.util.ArgsParser;
 6  import de.qfs.lib.util.MalformedArgumentListException;
 7
 8  public class MyMainClass {
 9
10      public static void main(String[] args)
11      {
12          ArgsParser parser = new ArgsParser (new String[] {
13              "-version",
14              "+dir",
15              "m+option",
16              "+log-*"
17          });
18
19          String filename;
20          try {
21              int pos = parser.parse(args);
22              if (pos >= args.length) {
23                  // Missing file argument, print error and exit
24              }
25              filename = args[pos];
26          } catch (MalformedArgumentListException ex) {
27              // Bad options, print error and exit
28          }
29          Hashtable options = parser.getOptions();
30      }
31  }
Beispiel 1: Kommandozeile mit Hilfe eines ArgsParsers auswerten

Der Aufruf

java MyMainClass -dir /home -option opt1 -option opt2 myfile

führt im Beispiel 1 dazu, dass filename den Wert "myfile" annimmt und die Hashtable options wie folgt aufgebaut ist:

Key Value
"dir" "/home"
"option" ["opt1", "opt2"]

Konfigurieren des Logging Systems über die Kommandozeile

Aufbauend auf dem ArgsParser wurde mit Version 0.98 die neue Klasse LogSetup eingeführt, die diverse Argumente auswertet und das Logging entsprechend konfiguriert. So ist es z.B. möglich, diverse Parameter der Log Klasse anzugeben (Ausgabelevel etc.), ebenso wie die Level der Logger. Ausgaben lassen sich in ein oder mehrere Dateien umleiten, die Verbindung mit dem Logserver qflog läßt sich herstellen und sogar die LogView Komponente des Logservers direkt in die Applikation einbinden.

Die genauen Angaben über die Optionen und deren Bedeutung entnehmen Sie bitte der API Referenz zu LogSetup. Das folgende Beispiel zeigt, wie einfach es ist, LogSetup in Ihre Applikation einzubinden:

 1  package mypackage;
 2
 3  import de.qfs.lib.util.ArgsParser;
 4  import de.qfs.lib.util.LogSetup;
 5  import de.qfs.lib.util.MalformedArgumentListException;
 6
 7  public class MyMainClass {
 8
 9      public static void main(String[] args)
10      {
11          ArgsParser parser = new ArgsParser (new String[] {
12              "-version",
13              "+dir",
14              "m+option"
15          });
16
17          // Add the LogSetup's options to the parser
18          LogSetup.instance().addOptions(parser);
19          try {
20              int pos = parser.parse(args);
21          } catch (MalformedArgumentListException ex) {
22              // Bad options, print error and exit
23          }
24          // Evaluate the logging options
25          LogSetup.instance().setupLogging("myApp", parser);
26
27          //...
28
29          // Cleanup when you are done
30          LogSetup.instance().stopLogging();
31      }
32  }
Beispiel 2: Einbinden der LogSetup Klasse

Lokalisierung mit dem MapResourceBundle

Java bietet mit dem ResourceBundle Mechanismus eine weitreichende Unterstützung von länderspezifischer Lokalisierung. Das MapResourceBundle erweitert diesen um einige nützliche Funktionen:

  • Laden von Properties auch aus jar Archiven
  • Defaultwerte statt Exceptions beim Abfragen von Resourcen
  • Spezielle Unterstützung von Icons
 1  package mypackage;
 2
 3  import de.qfs.lib.util.MapResourceBundle;
 4  import de.qfs.lib.gui.Message;
 5
 6  public class MyMainClass {
 7
 8      public static void main(String[] args)
 9      {
10          MapResourceBundle bundle = new MapResourceBundle ();
11
12          bundle.fetchProperties("/de/qfs/lib/resources/properties/qflib", MapResourceBundle.class);
13          bundle.fetchProperties("/mypackage/resources/myproperties", MyMainClass.class);
14
15          Message.setResources(bundle);
16
17          bundle.getString("dialog.yes.name", "Yes");
18          bundle.getIcon("myicons.arrow", null);
19      }
20  }
Beispiel 3: Lokalisierung mit dem MapResourceBundle

In Zeile 12 wird das MapResourceBundle zunächst mit den Properties der qflib bestückt. Anschliessend (Zeile 13) werden eigene Properties aus mypackage hinzugefügt, die durchaus Werte aus der qflib überschreiben können, um diese an das eigene Programm anzupassen. Die übergebenen Klassen dienen zum Auffinden der Property Dateien in jar Archiven. Sie müssen so gewählt sein, dass sie im selben Archiv liegen wie die Property Datei.

Das so inititalisierte MapResourceBundle kann nun zum Beispiel an die Message Klasse aus dem de.qfs.lib.gui Package zur Ausgabe von lokalisierten Fehlerdialogen weitergereicht werden.

Zeile 17 demonstriert die Abfrage eines Wertes aus den Properties, bei der der Defaultwert angegeben wird, für den Fall, dass der Schlüssel des Wertes nicht vorhanden ist. Das vereinfacht die Handhabung im Vergleich zu Exceptions erheblich.

Damit die Abfrage des Icons (Zeile 18) funktioniert, muß in mypackage/resources/myproperties dem Schlüssel "myicons.arrow" der Pfad der Grafikdatei mit dem entsprechenden Icon zugeordnet sein, zum Beispiel "/mypackage/myicons/arrow.gif".

Package de.qfs.lib.gui

Eine Tabelle mit automatischer Sortierung

Das de.qfs.lib.gui Package enthält unter anderem eine Erweiterung der Swing JTable um Fähigkeiten zur Sortierung und zum Filtern von Zeilen. Obwohl dies ein sehr komplexer Mechanismus ist, ist das Interface mit dem sich der Anwender auseinandersetzen muss recht einfach, da die Defaulteinstellungen für den typischen Anwendungsfall ausreichen.

 1  package mypackage;
 2
 3  import javax.swing.JTable;
 4
 5  import de.qfs.lib.gui.SortedTableHelper;
 6
 7  public class MyClass {
 8
 9      public static JTable createTable(Object[][] rowData, Object[] columnNames)
10      {
11          JTable table = new JTable(rowData, columnNames);
12          SortedTableHelper helper = new SortedTableHelper (table);
13          helper.prepareTable();
14          return table;
15      }
16  }
Beispiel 1: Verwendung eines SortedTableHelpers um eine Tabelle zu sortieren

Beispiel 1 zeigt, wie Sie eine Tabelle mit der Standardfunktionalität zur Sortierung erzeugen. Diese hat keinen Filter, sortiert wird in Abhängigkeit der Klasse von Objektern einer Spalte, wie sie von TableMode.getColumnClass zurückgeliefert wird. Hierfür ist der DefaultTableModelSorter zuständig.

Ein komplexeres Demo, das auch einen Filter implementiert, ist als SortedTable.java im demo Verzeichnis enthalten. Wenn Sie es kompilieren und starten, erhalten Sie ein Fenster mit einer Tablle, in der Sie die verschiedenen Funktionen ausprobieren können. Die Tabelle erlaubt Mehrfachselektion von Zeilen, so dass Sie den Effekt der Sortierung auf die Selektion sehen können.

Die Spalte für die Sortierung wird durch einfachen Click in den Spaltenkopf bestimmt. Ein weiterer Click in den selben Kopf kehrt die Sortierrichtung um. Ein kleiner Pfeil im Spaltenkopf zeigt die aktuelle Sortierung an. Des weiteren können Sie mit einem Doppelklick in den Spaltenkopf die Spalte auf die Breite anpassen, die zur Anzeige für die aktuell sichtbaren Zellen dieser Spalte benötigt wird.

Eine sortierte Tabelle behält beim Umsortieren die Selektion ihrer Zeilen bei, wie Sie leicht nachvollziehen können indem Sie ein paar Zeilen selektieren und dann die Sortierung ändern. Sollten Sie das ListSelectionModel für die Zeilen der Tabelle ändern, nachdem Sie die Sortierung aktiviert haben, müssen Sie helper.saveSelection(true) aufrufen, um diesen Effekt beizubehalten.

Mit Hilfe der ComboBox oben im Fenster können Sie zwischen verschiedenen Filtereinstellungen wählen.

Es gibt einen wichtigen Punkt bei der Implementierung einer sortierten Tabelle dessen Sie sich bewusst sein sollten: Das TableModel das die Tabelle sieht ist nicht mehr das original Model mit dem sie erzeugt wurde, sondern ein FilteredAndSortedTableModel das zwischen die Tabelle und das Model geschoben wird. Da die Sortierung und Filterung von diesem FilteredAndSortedTableModel bestimmt werden, ohne die Daten im original Model zu ändern, entsprechen Zeilennummern, die von der Tabelle geliefert werden (z.B. bei getSelectedRows()) nicht mehr den Zeilennummern im original Model. Zum übersetzen müssen Sie helper.getSortedTableModel().getMappedRow(row) verwenden. Mit Hilfe des Dump Buttons im Demo können Sie diesen Effekt nachvollziehen, da damit die Indizes der selektierten Zeilen und deren Umsetzung auf die original Model Koordinaten auf System.err ausgegeben werden.


Letzte Änderung: dd.MM.yyyy, Copyright © 2000 Quality First Software, Gregor Schmid