Unicode-Blöcke

Unicode-Zeichen gehören immer Blöcken an und bei denen ist teilweise immer etwa Platz, um nachrückende Zeichen noch aufnehmen zu können. Beim lateinischen Alphabet ist das nicht so wichtig, wohl aber bei mathematischen Sonderzeichen oder anderen Symbolen.

Die Klasse Character deklariert eine öffentliche statische finale Klasse UnicodeBlock mit einer Vielzahl von Unicode-Blöcken, die als öffentliche statische Variablen in UnicodeBlock deklariert sind und selbst vom Typ UnicodeBlock sind. Character.UnicodeBlock.BASIC_LATIN ergibt zum Beispiel so einen Block, allerdings ist der Typ nicht so ausdrucksstark, nur der Name kommt bei einem toString() dabei raus, aber nicht etwa in welchem Bereich die Zeichen liegen. Auch fehlt die Möglichkeit alle Zeichen aufzuzählen oder zu testen, ob ein Zeichen im Block liegt. Was jedoch der Typ UnicodeBlock bietet sind zwei statische Methoden of(int) und of(char), die als Fabrikfunktionen einen UnicodeBlock für ein gewisses Zeichen geben. Der ist-ein-Element-von-Test lässt sich also damit indirekt realisieren.

Beispiel. Gib die Namen der Unicode-Blöcke für einige Zeichen aus:

UnicodeBlock basicLatin = Character.UnicodeBlock.BASIC_LATIN;

System.out.println( basicLatin );

System.out.println( Character.UnicodeBlock.of( ‚ß‘ ) );

System.out.println( Character.UnicodeBlock.of( ‚\u263A‘ ) );

System.out.println( Character.UnicodeBlock.of( ‚\u20ac‘ ) );

System.out.println( Character.UnicodeBlock.of( 0x1D15E ) );

Das liefert BASIC_LATIN LATIN_1_SUPPLEMENT MISCELLANEOUS_SYMBOLS

CURRENCY_SYMBOLS MUSICAL_SYMBOLS.

Das Wissen um den Bereich ist immer hilfreich dann, wenn ein unbekannter Text zugeordnet werden soll, denn auf diese Weise lässt sich erahnen, ob der Text zum Beispiel auf lateinischen Buchstaben basiert, er arabisch, chinesisch oder japanisch (Kanji/Kana) ist.

JDK 8 Milestones

Nach http://openjdk.java.net/projects/jdk8/milestones sind das nun:

M1    2012/04/26    (b36)
117 Remove the Annotation-Processing Tool (apt)
M2    2012/06/14    (b43)
133 Unicode 6.1

M3    2012/08/02    (b50)
124 Enhance the Certificate Revocation-Checking API
130 SHA-224 Message Digests
131 PKCS#11 Crypto Provider for 64-bit Windows

Hier stehen wir, das soll kommen:

M4    2012/09/13   
105 DocTree API
121 Stronger Algorithms for Password-Based Encryption
129 NSA Suite B Cryptographic Algorithms

M5    2012/11/29   
106 Add Javadoc to javax.tools
110 New HTTP Client
111 Additional Unicode Constructs for Regular Expressions
112 Charset Implementation Improvements
113 MS-SFU Kerberos 5 Extensions
114 TLS Server Name Indication (SNI) Extension
119 javax.lang.model Implementation Backed by Core Reflection
122 Remove the Permanent Generation
128 BCP 47 Locale Matching
140 Limited doPrivileged
153 Launch JavaFX Applications

M6    2013/01/31        Feature Complete
101 Generalized Target-Type Inference
104 Annotations on Java Types
107 Bulk Data Operations for Collections
108 Collections Enhancements from Third-Party Libraries
109 Enhance Core Libraries with Lambda
115 AEAD CipherSuites
118 Access to Parameter Names at Runtime
120 Repeating Annotations
123 Configurable Secure Random-Number Generation
126 Lambda Expressions and Virtual Extension Methods
135 Base64 Encoding and Decoding
156 G1 GC: Reduce need for full GCs
160 Lambda-Form Representation for Method Handles

M7    2013/02/21        Developer Preview
2013/03/18        All Tests Run
2013/04/04        Rampdown start
2013/05/02        API/Interface Freeze
2013/05/16        Zero Bug Bounce
2013/06/13        Rampdown phase 2
M8    2013/07/05        Final Release Candidate
GA    2013/09/09        General Availability

