Details zu den Neuerungen unter https://junit.org/junit5/docs/5.1.0/release-notes/index.html#release-notes-5.1.0
Autor: Christian Ullenboom
Callable oder Runnable mit FutureTask ummanteln
Die Klasse java.util.concurrent.FutureTask implementiert Future, Runnable und RunnableFuture und wird intern von der Java-Bibliothek verwendet, wenn wir mit submit(…) etwas beim ExecutorService absetzen. Auch wir können den Typ direkt als Wrapper um ein Callable oder Runnable nutzen, denn es gibt praktische Callback-Methoden, die wir überschreiben können, etwa done(), wenn eine Berechung fertig ist.
Dazu ein Beispiel: Ein Callable liefert den Namen des Benutzers. Ein FutureTask legt sich um dieses Callable und bekommt mit, wann das Callable fertig ist und modifiziert dann den Benutzernamen und gibt weiterhin eine Meldung aus.
Callable<String> username = () -> System.getProperty( "user.name" );
FutureTask<String> wrappedUsername = new FutureTask<>( username ) {
@Override
protected void done() {
try {
System.out.printf( "done: isDone=%s, isCancelled=%s%n", isDone(), isCancelled() );
System.out.println( "done: get=" + get() );
}
catch ( InterruptedException | ExecutionException e ) { /* Ignore */ }
}
@Override
protected void set( String v ) {
System.out.println( "set: " + v );
super.set( v.toUpperCase() );
}
};
ExecutorService scheduler = Executors.newCachedThreadPool();
scheduler.submit( wrappedUsername );
System.out.println( "main: " + wrappedUsername.get() );
scheduler.shutdown();
Wichtig in der Nutzung ist, nicht die Rückgabe vom submit(…) auszuwerten, was wir normalerweise machen, sondern das übergebene FutureTask zu erfragen.
Die Ausgaben vom Programm sind oft ein wenig durcheinander:
set: Christian done: isDone=true, isCancelled=false done: get=CHRISTIAN main: CHRISTIAN
Die Reihenfolge in den Aufrufen ist immer so: Der FutureTask stellt die Fertigstellung vom Callable fest und ruft set(…) auf. Anschließend done().
Tor auf mit CountDownLatch
Die Klasse CountDownLatch hilft Threads an einem gewissen Punkt zusammenzukommen und dann weiterzumachen. Das kann ein Thread sein, der auf das Fertigwerden von N anderen Threads wartet oder N Threads, die ein Signal zum Loslaufen bekommen. CountDownLatch ist mit einem Zähler assoziiert, der sich herunterzählen lässt. Auf der anderen Seite wartet die andere Partei darauf, dass der Zähler 0 wird, um fortzuführen. Mit diesem Wissen ergibt auch der Klassenname Sinn: „count down“ heißt auf deutsch „runterzählen“ und „latch“ ist das englische Wort für Falle, Riegel, Sperrklinke; nach dem erfolgreichen runterzählen entsperrt die Klinke.
Ein Beispiel. Wir wollen prüfen, ob Hosts im Internet erreichbar sind. Der Zähler vom CountDownLatch entspricht der Anzahl der zu überprüfenden Hosts. Für jeden Host starten wir einen Thread. Ist der Host erreichbar, wird CountDownLatch mit countDown() herunterzählt. Am Ende waren wir jedoch nicht mit await() ob wir bei 0 angekommen sind – Hosts können nicht erreichbar sein – sondern wir warten eine gewisse Zeit mit der überladenen Methode await(long timeout, TimeUnit unit). Ist der Zähler nach der Zeit nicht 0 gibt es keine Ausnahme, sondern wir erfragen den Zähler um herauszufinden, wie viele Hosts erreichbar waren.
List<String> hosts = List.of( "popel.nase", "tutego.de" );
CountDownLatch latch = new CountDownLatch( hosts.size() );
for ( String host : hosts ) {
new Thread( () -> {
try {
if ( InetAddress.getByName( host ).isReachable( 900 /* ms */ ) )
latch.countDown();
}
catch ( IOException e ) { /* ignore */ }
} ).start();
}
try {
if ( latch.await( 1, TimeUnit.SECONDS ) )
System.out.println( "Alle Hosts erreicht" );
System.out.printf( "%d von %d Hosts nicht erreicht", latch.getCount(), hosts.size() );
}
catch ( InterruptedException e ) {
e.printStackTrace();
}
Der CountDownLatch hat nur eine Methode zum Herunterzählen, nicht zum wieder erhöhen. Damit ist klar, dass sich ein heruntergezähltes Objekt nicht mehr verwenden kann.
Java sollte „Inkubator“-Features bekommen.
Das bedeutet, es gibt Syntaxerweiterungen, aber die sind erst mal zum Testen.
http://openjdk.java.net/jeps/12
An incubating language or VM feature is a new feature of the Java SE Platform that is fully specified, fully implemented, and yet impermanent. It is available in a JDK feature release to provoke developer feedback based on real world use; this may lead to it becoming permanent in a future Java SE Platform.
In der Mailliste fragen die Eclipse-Bauer schon nach, ob das verpflichtend ist … es wäre blöd, wenn man sich bei den JDT viel Mühe gibt, und dann wird das doch nicht übernommen: http://mail.openjdk.java.net/pipermail/jdk-dev/2018-February/000642.html
JEP-12 ist allerdings noch nicht bestätigt.
Erster Release Candidate für JDK 10
Download unter http://jdk.java.net/10/. Ich bin irgendwie überhaupt noch nicht in Java 10 Stimmung, wie geht es euch? Meine Welt ist immer noch Java 8 …
Inselupdate: switch hat ohne Unterbrechung Durchfall
Bisher haben wir in die letzte Zeile eine break-Anweisung gesetzt. Ohne ein break würden nach einer Übereinstimmung alle nachfolgenden Anweisungen ausgeführt. Sie laufen somit in einen neuen Abschnitt herein, bis ein break oder das Ende von switch erreicht ist. Da dies vergleichbar mit einem Spielzeug ist, bei dem Kugeln von oben nach unten durchfallen, nennt sich dieses auch Fall-through. Ein häufiger Programmierfehler ist, das break zu vergessen, und daher sollte ein beabsichtigter Fall-through immer als Kommentar angegeben werden.
Über dieses Durchfallen ist es möglich, bei unterschiedlichen Werten immer die gleiche Anweisung ausführen zu lassen. Das nutzt auch das nächste Beispiel, das einen kleinen Parser für einfache Datumswerte realisiert. Der Parser soll mit drei unterschiedlichen Datumsangeben umgehen können, je ein Beispiel:
- „18 12“: Jahr in Kurzform, Monat
- „2018 12“: Jahr, Monat
- „12“: Nur Monat, es soll das aktuelle Jahr implizit gelten
public class SimpleYearMonthParser {
@SuppressWarnings( "resource" )
public static void main( String[] args ) {
String date = "17 12";
int month = 0, year = 0;
java.util.Scanner scanner = new java.util.Scanner( date );
switch ( date.length() ) {
case 5: // YY MM
year = 2000;
// Fall-through
case 7: // YYYY MM
year += scanner.nextInt();
// Fall-through
case 2: // MM
month = scanner.nextInt();
if ( year == 0 )
year = java.time.Year.now().getValue();
break;
default :
System.err.println( "Falsches Format" );
}
System.out.println( "Monat=" + month + ", Jahr=" + year );
}
}
In dem Beispiel bestimmt eine case-Anweisung über die Länge, wie der Aufbau ist. Ist die Länge 5, so ist die Angabe des Jahres verkürzt und wir initialisieren das Jahr mit 2000, um im folgenden Schritt mit Hilfe vom Scanner das Jahr einzulesen. Zu diesem Schritt wären wir auch direkt gekommen, wenn die Länge der Eingabe 7 gewesen wäre, also das Jahr vierstellig gewesen wäre. Damit ist der Jahresanteil geklärt, es bleibt die Monate zu parsen. Kommen wir direkt über einen String der Länge 2, ist vorher kein Jahr gesetzt, wir bekommen über java.time.Year.now().getValue() das aktuelle Jahr, andernfalls überschreiben wir die Variable nicht.
Was sollte der Leser von diesem Beispiel mitnehmen? Eigentlich nur Kopfschütteln für eine schwer zu verstehende Lösung. Das Durchfallen ist eigentlich nur zur Zusammenfassung mehrerer case-Blöcke sinnvoll zu verwenden.
Sprachvergleich *
Obwohl ein fehlendes break zu lästigen Programmierfehlern führt, haben die Entwickler von Java dieses Verhalten vom syntaktischen Vorgänger C übernommen. Eine interessante andere Lösung wäre gewesen, das Verhalten genau umzudrehen und das Durchfallen explizit einzufordern, zum Beispiel mit einem Schlüsselwort. Dazu gibt es eine interessante Entwicklung: Java »erbt« diese Eigenschaft von C(++), die wiederum erbt sie von der Programmiersprache B. Einer der »Erfinder« von B ist Ken Thompson, der heute bei Google arbeitet und an der neuen Programmiersprache Go beteiligt ist. In Go müssen Entwickler ausdrücklich die fallthrough-Anweisung verwenden, wenn ein case-Block zum nächsten weiterleiten soll. Das gleiche gilt für die neue Programmiersprache Swift; auch hier gibt es die Anweisung fallthrough. Selbst in C++ gibt es seit dem Standard C++17 das Standard-Attribut [[fallthrough]][1] – Attribute sind mit Java-Annotationen vergleichbar ist; es steuert den Compiler, bei einem Durchfallen keine Warnung anzuzeigen.
Stack-Case-Labels
Stehen mehrere case-Blöcke untereinander, um damit Bereiche abzubilden, nennt sich das auch Stack-Case-Labels. Nehmen wir an, eine Variable hour steht für eine Stunde am Tag und wir wollen herausfinden, ob Mittagsruhe, Nachtruhe oder Arbeitszeit ist:
int hour = 12;
switch ( hour ) {
// Nachtruhe von 22 Uhr bis 6 Uhr
case 22:
case 23:
case 24: case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
System.out.println( "Nachtruhe" );
break;
// Mittagsruhe von 13 bis 15 Uhr
case 13:
case 14:
System.out.println( "Mittagsruhe" );
break;
default :
System.out.println( "Arbeiten" );
break;
}
[1] http://en.cppreference.com/w/cpp/language/attributes
Herunterladen der JDK 10 Early-Access Builds
Das ist unter http://jdk.java.net/10/ möglich. Die Javadoc liegt unter https://download.java.net/java/jdk10/docs/api/overview-summary.html. Release Notes unter http://jdk.java.net/10/release-notes
Interessanter ist:
Provide a new comment tag to specify the summary of an API description.
By default, the summary of an API description is inferred from the first sentence, which is determined by using either a simple algorithm or java.text.BreakIterator. But, the heuristics are not always correct, and can lead to incorrect determination of the end of the first sentence. A new inline tag {@summary ...} is now available, to explicitly specify the text to be used as the summary of the API description. Please refer to Documentation Comment Specification for the Standard Doclet.
The policytool security tool has been removed from the JDK.
Auch Eclipse kann mittlerweile schon zumindest Java 10 im Dialog einstellen, einige Updates wurden gemacht, siehe https://bugs.eclipse.org/bugs/show_bug.cgi?id=529875.
Feature-Release vs. zeitorientiertes Release (ab Java 10)
20 Jahre lang bestimmten Features die Freigabe von neuen Java-Versionen; die Entwickler setzten bestimmte Neuerungen auf die Wunschliste, und wenn alle Features realisiert und getestet waren, erfolgte die allgemeine Verfügbarkeit (eng. general availability, kurz GA). Hauptproblem dieses Feature-basierten Vorgehensmodells waren die Verzögerungen, die mit Problemen bei der Implementierung einhergingen. Vieldiskutiert war das Java 9-Release, weil es unbedingt ein Modulsystem enthalten sollte.
Die Antwort auf diese Probleme, und der Wusch der Java-Community nach häufigeren Releases, beantwortet Oracle mit der „JEP 322: Time-Based Release Versioning“[1]. Vier Releases sind im Jahr geplant:
- Im März und September erscheinen Haupt-Releases, wie Java 10, Java 11.
- Updates erscheinen einen Monat nach einem Haupt-Release und dann im Abstand von drei Monaten. Im April und Juli erscheinen folglich Updates 10.0.1 und 10.0.2. Für Java 11 wären das Oktober 2018 und Januar 2019.
Anders gesagt: Im Halbjahresrhythmus gibt es Updates, die es Oracle erlauben, in der schnelllebigen IT-Zeit die Bibliotheken weiter zu entwickeln und neue Spracheigenschaften zu integrieren. Kommt es zu Verzögerungen, hält das nicht gleich das ganze Release auf. Java 10 erscheint als erste Version im März 2018 nach diesem Modell.[2]
[1] http://openjdk.java.net/jeps/322
[2] http://openjdk.java.net/projects/jdk/10/
Neuerungen in Eclipse Photon (4.8) M5
Alle Details unter https://www.eclipse.org/eclipse/news/4.8/M5/.
Der Formatter ist eines der großen Änderungen.

