Java Schulung Objektorientierte Programmierung in Java Teil VI: Wichtige Java Pakete Prof. Dr. Nikolaus Wulff Java Pakete • Für die meisten Aufgaben stellt Java bereits viele nützliche Bibliotheken zur Verfügung: – Datei Ein- und Ausgabe java.io – Container Klassen java.util – Logging API java.util.logging – Netzwerk und Socket Programmierung java.net – Datenbankzugriffe java.sql – Graphische Benutzerschnittstellen java.awt und javax.swing • Das Problem ist lediglich das richtige Paket für die gestellte Aufgabe zu finden :-( Prof. Dr. Nikolaus Wulff 2 java.io • Das java.io Paket enthält die wichtigen Konzepte zur Manipulation von Dateien und das Arbeiten mit Streams: – File – Input- und OutputStream – Reader und Writer • Die Streams und Reader/Writer Klassen sind nach dem Unix Pipe Konzept modelliert und lassen sich nahezu beliebig verschachteln. Prof. Dr. Nikolaus Wulff 3 Input Klassenhierarchie System InputStream (from lang) -in +$in FilterInputStream ByteArrayInputStream ObjectInputStream StringBufferInputStream L ineNumberInp utStre am -dis DataInputStream PipedInputStream BufferedInputStream PushbackInp utStre am io (from java) -fd Fil eInputStream FileDescriptor +$out +$err +$in IOExcep tion Strin g -path (from lang) File +$separator +$pathSeparato r Prof. Dr. Nikolaus Wulff 4 Output Klassenhierarchie <<Interface>> ObjectStreamConstants OutputStream #out -out <<Interface>> ObjectOutput PipedOutp utStre am FilterOutputStream Ob jectOutputStream ByteArrayOutputStream -dos io BufferedOutputStream DataOutputStream (from java) Prin tStrea m OutputStreamWriter -charOut IOException +$out FileOutputStream FileDescriptor -fd System +$out Prof. Dr. Nikolaus Wulff (from lang) 5 Reader • Die Reader Klassen kapseln Texteingaben. • Häufig verwenden sie im Konstruktor einen InputStream, von dem sie die Daten einlesen. InputStream Reader #in -in InputStreamReader StringReader BufferedReader Fil terReader PipedReader CharArrayReader FileReader Prof. Dr. Nikolaus Wulff – Beispiel InputStream- und FileReader • Die Reader lassen sich verschachteln. 6 Writer Writer PipedWriter -out -out CharArrayWriter PrintWriter Fil terWri ter StringWrite r OutputStreamWriter BufferedWriter • Die Writer Hierarchie besteht aus Klassen für Textausgaben. • Sie ist ähnlich strukturiert wie die Reader Hierarchie. Prof. Dr. Nikolaus Wulff 7 Fehler über Fehler... EOFException io IOException (from java) FileNotFoundException InterruptedIOException SyncFailedException Ob jectStreamException CharConversionException UTFDataFormatException NotActiveException UnsupportedEncodingException Invali dC lass Excepti on StreamCorruptedException Invali dObjectException OptionalDataException WriteAbortedException Prof. Dr. Nikolaus Wulff 8 Übung • Entwickeln Sie das Unix Head Utility – – – – – Head schreibt die ersten 10 Zeilen einer Datei auf die Console. Die Anzahl der zu lesenden Zeilen kann vorgegeben werden. Testen Sie mit JUnit! Die Ausgabe kann in eine Datei umgeleitet werden. Aufruf: java Head dateiname [-o ausgabedatei] [-l anzahl zeilen] • Ziele: – Kennenlernen der io-Klassen: • File & FileInput/OutputStream, LineNumberReader • Stream Verkettungen – Datei IO sinnvoll mit JUnit testen, temporäre Dateien anlegen. Prof. Dr. Nikolaus Wulff 9 Head in Aktion Aufruf Verwendete Klassen Ausgabe Schleife Zeilen zählen! Prof. Dr. Nikolaus Wulff 10 Ströme verketten • Die Java IO-Ströme lassen sich wie bei der Unix Pipe Architektur verketten. • Dadurch ist es möglich Filter dazwischen zu schalten oder Ströme mit neuen Eigenschaften zu dekorieren. Eingabe Datei FileInputStream copy(from, to) Ausgabe Datei FileOutputStream GZIPOutputStream Prof. Dr. Nikolaus Wulff 11 Ein ZIP Strom • Die Verkettung von Strömen ist recht einfach: OutputStream out; out = new FileOutputStream(destination); out = new BufferedOutputStream(out); out = new GZIPOutputStream(out); • und hier die generische Kopiermethode: protected void copy(InputStream in, OutputStream out) throws IOException { byte[] buf = new byte[4096]; int read, EOF = -1; while( (read = in.read(buf)) != EOF) { out.write(buf, 0, read); } } Prof. Dr. Nikolaus Wulff 12 Filter als Ströme • Durch die Verkettung von Eingabe- oder Ausgabeströmen lassen sich sehr einfach und modular Filter entwickeln. • Es muss lediglich ein zusätzlicher Filter in die Einoder Ausgabe geschaltet werden: Eingabe Datei FileInputStream MyFilterInputStream Ausgabe Datei FileOutputStream copy(from, to) Prof. Dr. Nikolaus Wulff 13 Ein HTML Filter • Ein einfacher HTML zu Text Konverter soll entwickelt werden. • Hierzu wird ein Filter verwendet, der alle HTML Markup Tags elemeniert. • Da es sich hierbei um reinen Text handelt ist es einfacher mit einem Reader/Writer Paar zu arbeiten: Eingabe Datei FileReader Ausgabe Datei FileWriter Prof. Dr. Nikolaus Wulff HTMLReader copy(from, to) 14 BufferedReader überladen • Um einen CharakterFilter zu entwickeln bietet es sich an von BufferedReader zu erben und dessen read-Methode zu überladen. • Die gesamte Funktionalität eines (gepufferten) Readers ist dann bereits vorhanden. • Es wird lediglich eine Methode implementiert, die die HTML Auszeichnungen <xxx> herausfiltert. • Eine „gute“ Implementierung würde eventuell noch mehr leisten und z. B. an den richtigen Stellen Zeilenumbrüche einbauen etc... public class HTMLReader extends BufferedReader {...} Prof. Dr. Nikolaus Wulff 15 Gefiltertes read public int read(char[] buf, int start, int len) throws IOException { int i, j, num = 0; boolean markup = false; while(num == 0) { num = super.read(buf, start, len); if (num == EOF) return EOF; i = j = start; for(; i < start + num; i++) { switch( buf[i]) { case '<': markup = true; break; case '>': markup = false; break; default: if(!markup) buf[j++] = buf[i]; } // end switch } // for loop num = j - start; } // while loop return num; } Prof. Dr. Nikolaus Wulff 16 Java Persistenz • Im IO Paket befindet sich die Serializable Schnittstelle. • Serializable ist ein leeres Marker Interface. • Klassen die diese Schnittstelle implementieren können von der JVM automatisch in einem Stream serialisiert und somit per TCP/IP verschickt oder im Dateisystem gespeichert und wieder ausgelesen werden. • Java verwendet hierzu die von Object geerbten Methoden read/writeObject. • Jedes Objekt lässt sich in einen ObjectOutputStream schreiben oder von einem ObjectInputStream lesen. Prof. Dr. Nikolaus Wulff 17 Übung • Erweitern Sie das Painter und Shapes Beispiel, so dass der Painter die Shapes in eine Datei schreiben oder wieder auslesen kann. – Geben Sie den Shapes mehr Attribute • posX, posY, width, height – Speichern/Lesen Sie in/aus "Paintable.dat" – Geben Sie die Datei mit dem DOS Type Befehl auf der Console aus. Was sehen Sie? – Was passiert wenn eine der Shapes Klassen nicht Serializable implementiert? Prof. Dr. Nikolaus Wulff 18 Tip: Painter (from painter) ObjectOutputStream (from j av a. io) ObjectInputStream writeAll drawAll() addPaintable() storeAll() readAll() readAll (from j av a.io) 0..* <<Interface>> Paintable <<Interface>> Serializable (from painter) (from j av a.io) Shape (from shapes) • • Lesen und Schreiben sind symmetrische Aktionen mit korrepondierenden IO-Streams. Das Serializable Marker-Interface wird am besten von der abstrakten Shapes Klasse implementiert. Prof. Dr. Nikolaus Wulff 19 Serialisierung in Aktion public static void main(String[] args) throws Exception { Painter painter = new Painter(); addShapes(painter); painter.drawAll(); System.out.println(""); String fileName = "Paintable.dat"; // store graph to file system System.out.println("Store to " + fileName); painter.storeAll(fileName); // now create an empty fresh painter Painter emptyPainter = new Painter(); System.out.println(""); System.out.println("Read from " + fileName); emptyPainter.readAll(fileName); emptyPainter.drawAll(); Prof. Dr. Nikolaus Wulff } 20 Systemeigenschaften • Das java.util Paket enthält die Klasse Properties als Erweiterung einer Hashtable (Map). • In ihr werden System- oder Applikationseigenschaften hinterlegt. • Properties werden häufig als Textdateien gepflegt, um eine Anwendung zu parametrisieren - ohne neu compilieren zu müssen. • Properties lassen sich per In-/OutStream einfach (lesbar!) serialisieren. • Die java.lang.System Klasse liefert z.B. per getProperties die aktuellen Umgebungsvariablen der JVM. Properties props = System.getProperties(); Prof. Dr. Nikolaus Wulff 21 Eine Property Datei # -------------------------------------------------# general server settings # -------------------------------------------------server.url = localhost server.protocol = http server.port = 80 # -------------------------------------------------# available services # -------------------------------------------------service.1.name = HelloWorld service.1.class = services.HelloWorldService service.1.param = DummyParameter # service.2.name = Calculator service.2.class = services.CalculatorService • Obiges Beispiel zeigt die Struktur einer Property Datei. Sie besteht aus (key,value)-Tupeln mit dem Kommentarzeichen #. • Aufgrund des einfachen Aufbaus lässt sich diese Struktur ideal in einer Map/Collection Klasse speichern... Prof. Dr. Nikolaus Wulff 22 Übung • Schreiben Sie ein Programm, das die Systemeinstellungen auf der Console ausgibt und auf Wunsch in einer Datei speichert. Was bedeuten die Eigenschaften? • Systemeinstellungen lassen sich beim Start der JVM per Kommandozeile java -Dkey=value übergeben. Probieren sie dies aus. – Tip: versuchen Sie einmal java -help • Erweitern Sie die Klasse, so dass diese eine beliebige Property Datei wieder einliest und ausgibt. Prof. Dr. Nikolaus Wulff 23 Properties einlesen • Eigenschaften lassen sich von beliebigen InputStreams einlesen: protected Properties loadInput(String name) { Properties props = new Properties(); try { InputStream fin = new FileInputStream(name); props.load(fin); } catch (Exception error) { println("error reading " + name); error.printStackTrace(); } return props; } Prof. Dr. Nikolaus Wulff 24 Properties schreiben • Ebenso leicht lassen sich diese in beliebige Ströme schreiben: protected void storeOutput(Properties props, String name) { try { OutputStream fout = new FileOutputStream(name); props.store(fout, "Properties example"); } catch (Exception error) { println("error writing " + name); error.printStackTrace(); } } Prof. Dr. Nikolaus Wulff 25 Logging • Häufig werden während der Entwicklung Debug Ausgaben per System.out.println ausgegeben. • Im Release Code sollen diese meistens nicht mehr erscheinen und auch die Laufzeitumgebung nicht mehr unnötig mit String Operationen belasten. Nur noch wichtige Fehlermeldungen müssen protokolliert werden. • Diese Funktionalität wird von Bibliotheken wie z.B. Log4J oder seit JDK1.4 von dem java.util.logging Paket erfüllt. • Debug Nachrichten lassen sich per Konfiguration aus einer Properties Datei gezielt für einzelne Pakete oder Klassen gestaffelt nach der Wichtigkeit (DEBUG, INFO, WARN, ERROR,...) an oder ausschalten und in Dateien umleiten. Prof. Dr. Nikolaus Wulff 26 Logging Example import java.util.logging.Logger; public class LoggingExample { public static final boolean DEBUG = true; /** create a logger associated to this class name */ private static Logger logger = Logger.getLogger(LoggingExample.class.getName()); public void assertion(boolean cond, String reason) { if (!cond) { // assertions logging will stay in production code logger.severe("ASSERTION " + reason); throw new IllegalStateException(reason); } } public void doSomething(Object obj) { if (DEBUG) { // no info logging in production code logger.info("doing something with " + obj); } assertion(obj != null,"Object == null"); // do something useful ... } public static void main(String[] args) { LoggingExample example = new LoggingExample(); example.doSomething(null); } } Prof. Dr. Nikolaus Wulff 27 Tips zum Logging • Da das DEBUG boolean als final static markiert ist, kann der Compiler dies bereits auswerten und der gesamte Codeblock wird nicht in die Class-Datei generiert, falls das Flag false ist. => Performance im Release! • Damit nicht in allen Klassen DEBUG Flags gesetzt werden müssen, das Flag pro Projekt/Bibliothek in eine zentrale Schnittstellendatei auslagern. Diese wird an einer zentralen Stelle verwaltet: import myproject.settings.Global; public class LoggingExample { public void doSomething(Object obj) { if (Global.DEBUG) { // no info logging in production code logger.info("doing something with " + obj); } assertion(obj != null,"Object == null"); // do something useful ... } Prof. Dr. Nikolaus Wulff 28 Logging Format • Eigene LoggingFormatter können entwickelt und definiert werden. • Der Formatter der Log Ausgaben lässt sich in der Logging.properties Datei parametrisieren. • Optional kann er auch als Systemeigenschaft definieren werden (funktioniert noch nicht): -Djava.util.logging.ConsoleHandler.formatter=myFormatter Prof. Dr. Nikolaus Wulff 29 Logging.properties ## From %JDK_HOME%\jre\lib\Logging.properties handlers= java.util.logging.ConsoleHandler # To also add the FileHandler, use the following line instead. #handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler # Default global logging level. .level= INFO ############################################################ # Handler specific properties. # Describes specific configuration info for Handlers. ############################################################ # default file output is in user's home directory. java.util.logging.FileHandler.pattern = %h/java%u.log java.util.logging.FileHandler.limit = 50000 java.util.logging.FileHandler.count = 1 java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter # Limit the message that are printed on the console to INFO and above. java.util.logging.ConsoleHandler.level = INFO # java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter java.util.logging.ConsoleHandler.formatter=logging.LogFormatter Einbinden eines eigenen Formatierers Prof. Dr. Nikolaus Wulff 30 Übung • Entwickeln Sie einen LogFormatter der die Klasse und Zeilenummer der Lognachricht mit ausgibt. • Tip: Die Zeilennummer lässt sich aus dem Stacktrace einer Exception ermitteln... • Verwenden Sie anstatt des + Operators die Klasse java.lang.StringBuffer, um Strings zusammenzufügen. Prof. Dr. Nikolaus Wulff 31 Stringparsing • Häufig müssen zusammengesetzte Strings nach einem bestimmten Schema zerlegt werden, z.B. CSV Dateien (comma-separated-values). • Im java.util Paket befindet sich die Klasse StringTokenizer, die es gestattet String sehr komfortabel in Teilausdrücke (Tokens) zu zerlegen. String bigline = "this is a test"; StringTokenizer st = new StringTokenizer(bigline); while (st.hasMoreTokens()) { System.out.println(st.nextToken()); this } is a test Prof. Dr. Nikolaus Wulff 32 Stringparsing • Im Konstruktor kann das Trennungszeichen angeben werden • Voreinstellung ist das Leerzeichen (space) ' ' == '\s' • Seit dem JDK1.4 gibt es in der String Klasse die Operation split, die gleiches leistet und bevorzugt verwendet werden soll. (Erfordert regular expression Syntax). String[] result = bigline.split("\\s"); for (int x=0; x<result.length; x++) System.out.println(result[x]); Prof. Dr. Nikolaus Wulff 33 Übung • Entwickeln Sie einen Parser, der den Klassennamen einer beliebigen Klasse in die einzelnen Pakete und die eigentliche Klasse zerlegt. • Denken Sie daran auch innere Klassen und Interfaces mit auszugeben. Woran kann man diese erkennen? Tip: Schauen Sie in das bin Verzeichnis... • Vergessen Sie den JUnit Test nicht. Prof. Dr. Nikolaus Wulff 34 Übung • Entwickeln und Testen Sie einen Parser, der einen HTTP GET request in seine Bestandteile zerlegt. • Ein solcher Request hat die generische Form Request-Line = Method SP Request-URI SP HTTP-Version CRLF • Die URI setzt sich aus dem Pfad und dem Dokument zusammen. • Handelt es sich statt dessen um ein Servlet so können durch '?' und '&' getrennt optionale Parameter übergeben werden. Request-URI = /path/dokument Request-URI = /name? arg1=val1 &arg2=val2 ... GET /Calculator?arg1=5&arg2=3&op=+ HTTP/1.1 Prof. Dr. Nikolaus Wulff 35