Von Date-Time API nichts zu sehen!

Thema der Woche: @CheckForNull, @Nonnull

Null-Pointer-Exceptions sind eine Qual, da oftmals eine Referenzvariable null ist, die nicht null sein darf. Das kommt oft erst zur Laufzeit bei ganz besonderen Ausführungspfaden raus.  Mit Annotationen kann man dem ein wenig entgegentreten, da man zum Einen gut dokumentiert was erlaubt ist und was nicht, und zum Anderen Analysetools erlaubt, sich die Ausführungspfade etwas genauer anzuschauen.

Snippet: Kalender ausdrucken, Teil 2: von-bis

Die erste Version meines Kalenderprogramms druckte ein Kalender für ein Jahr. In Anwendungen dürfte häufiger vorkommen, dass es ein Start- und Enddatum gibt, das auch über Jahresgrenzen liegt. Das macht dieses Programm:

public static class CalLine
{
  public int year;
  public int weekOfYear;
  public int month = -1;  // 0 <= month <= 11
  public int[] day = { -1, -1, -1, -1, -1, -1, -1 };
}

public static List<CalLine> calenderOfTheYear( Date start, Date end )
{
  Calendar startCal = new GregorianCalendar();
  startCal.setTime( start );
  Calendar endCal = new GregorianCalendar();
  endCal.setTime( end );
  return calenderOfTheYear( startCal, endCal );
}

public static List<CalLine> calenderOfTheYear( Calendar start, Calendar end )
{
  List<CalLine> lines = new ArrayList<>();

  // Calender instances are mutable, so copy them
  Calendar startCal = (Calendar) start.clone(); 
  Calendar endCal   = (Calendar) end.clone(); 

  // For start date: first go backwards to the beginning of the month
  // then find monday of this week
  while ( startCal.get( Calendar.DAY_OF_MONTH ) != 1 )
    startCal.add( Calendar.DAY_OF_YEAR, -1 );
  while ( startCal.get( Calendar.DAY_OF_WEEK ) != Calendar.MONDAY )
    startCal.add( Calendar.DAY_OF_YEAR, -1 );

  // For end date: go forwards and find end of month
  // then find sunday of this week
  while ( endCal.get( Calendar.DAY_OF_MONTH ) != startCal.getActualMaximum( Calendar.DAY_OF_MONTH ) )
    endCal.add( Calendar.DAY_OF_YEAR, 1 );
  while ( endCal.get( Calendar.DAY_OF_WEEK ) != Calendar.SUNDAY )
    endCal.add( Calendar.DAY_OF_YEAR, 1 );
  endCal.add( Calendar.DAY_OF_YEAR, 1 );  // add 1 to test with < not <=

  CalLine line = new CalLine();

  while ( startCal.before( endCal ) ) {
    if ( line.year == 0 )
      line.year = startCal.get( Calendar.YEAR );
    if ( line.weekOfYear == 0 )
      line.weekOfYear = startCal.get( Calendar.WEEK_OF_YEAR );

    int dayOfMonth = startCal.get( Calendar.DAY_OF_MONTH );
    int dayOfWeek  = startCal.get( Calendar.DAY_OF_WEEK );

    if ( dayOfMonth == 1 )
      line.month = startCal.get( Calendar.MONTH );

    line.day[dayOfWeek - 1] = dayOfMonth;

    if ( dayOfWeek == Calendar.SUNDAY ) {
      // Days are Sun, Mon, ..., Sat. Rearange to Mon, ..., Sun 
      int first = line.day[ 0 ]; // This is faster then System.arraycopy()
      line.day[ 0 ] = line.day[ 1 ]; line.day[ 1 ] = line.day[ 2 ]; line.day[ 2 ] = line.day[ 3 ];
      line.day[ 3 ] = line.day[ 4 ]; line.day[ 4 ] = line.day[ 5 ]; line.day[ 5 ] = line.day[ 6 ];
      line.day[ 6 ] = first;

      lines.add( line );
      line = new CalLine();   // it ends always with SUN, last line is not added
    }

    startCal.add( Calendar.DAY_OF_YEAR, 1 );
  }

  return lines;
}