Auch das Behandeln von Testfällen-Code.

JEP Draft für „Raw String Literals“ in Java
In der Diskussion ist der „Back-Tick“, sodass es so aussehen würde:
File Paths Example
| Traditional String Literals | Raw String Literals |
|---|---|
Runtime.getRuntime().exec("\"C:\\Program Files\\foo\" bar");
|
Runtime.getRuntime().exec(`"C:\Program Files\foo" bar"`); |
Multi-line Example
| Traditional String Literals | Raw String Literals |
|---|---|
String html = "<html>\n"
" <body>\n" +
" <p>Hello World.</p>\n" +
" </body>\n" +
"</html>\n";
|
String html = `<html>
<body>
<p>Hello World.</p>
</body>
</html>
`;
|
Regular Expression Example
| Traditional String Literals | Raw String Literals |
|---|---|
System.out.println("this".matches("\\w\\w\\w\\w"));
|
System.out.println("this".matches(`\w\w\w\w`));
|
Output:
true
Polyglot Example
| Traditional String Literals | Raw String Literals |
|---|---|
String script = "function hello() {\n" +
" print(\'\"Hello World\"\');\n" +
"}\n" +
"\n" +
"hello();\n";
ScriptEngine engine = new ScriptEngineManager().getEngineByName("js");
Object obj = engine.eval(script);
|
String script = `function hello() {
print('"Hello World"');
}
ScriptEngine engine = new ScriptEngineManager().getEngineByName(„js“); Object obj = engine.eval(script); |
Output:
"Hello World"
Database Example
| Traditional String Literals | Raw String Literals |
|---|---|
String query = "SELECT EMP_ID, LAST_NAME\n" +
"FROM EMPLOYEE_TBL\n" +
"WHERE CITY = 'INDIANAPOLIS'\n" +
"ORDER BY EMP_ID, LAST_NAME;\n";
|
String query = `SELECT EMP_ID, LAST_NAME
FROM EMPLOYEE_TB
WHERE CITY = 'INDIANAPOLIS'
ORDER BY EMP_ID, LAST_NAME;
`;
|
Ich bin gespannt, ob das in Java 10 kommen wird. Ich dachte schon, dass Java 10 Release wird eher klein, doch zusammen mit den var-Variablen sind das zwei kleine, aber bedeutende Sprachänderungen.
Details unter http://openjdk.java.net/jeps/8196004.
IntelliJ IDEA startet 2018.1 EA Programm
Und die IDE wird besser und besser.
Details unter https://blog.jetbrains.com/idea/2018/01/intellij-idea-starts-2018-1-early-access-program/.
Warum „können“ Arrays so wenig?
Arrays haben ein Attribut length und eine Methode clone() sowie die von Object geerbten Methoden. Das ist nicht viel. Für Arrays gibt es keine Klassendeklaration, also auch keine .class-Datei, die Methoden deklariert. Es gibt auch keinen Java-Code wie bei anderen Klassen wie String, Arrays oder System, die mit Javadoc dokumentiert sind und folglich in der API-Dokumentation auftauchen. Da es unendliche viele Array-Typen gibt – hinter jedem beliebigen Typ kann [] gesetzt werden – würde das auch unendlich viele Klassendeklarationen nach sich ziehen.
Das, was Arrays können, ist in der Java Sprachdefinition (JLS) festgeschrieben. Bei jeder neuen Methode oder Änderung müsste die JLS angepasst werden, was unpraktisch ist. Natürlich wären Methoden wie sort(…), indexOf(…) praktisch, aber die Beschreibung will Oracle aus der JLS heraushalten und sind in eine Extraklasse Arrays gewandert.
Eclipse Photon M4 freigeben
Auch Eclipse 4.8 geht in die nächste Runde mit dem Milestone 4. Die Pakete wie üblich unter https://www.eclipse.org/downloads/packages/release/Photon/M4. Die Änderungen dokumentiert https://www.eclipse.org/eclipse/news/4.7/.
Eclipse Oxygen.2 (4.7.2) freigegeben
Downloads wie üblich unter https://www.eclipse.org/downloads/eclipse-packages/. Wer der Updater laufen lässt, bekommt die Neuerungen automatisch eingespielt. Wer Java 9 nutzt, wird sich über Bugfixes freuen. Siehe auch https://www.eclipse.org/eclipse/news/4.7.2/.
Bitcoins und Ether spenden
Ab sofort können Spenden mit Bitcoins und Ether vorgenommen werden. Ich bin dankbar für jeden noch so kleinen Beitrag, der meine Arbeit versüßt 🙂
-
Bitcoin-Adresse: 12jhRLYbnCTCcDTbTDYXmPtuD3RmHuQyjH (BTC)
-
Bitcoin Cash-Adresse: 1Kyg6nnNoJK3ys9DBkrwTnj5DYVSux7QzQ (BCH)
-
Ether-Adresse: 0x701ae7f8ed4cc45cf3389e351d1f26f056862376 (ETH)
Aktuelles zu Java 10
Nach Java 9 wird uns Java 10 beglücken. Die Zeitleiste sieht so aus:
| 2017/12/14 | Rampdown Phase One | |
| 2018/01/11 | All Tests Run | |
| 2018/01/18 | Rampdown Phase Two | |
| 2018/02/22 | Final Release Candidate | |
| 2018/03/20 | General Availability |
Java 10 ist laut http://openjdk.java.net/projects/jdk/10/ ab heute in der „Rampdown Phase One“ (http://openjdk.java.net/projects/jdk8/milestones#Rampdown_start):
-
Rampdown — Phases in which increasing levels of scrutiny are applied to incoming changes. In phase 1, only P1-P3 bugs can be fixed. In phase 2 only showstopper bugs can be fixed.
Neuerungen dir wir für Java 10 erwarten können, beschrieben im JEP:
286: Local-Variable Type Inference
296: Consolidate the JDK Forest into a Single Repository
304: Garbage-Collector Interface
307: Parallel Full GC for G1
310: Application Class-Data Sharing
312: Thread-Local Handshakes
313: Remove the Native-Header Generation Tool (javah)
314: Additional Unicode Language-Tag Extensions
316: Heap Allocation on Alternative Memory Devices
317: Experimental Java-Based JIT Compiler
319: Root Certificates
322: Time-Based Release Versioning
Es wird auch Java 10, Java 11, usw. heißen und nicht, wie vorher angedacht, Java 18, etc. nach Jahreszahlen. Aus dem http://openjdk.java.net/jeps/322:
$FEATUREis incremented every six months: The March 2018 release is JDK 10, the September 2018 release is JDK 11, and so forth.
GWT 2.8.2 erschienen
http://www.gwtproject.org/release-notes.html#Release_Notes_2_8_2
Highlights
-
Supports running in Java 9. Note that this does not yet mean that GWT can compile Java 9 sources, or support the Java 9 JRE changes, but that a Java 9 JRE can be used to compile a GWT project. Do note that the new
--module-pathflag is not supported, but-classpathmust still be used as in the past. -
Chrome 61 removed functionality that had been used for reading the absolute top/left values. The internal implementation has been updated to reflect modern standards.
-
Uncaught exception handler will now receive all errors on the page, as handled by
window.onerror. This may potentially be a breaking change if there were misbehaving scripts on the page. To disable this functionality, set the propertygwt.uncaughtexceptionhandler.windowonerrortoIGNORE:
<set-property name="gwt.uncaughtexceptionhandler.windowonerror" value="IGNORE"/>
For more details, see com.google.gwt.core.Core.
Bug fixes
- LookupMethodCreator creates too large method
- NativeRegExp should use iframe instance, fixing Edge JIT bug
- JsProperty getter/setter sometimes were reported as incompatible
- Instantiating native JsTypes from JSNI results in InternalCompilerException
- Remove the SUBSIZED characteristic from filtered streams
- Internal compiler exception when using native JsType varargs in a JsMethod
- Regression in String.toLowerCase and toUpperCase for some locales, specifically for Turkish
- Missing bounds check in String.charAt
- Fix AIOOBE when compiling method references involving varargs.
- Apply HtmlUnit workaround ensuring that window.onerror is called correctly
Miscellanous
- Migrated lang/jre emulation JSNI to JsInterop to share with J2CL
- Added ErrorProne to gwt builds
- Improved compliance with CSP
- Added emulation for java.io.Externalizable
- Added emulation for java.lang.reflect.Array
- JSO.equals/hashcode will delegate to the JS object if it has methods with those names
- Removed outdated or unused parts of project
- Migrate guava JRE emulation to GWT
- HtmlUnit tests are now run in batch mode
For more detail, see the issue tracker and the commit log.
Inselraus: Zugriff auf SMB-Server mit jCIFS
Microsoft Windows nutzt zur Datei- und Verzeichnisfreigabe, zur Freigabe von Druckern und Kommunikationsschnittstellen das Protokoll SMB (Server Message Block). Es ist weit verbreitet, und jede aktuelle Windows-Version lässt sich als Client und Server konfigurieren – gleichzeitig gibt es unter Unix das populäre Samba, einen SMB-Server unter Open Source, der von Andrew Tridgell und Kollegen entwickelt wurde.
Mithilfe der jCIFS-SMB-Bibliothek (http://jcifs.samba.org/) kann ein Java-Programm auf Datei- und Verzeichnisfreigaben zugreifen und Freigaben auflisten. jCIFS ist eine erweiterte Implementierung von CIFS und unterstützt Unicode, Batching, verschlüsselte Authentifizierung, Transactions, das Remote Access Protocol (RAP) und Weiteres. Die Bibliothek steht unter der LGPL.
Die Klassen jcifs.smb.SmbFile, SmbFileInputStream und SmbFileOutputStream verhalten sich ähnlich wie java.io.File, FileInputStream und FileOutputStream. Sie werden mit einem Dateipfad (URL) parametrisiert, der mit smb:// beginnt. Um eine Datei zu beziehen, muss vorher der Server spezifiziert werden. Dazu dienen Eigenschaften wie WINS. Sie werden mit Config.setProperty(„wins“, „IP-Adresse“); gesetzt.
Beispiel: Lies eine Datei aus, und kopiere sie um:
InputStream in = new SmbFileInputStream( "smb://user:passwd@host/c/My Documents/doc.txt" ); Path target = … Files.copy( in, target );
Inselraus: Anwendungen für FilterReader und FilterWriter
Unsere nächste Klasse bringt uns etwas näher an das HTML-Format heran. Wir wollen eine Klasse HTMLWriter entwerfen, die FilterWriter erweitert und Textausgaben in HTML konvertiert. In HTML werden Tags eingeführt, die vom Browser erkannt und besonders behandelt werden. Findet etwa der Browser im HTML-Text eine Zeile der Form <strong>Dick</strong>, so stellt er den Inhalt „Dick“ in fetter Schrift dar, da das <strong>-Element den Zeichensatz umstellt. Alle Tags werden in spitzen Klammern geschrieben. Daraus ergibt sich, dass HTML einige spezielle Zeichenfolgen (Entities genannt) verwendet. Wenn diese Zeichen auf der HTML-Seite dargestellt werden, muss dies durch spezielle Zeichensequenzen geschehen:
- < wird zu <
- > wird zu >
- & wird zu &
Kommen diese Zeichen im Quelltext vor, so muss unser HTMLWriter diese Zeichen durch die entsprechende Sequenz ersetzen. Andere Zeichen sollen nicht ersetzt werden.
Den Browsern ist die Struktur der Zeilen in einer HTML-Datei egal. Sie formatieren wiederum nach speziellen Tags. Zeilenvorschübe etwa werden mit <br/> eingeleitet. Unser HTMLWriter soll zwei leere Zeilen durch das Zeilenvorschub-Element <br/> markieren.
HTML-Dokument schreiben
Alle sauberen HTML-Dateien haben einen wohldefinierten Anfang und ein wohldefiniertes Ende. Das folgende kleine HTML-Dokument ist wohlgeformt und zeigt, was unser Programm später erzeugen soll:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html><head><title>Superkreativer Titel</title></head> <body><p> Und eine Menge von Sonderzeichen: < und > und & Zweite Zeile <br/> Leerzeile Keine Leerzeile danach </p></body></html>
Der Titel der Seite sollte im Konstruktor übergeben werden können. Hier ist nun das Programm für den HTMLWriter:
package com.tutego.insel.io.stream;
import java.io.*;
class HTMLWriter extends FilterWriter {
private boolean newLine;
/**
* Creates a new filtered HTML writer with a title for the web page.
*
* @param out a Writer object to provide the underlying stream.
* @throws IOException if the header cannot be written
*/
public HTMLWriter( Writer out, String title ) throws IOException {
super( out );
out.write( "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\"" +
" \"http://www.w3.org/TR/html4/strict.dtd\">\n" );
out.write( "<html><head><title>" + title + "</title></head>\n<body><p>\n" );
}
/**
* Creates a new filtered HTML writer with no title for the web page.
*
* @param out a Writer object to provide the underlying stream.
*/
public HTMLWriter( Writer out ) {
super( out );
}
/**
* Writes a single character.
*/
@Override
public void write( int c ) throws IOException {
switch ( c ) {
case '<':
out.write( "<" );
newLine = false;
break;
case '>':
out.write( ">" );
newLine = false;
break;
case '&':
out.write( "&" );
newLine = false;
break;
case '\n':
if ( newLine ) {
out.write( "<br/>\n" );
newLine = false;
}
else
out.write( "\n" );
newLine = true;
break;
case '\r':
break; // ignore
default :
out.write( c );
newLine = false;
}
}
/**
* Writes a portion of an array of characters.
*
* @param cbuf Buffer of characters to be written
* @param off Offset from which to start reading characters
* @param len Number of characters to be written
* @exception IOException If an I/O error occurs
*/
@Override
public void write( char[] cbuf, int off, int len ) throws IOException {
for ( int i = off; i < len; i++ )
write( cbuf[ i ] );
}
/**
* Writes a portion of a string.
*
* @param str String to be written.
* @param off Offset from which to start reading characters
* @param len Number of characters to be written
* @exception IOException If an I/O error occurs
*/
@Override
public void write( String str, int off, int len ) throws IOException {
for ( int i = off; i < len; i++ )
write( str.charAt( i ) );
}
/**
* Closes the stream.
*
* @throws IOException If the prolog can not be written or the underlying stream
* not be closed
*/
@Override
public void close() throws IOException {
try {
out.write( "</p></body></html>" );
}
finally {
out.close(); // Ignoriere, falls out.close() und out.write() knallt
}
}
}
Ein Demo-Programm soll die aufbereiteten Daten in einen StringWriter schreiben:
StringWriter sw = new StringWriter();
try ( HTMLWriter html = new HTMLWriter( sw, "Superkreativer Titel" );
PrintWriter pw = new PrintWriter( html ) ) {
pw.println( "Und eine Menge von Sonderzeichen: < und > und &" );
pw.println( "Zweite Zeile" );
pw.println();
pw.println( "Leerzeile" );
pw.println( "Keine Leerzeile danach" );
}
System.out.println( sw );
HTML-Tags mit einem speziellen Filter überlesen
Unser nächstes Beispiel ist eine Klasse, die den FilterReader so erweitert, dass HTML-Tags überlesen werden. Die Klasse FilterReader deklariert den notwendigen Konstruktor zur Annahme des Reader, der die wirklichen Daten liefert, und überschreibt zwei read(…)-Methoden. Die read()-Methode ohne Parameter – die ein int für ein gelesenes Zeichen zurückgibt – legt einfach ein 1 Zeichen großes Feld an und ruft dann die zweite überschriebene read(char[], int, int)-Methode auf, die die Daten in ein Feld liest. Da dieser Methode neben dem Feld auch noch die Größe übergeben werden kann, müssen wirklich so viele Zeichen gelesen werden. Es reicht einfach nicht aus, die übergebene Anzahl von Zeichen vom tiefer liegenden Reader zu lesen, sondern hier müssen wir beachten, dass eingestreute Tags nicht zählen. Die Zeichenkette <p>Hallo<p> ist ja nur fünf Zeichen lang und nicht elf!
package com.tutego.insel.io.stream;
import java.io.*;
public class HTMLReader extends FilterReader {
private boolean inTag = false;
public HTMLReader( Reader in ) {
super( in );
}
@Override
public int read() throws IOException {
char[] buf = new char[ 1 ];
return read( buf, 0, 1 ) == –1 ? –1 : buf[ 0 ];
}
@Override
public int read( char[] cbuf, int off, int len ) throws IOException {
int numchars = 0;
while ( numchars == 0 ) {
numchars = in.read( cbuf, off, len );
if ( numchars == –1 ) // EOF?
return –1;
int last = off;
for ( int i = off; i < off + numchars; i++ ) {
if ( ! inTag ) {
if ( cbuf[ i ] == '<' )
inTag = true;
else
cbuf[ last++ ] = cbuf[ i ];
}
else if ( cbuf[ i ] == '>' )
inTag = false;
}
numchars = last – off;
}
return numchars;
}
}
Ein Beispielprogramm soll die Daten aus einem StringReader ziehen. Der HTMLReader bekommt diesen StringReader und wird selbst von Scanner genutzt, damit wir die komfortable nextLine()-Methode nutzen können. Da hier keine externen Ressourcen vorkommen, müssen wir nichts schließen, und ein try mit Ressourcen kann entfallen.
String s = "<html>Hallo! <b>Ganz schön fett.</b> " + "Ah, wieder normal.</html>"; Reader sr = new StringReader( s ); Reader hr = new HTMLReader( sr ); Scanner scanner = new Scanner( hr ); while ( scanner.hasNextLine() ) System.out.println( scanner.nextLine() );
Es produziert dann die einfache Ausgabe:
Hallo! Ganz schön fett. Ah, wieder normal.
Inselraus: Ein LowerCaseWriter
Wir wollen im Folgenden einen Filter schreiben, der alle in den Strom geschriebenen Zeichen in Kleinbuchstaben umwandelt. Drei Dinge sind für einen eigenen FilterWriter nötig:
- Die Klasse leitet sich von FilterWriter
- Unser Konstruktor nimmt als Parameter ein Writer-Objekt und ruft mit super(out) den Konstruktor der Oberklasse auf, also FilterWriter. Die Oberklasse speichert das übergebene Argument in der geschützten Objektvariablen out, sodass die Unterklassen darauf zugreifen können.
- Wir überlagern die drei write(…)-Methoden und eventuell noch close() und flush(). Unsere write(…)-Methoden führen dann die Filteroperationen aus und geben die wahren Daten an den Writer
class LowerCaseWriter extends FilterWriter {
public LowerCaseWriter( Writer writer ) {
super( writer );
}
@Override
public void write( int c ) throws IOException {
out.write( Character.toLowerCase((char)c) );
}
@Override
public void write( char[] cbuf, int off, int len ) throws IOException {
write( String.valueOf( cbuf ), off, len );
}
@Override
public void write( String s, int off, int len ) throws IOException {
out.write( s.toLowerCase(), off, len );
}
}
Und die Nutzung sieht dann so aus:
StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter( new LowerCaseWriter( sw ) ); pw.println( "Eine Zeile für klein und groß" ); System.out.println( sw.toString() );