Beispielaufruf:

List<CalLine> lines = DateUtils.calenderOfTheYear( new GregorianCalendar( 2011, Calendar.NOVEMBER, 12 ), new GregorianCalendar( 2012, Calendar.JANUARY, 22 ) );

String[] monthNames = { "Jan", "Feb", "Mrz", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez" };
System.out.println( "KW        Mo Do Mi Do Fr Sa So" ); // to lazy for DateFormatSymbols here...

for ( CalLine l : lines ) {
  String monthStr = (l.month == -1) ? "   " : monthNames[ l.month ];
  String s = String.format( "%2d  %s   %(2d %(2d %(2d %(2d %(2d %(2d %(2d",
                            l.weekOfYear, monthStr,
                            l.day[0], l.day[1], l.day[2], l.day[3], l.day[4], l.day[5], l.day[6] ).replace( "(1)", "  " );
  System.out.println( s );
}

Das führt zu

KW        Mo Do Mi Do Fr Sa So
44  Nov   31  1  2  3  4  5  6
45         7  8  9 10 11 12 13
46        14 15 16 17 18 19 20
47        21 22 23 24 25 26 27
48  Dez   28 29 30  1  2  3  4
49         5  6  7  8  9 10 11
50        12 13 14 15 16 17 18
51        19 20 21 22 23 24 25
52  Jan   26 27 28 29 30 31  1
 1         2  3  4  5  6  7  8
 2         9 10 11 12 13 14 15
 3        16 17 18 19 20 21 22
 4        23 24 25 26 27 28 29
 5  Feb   30 31  1  2  3  4  5

Thema der Woche: Externe Programme starten, URL Protokoll unter Windows registrieren

Lies http://openbook.galileocomputing.de/javainsel/javainsel_11_008.html#dodtpd29fe557-8d1f-4a1a-a23e-ce87eda2454b

Schreibe ein Java-Programm, welches per Kommandozeile eine Bug-ID annimmt, und dann die entsprechende Seite unter http://bugs.sun.com/ aufruft. Beispiel: Zur Bug-ID 6787890 soll der Browser http://bugs.sun.com/view_bug.do?bug_id=6787890 ansteuern.

Lies http://msdn.microsoft.com/en-us/library/aa767914(v=vs.85).aspx

Kopiere folgendes in eine temporäre Datei, ersetzte dabei YourApp mit etwas Eigenem und passe den Pfad korrekt auf unser Bug-ID-Öffnen-Programm an. Das Protokoll kann zum Beispiel “sunbugid” sein.

REGEDIT4

[HKEY_CLASSES_ROOT\YourApp]
@=“URL:YourApp Protocol“
„URL Protocol“=““

[HKEY_CLASSES_ROOT\YourApp\DefaultIcon]
@=“\“C:\\Program Files\\YourApp\\YourApp.exe\““

[HKEY_CLASSES_ROOT\YourApp\shell]

[HKEY_CLASSES_ROOT\YourApp\shell\open]

[HKEY_CLASSES_ROOT\YourApp\shell\open\command]
@=“\“C:\\Program Files\\YourApp\\YourApp.exe\“ \“%1\“ \“%2\“ \“%3\“ \“%4\“ \“%5\“ \“%6\“ \“%7\“ \“%8\“ \“%9\““

(Quelle http://stackoverflow.com/questions/389204/how-do-i-create-my-own-url-protocol-e-g-so)

Führe die Datei mit regedit aus.

Steht dann auf einer Webseite die URL sunbugid:6787890 sollte bei der Aktivierung des Links das eigene Java-Programm ein neues Browserfenster mit der Bug-Beschreibung aufkommen.

Über eine Reihe von Werten laufen, Ist-Element-von-Test

Rechts vom Doppelpunkt lässt sich auf die Schnelle ein Feld aufbauen, über welches das erweiterte for dann laufen kann.

for ( int prime : new int[]{ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31 } )
  System.out.println( prime );

Das ist praktisch, um über eine feste Menge von Werten zu laufen. Das funktioniert auch für Objekte, etwa Strings:

for ( String name : new String[]{ "Krissy", "Morris", "Dan" } )
  System.out.println( name );

Einige Programmierer verstecken die Objekterzeugung auch in einen Methodenaufruf:

for ( String name : Arrays.asList( "Krissy", "Morris", "Dan" ) )
  System.out.println( name );

Arrays.asList(…) erzeugt kein Array als Rückgabe, sondern baut aus der variablen Argumentliste eine Sammlung auf, die von einem speziellen Typ Iterable ist – das kann auch die erweiterte for-Schleife ablaufen.

Unabhängig vom erweiterten for hat die Nutzung von Arrays.asList(…)noch einen anderen Vorteil, etwa bei ist-Element-von-Anfragen, etwa so:

if ( Arrays.asList( 1, 2, 3, 4, 5, 6, 7, 8, 10 ).contains( number ) )

  …

java.net.ConnectException? Was tun?

Falls der Computer keinen direkten Internetzugriff hat, kommt es zu einem Timeout. Der übliche Fehler ist eine „java.net.ConnectException: Operation timed out“. Hier gilt zu prüfen, woher der Fehler kommt. Eine Fehlerquelle sind Proxies, die zwischen dem eigenen Rechner und dem Internet hängen. Proxy-Einstellungen können in Java gesetzt werden, wie im im Kapitel über Proxies in der Insel beschrieben. Eine gute Idee ist, mithilfe des Kommandozeilenprogramms telnet[1] die Erreichbarkeit eines Servers zu überprüfen. Es kann auch sein, dass die Verbindung grundsätzlich besteht, sie jedoch langsam ist, und Java aus Ungeduld aufgibt. Die Lösung ist dann, den Timeout hochzusetzen, etwa bei dem URLConnection-Objekt über setConnectTimeout(millis).


[1] Muss in einem aktuellen Windows erst aktiviert werden.

Snippet: Kalender des Jahres ausgeben

Angeregt durch den Compact Calendar (http://davidseah.com/compact-calendar/) wollte ich etwas ähnliches in meine Webanwendung einbauen, sodass das Ergebnis tabellenartig wie https://docs.google.com/spreadsheet/ccc?key=0AkyxK00VLnSidEl1SS1sZjZiVlpuRnJIY1JmUW9IRHc#gid=0 formatiert wird.

import java.text.DateFormatSymbols;
import java.util.*;

public class PrintCalender
{
  public static class CalLine
  {
    int weekOfYear; 
    int month = -1;  // 0 <= month <= 11 
    int[] day = { -1, -1, -1, -1, -1, -1, -1 }; 
  }

  public static List<CalLine> calenderOfTheYear( int year )
  {
    Calendar cal = new GregorianCalendar( year, 1, 1 );

    List<CalLine> lines = new ArrayList<>();
    CalLine line = new CalLine();

    for ( int dayOfYear = 1; dayOfYear <= cal.getActualMaximum( Calendar.DAY_OF_YEAR ); dayOfYear++ ) {
      cal.set( Calendar.DAY_OF_YEAR, dayOfYear );
      line.weekOfYear = cal.get( Calendar.WEEK_OF_YEAR ); // Set several times, thats ok

      int dayOfMonth = cal.get( Calendar.DAY_OF_MONTH );
      int dayOfWeek  = cal.get( Calendar.DAY_OF_WEEK );

      if ( dayOfMonth == 1 )
        line.month = cal.get( Calendar.MONTH );

      line.day[dayOfWeek - 1] = dayOfMonth;

      if ( dayOfWeek == Calendar.SUNDAY ) {
        lines.add( line );
        line = new CalLine();
      }
    }
    lines.add( line );
    return lines;
  }
  
  public static void main( String[] args )
  {
    List<CalLine> lines = calenderOfTheYear( 2012 );

    String[] monthNames = new DateFormatSymbols( Locale.GERMANY ).getMonths();
    System.out.println( "KW        Mo Do Mi Do Fr Sa So" ); // to lazy for DateFormatSymbols here...

    for ( CalLine l : lines ) {
      String monthStr = (l.month == -1) ? "   " : monthNames[ l.month ].substring( 0, 3 );
      // Days are Sun, Mon, ..., Sat. Rearange to Mon, ..., Sun
      String s = String.format( "%2d  %s   %(2d %(2d %(2d %(2d %(2d %(2d %(2d",
                                l.weekOfYear, monthStr,
                                l.day[1], l.day[2], l.day[3], l.day[4], l.day[5], l.day[6], l.day[0] ).replace( "(1)", "  " );
      System.out.println( s );
    }
  }
}

Zur Demo gibt eine Textausgabe (mit einem Hack). Das Ergebnis für dieses Jahr:

kw        mo do mi do fr sa so
52  jan                      1
 1         2  3  4  5  6  7  8
 2         9 10 11 12 13 14 15
 3        16 17 18 19 20 21 22
 4        23 24 25 26 27 28 29
 5  feb   30 31  1  2  3  4  5
 6         6  7  8  9 10 11 12
 7        13 14 15 16 17 18 19
 8        20 21 22 23 24 25 26
 9  mär   27 28 29  1  2  3  4
10         5  6  7  8  9 10 11
11        12 13 14 15 16 17 18
12        19 20 21 22 23 24 25
13  apr   26 27 28 29 30 31  1
14         2  3  4  5  6  7  8
15         9 10 11 12 13 14 15
16        16 17 18 19 20 21 22
17        23 24 25 26 27 28 29
18  mai   30  1  2  3  4  5  6
19         7  8  9 10 11 12 13
20        14 15 16 17 18 19 20
21        21 22 23 24 25 26 27
22  jun   28 29 30 31  1  2  3
23         4  5  6  7  8  9 10
24        11 12 13 14 15 16 17
25        18 19 20 21 22 23 24
26  jul   25 26 27 28 29 30  1
27         2  3  4  5  6  7  8
28         9 10 11 12 13 14 15
29        16 17 18 19 20 21 22
30        23 24 25 26 27 28 29
31  aug   30 31  1  2  3  4  5
32         6  7  8  9 10 11 12
33        13 14 15 16 17 18 19
34        20 21 22 23 24 25 26
35  sep   27 28 29 30 31  1  2
36         3  4  5  6  7  8  9
37        10 11 12 13 14 15 16
38        17 18 19 20 21 22 23
39        24 25 26 27 28 29 30
40  okt    1  2  3  4  5  6  7
41         8  9 10 11 12 13 14
42        15 16 17 18 19 20 21
43        22 23 24 25 26 27 28
44  nov   29 30 31  1  2  3  4
45         5  6  7  8  9 10 11
46        12 13 14 15 16 17 18
47        19 20 21 22 23 24 25
48  dez   26 27 28 29 30  1  2
49         3  4  5  6  7  8  9
50        10 11 12 13 14 15 16
51        17 18 19 20 21 22 23
52        24 25 26 27 28 29 30
 1        31                  

			

Thema der Woche: Excel API

Abbruch-Signale

Das Betriebssystem sendet beim Abbruch eines Programms Signale. Für Windows/Unix-Systeme sind das zum Beispiel INT (Abbruch z. B. mit Strg-C), TERM (Aufforderung zur Terminierung), BREAK (Break-Anforderung vom Termin), usw.

Java kann diese unterschiedlichen Feinheiten des Programmabbruchs nicht auswerten, jedenfalls nicht mit einer erlaubten API. Es gibt aber zwei Klassen sun.misc.Signal und sun.misc.SignalHandler, die verwendet werden können, wenn Entwickler sich der Konsequenzen bewusst sind, dass diese API in Zukunft verschwinden könnte. Die Nutzung sieht so aus:

class MySignalHandler implements SignalHandler {

public void handle( Signal signal ) { … }

}

String signalName = "TERM";

handler = Signal.handle( new Signal( signalName ), new MySignalHandler() );

Weitere Informationen auch zu den unterschiedlichen Signaltypen liefern http://stackoverflow.com/questions/5023520/sending-signals-to-a-running-jvm und die referenzierten Unterseiten.

Gruppen in regulären Ausdrücken

In regulären Ausdrücken lassen sich Teilausdrücke in runde Klammen setzen und diese bilden dann Gruppen; im Englischen heißen sie capturing groups. Gruppen haben zwei Vorteile:

· An die Gruppe gesetzte Operationen wirken für die Gruppe. Beispiel: Der Ausdruck (\d\d)+ steht für gerade Anzahl von Ziffern.

· Auf eine Gruppe lässt sich gezielt zurückgreifen.

Beispiel 1

Ein String enthält zwei Zahlen, die mit einem Doppelpunkt getrennt sind. Wie interessieren uns für die erste und zweite Zahl:

Matcher matcher = Pattern.compile( "(\\d+):(\\d+)").matcher( "1:123" );

if ( matcher.find() )

System.out.println( matcher.group(1) + "," + matcher.group(2) ); // 1,123

Der Trick ist also, bei group(int) einen Index anzugeben; der beginnt bei 1.

Beispiel 2

Ein String aus einer Datei enthält einen Block in geschweiften Klammen. Wir interessieren uns für den Block.

String input = "line1\n{line2\nline3}\nline4";

Matcher matcher = Pattern.compile( "\\{(.*)\\}", Pattern.MULTILINE | Pattern.DOTALL ).matcher( input );

if ( matcher.find() )

  System.out.println( matcher.group( 1 ) ); // line1, dann Umbruch, line2

Bei der Angabe ohne Index, also group(), ist die Rückgabe alles was find() gefunden hat. Die Fundstellen sind natürlich Teilstrings der gesamten Gruppe.

Welche Jars braucht man mindestens für ein JPA-Hibernate-Beispiel?

  • javax.persistence-2.0.0.jar
  • hibernate-commons-annotations-4.0.1.Final.jar
  • hibernate-core-4.1.4.Final.jar
  • hibernate-entitymanager-4.1.4.Final.jar
  • hibernate-validator-4.3.0.Final.jar
  • javassist-3.7.ga.jar
  • jboss-logging-3.1.1.GA.jar
  • jta-1.1.jar
  • antlr-2.7.5H3.jar
  • dom4j-1.6.1.jar

Zu beziehen in einem Maven-Repository des Vertrauens oder gleich über Maven auflösen lassen.

Inselupdate: Property-Dateien mit java.util.Properties lesen und schreiben

Dateien, die Schlüssel-Werte-Paare als String repräsentieren und die Schlüssel und Wert durch ein Gleichheitszeichen trennen, nennen sich Property-Dateien. Sie kommen zur Programmkonfiguration häufig vor, und Java bietet mit der Klasse Properties die Möglichkeit, die Property-Dateien einzulesen und zu schreiben.

store() und load()-Methoden vom Properties Objekt

Die Methode store(…) dient zum Speichern der Zustände und load(…) zum Initialisieren eines Properties-Objekts aus einem Datenstrom. Die Schlüssel und Werte trennt ein Gleichheitszeichen. Die Lade-/Speicher-Methoden sind:

class java.util.Properties
extends Hashtable<Object,Object>

§ void store(OutputStream out, String comments)

§ void store(Writer writer, String comments)
Schreibt die Properties-Liste in des Ausgabestroms. Am Kopf der Datei wird eine Kennung geschrieben, die im zweiten Argument steht. Die Kennung darf null sein.

§ void load(InputStream inStream)

§ void load(Reader reader) throws IOException
Liest eine Properties-Liste aus einem Eingabestrom.

Ist der Typ ein Binärstrom also OutputStream/InputStream, so behandeln die Methoden die Zeichen in der ISO 8859-1 Kodierung. Reader/Writer erlauben eine freie Kodierung. Eine ähnliche Methode list(…) ist nur für Testausgaben gedacht ist, sie sollte nicht mit store(…) verwechselt werden.

Das folgende Beispiel initialisiert ein Properties-Objekt mit den Systemeigenschaften und fügt dann einen Wert hinzu. Anschließend macht store(…) die Daten persistent, load(…) liest sie wieder, und list(…) gibt die Eigenschaften auf dem Bildschirm aus:

Path path = Paths.get( "properties.txt" );

try ( Writer writer = Files.newBufferedWriter( path, StandardCharsets.UTF_8 ) ) {

Properties prop1 = new Properties( System.getProperties() );

prop1.setProperty( "MeinNameIst", "Forrest Gump" );

prop1.store( writer, "Eine Insel mit zwei Bergen" );

try ( Reader reader = Files.newBufferedReader( path, StandardCharsets.UTF_8 ) ) {

Properties prop2 = new Properties();

prop2.load( reader );

prop2.list( System.out );

}

}

catch ( IOException e ) {

e.printStackTrace();

}

Besonderheiten des Formats

Beginnt eine Zeile mit einem „#“ oder „!“ gilt sie als Kommentar und wird überlesen. Da der Schlüssel selbst aus einem Gleichheitszeichen bestehen kann, steht in dem Fall ein „\“ voran, folglich liefert Properties p = new Properties(); p.setProperty("=", "="); p.store(System.out, null); neben dem Kommentar die Zeile „\==\=“. Beim Einlesen berücksichtigen die Lesemethoden auch Zeilenumbrüche: eine Zeile darf mit \ enden und dann führt die folgende Zeile die vorangehende fort. Die Property „cars“ ist also “Honda, Mazda, BMW”, wenn steht:

cars = \

Honda, Mazda, \

BMW

Mit der internen Compiler-API auf den AST einer Klasse zugreifen

Der Zugriff zum Java-Compiler ist über die Java-Compiler-API standardisiert, jedoch sind alle Interna, wie die tatsächliche Repräsentation des Programmcodes verborgen. Die Compiler-API abstrahiert alles über Schnittstellen, und so kommen Entwickler nur mit JavaCompiler, StandardJavaFileManager und CompilationTask in Kontakt – alles Schnittstellen aus dem Paket javax.tools. Um etwas tiefer einzusteigen, lässt sich zum einem Trick greifen: Klassen implementieren Schnittstellen und wenn ein Programm den Schnittstellentyp auf den konkreten Klassentyp anpasst, dann stehen in der Regel mehr Methoden zur Verfügung. So lässt sich der CompilationTask auf eine com.sun.tools.javac.api.JavacTaskImpl casten und dann steht eine parse()-Methode für Verfügung. Die parse()-Methode liefert als Rückgabe eine Aufzählung von CompilationUnitTree. Um diesen Baum nun abzulaufen, lässt sich das Besuchermuster einsetzen. CompilationUnitTree bietet eine accept(…)-Methode; der übergeben wir einen TreeScanner. Die accept(…)-Methode ruft dann beim Ablaufen jedes Knotens unseren Besucher auf.

package com.tutego.tools.javac;

import java.io.*;
import java.net.*;
import javax.tools.*;
import javax.tools.JavaCompiler.CompilationTask;
import com.sun.source.tree.*;
import com.sun.source.util.TreeScanner;
import com.sun.tools.javac.api.JavacTaskImpl;

public class PrintAllMethodNames {

  final static TreeScanner<?, ?> methodPrintingTreeVisitor = new TreeScanner<Void, Void>() {
    @Override public Void visitCompilationUnit( CompilationUnitTree unit, Void arg ) {
      System.out.println( "Paket: " + unit.getPackageName() );
      return super.visitCompilationUnit( unit, arg );
    };
    @Override public Void visitClass( ClassTree classTree, Void arg ) {
      System.out.println( "Klasse: " + classTree.getSimpleName() );
      return super.visitClass( classTree, arg );
    }
    @Override public Void visitMethod( MethodTree methodTree, Void arg ) {
      System.out.println( "Methode: " + methodTree.getName() );
      return super.visitMethod( methodTree, arg );
    }
  };

  public static void main( String[] args ) throws IOException, URISyntaxException {
    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    StandardJavaFileManager fileManager = compiler.getStandardFileManager( null, null, null );
    URI filename = PrintAllMethodNames.class.getResource( "PrintAllMethodNames.java" ).toURI();
    Iterable<? extends JavaFileObject> fileObjects = fileManager.getJavaFileObjects( new File( filename ) );
    CompilationTask task = compiler.getTask( null, null, null, null, null, fileObjects );

    JavacTaskImpl javacTask = (JavacTaskImpl) task;

    for ( CompilationUnitTree tree : javacTask.parse() )
      tree.accept( methodPrintingTreeVisitor, null );
  }
}

Ein TreeScanner hat viele Methoden, wir interessieren uns nur für den Start einer Compilationseinheit für den Paketnamen, für alle Klassen und Methoden. Wir könnten uns aber auch über alle Annotationen oder do-while-Schleifen informieren lassen. Die Ausgabe ist:

Paket: com.tutego.tools.javac

Klasse: PrintAllMethodNames

Klasse:

Methode: visitCompilationUnit

Methode: visitClass

Methode: visitMethod

Methode: main

Die zweite Angabe für den Klassennamen ist leer, da die anonyme Klasse eben keinen Namen hat.

Inselupdate: Konstruktoren der Formatter Klasse

Die String.format(…)-Methode und prinf(…)-Methoden der Ein-/Ausgabeklassen übernehmen die Aufbereitung nicht selbst, sondern delegieren sie an die Klasse java.util.Formatter. Das ist auch der Grund, warum die Dokumentation für die Formatspezifizierer nicht etwa an String.format(…) hängt, sondern an Formatter.

Konstruktor vom Formatter

Die Klasse Formatter hat eine beeindruckende Anzahl von Konstruktoren:

· Formatter()

· Formatter(Appendable a)

· Formatter(Appendable a, Locale l)

· Formatter(File file)

· Formatter(File file, String csn)

· Formatter(File file, String csn, Locale l)

· Formatter(Locale l)

· Formatter(OutputStream os)

· Formatter(OutputStream os, String csn)

· Formatter(OutputStream os, String csn, Locale l)

· Formatter(PrintStream ps)

· Formatter(String fileName)

· Formatter(String fileName, String csn)

· Formatter(String fileName, String csn, Locale l)

Wird nicht der Standardkonstruktur eingesetzt, schreibt der Formatter in die angegebene Quelle. Daher ist die Klasse schön für das Schreiben von Texten in Dateien geeignet. Formatter implementiert Closeable, ist also auch AutoCloseable. Ein Beispiel zum Schreiben in Dateien:

try ( Formatter out = new Formatter( "ausgabe.txt", StandardCharsets.ISO_8859_1.name() ) ) {

for ( int i = 0; i < 10; i++ )

  out.format( "%02d%n", i );

}

catch ( FileNotFoundException | UnsupportedEncodingException e ) {

e.printStackTrace();

}

Inselupdate: Lokale und innere anonyme Klasse für einen Timer nutzen

Lokale Klasse für einen Timer nutzen

Damit die Beispiele etwas praxisnäher werden, wollen wir uns anschauen, wie ein Timer wiederholende Aufgaben ausführen kann. Die Java-Bibliothek bringt hier schon alles mit: Es gilt ein Exemplar von java.util.Timer() zu bilden und der Objektmethode scheduleAtFixedRate(…) ein Exemplar vom Typ TimerTask zu übergeben. Die Klasse TimerTask schreibt eine abstrakte Methode run() vor, in die der parallel und regelmäßig abzuarbeitende Programmcode gesetzt wird.

Nutzen wir das für ein Programm, welches uns sofort und regelmäßig daran erinnert, wie wichtig doch Sport ist:

public class SportReminder {

public static void main( String[] args ) {

  class SportReminderTask extends TimerTask {

   @Override public void run() {

    System.out.println( "Los, beweg dich du faule Wurst!" );

   }

  }

  new Timer().scheduleAtFixedRate( new SportReminderTask(), 0 /* ms delay */, 1000 /* ms period */ );

}

}

Unser Klasse SportReminderTask, die TimerTask erweitert, ist direkt in main(…) deklariert. Das erzeugte Exemplar kommt später in scheduleAtFixedRate(…) und los rennt der Timer, um uns jede Sekunde an die Wichtigkeit von Bewegung zu erinnern.

Nutzung einer anonymen innerer Klassen für den Timer

Eben gerade haben wir für den Timer extra eine neue lokale Klasse deklariert, aber genau genommen haben wir diese nur einmal nutzen müssen, nämlich um ein Exemplar bilden, und scheduleAtFixedRate(…) übergeben zu können. Das ist ein perfektes Szenario für anonyme innere Klassen. Aus

class SportReminderTask extends TimerTask {

@Override public void run() { … }

}

new Timer().scheduleAtFixedRate( new SportReminderTask(), … );

wird

new Timer().scheduleAtFixedRate( new TimerTask() {

@Override public void run() {

  System.out.println( "Los, …" );

}

},

0 /* ms delay */,

1000 /* ms period */);

Im Kern ist es also eine Umwandlung von new SportReminderTask() in new TimerTask() { … }. Von dem Klassennamen SportReminderTask ist nichts mehr zu sehen, das Objekt ist anonym.