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.

Inselupdate: Methode mit variabler Argumentanzahl (Vararg)

Bei vielen Methoden ist es klar, wie viele Argumente sie haben; eine Sinus-Methode bekommt nur ein Argument, equals(Object) ein Objekt, println(…) nichts oder genau ein Argument, usw. Es gibt jedoch Methoden, bei denen die Zahl mehr oder weniger frei ist. Ein paar Beispiele:

· Wenn der Aufruf System.out.printf(formatierungsstring, arg1, args2, arg3, …) etwas auf dem Bildschirm ausgibt, ist erst einmal nicht bekannt, wie viele Argumente die Methode besitzt, denn sie sind abhängig vom Formatierungsstring.

· Fügt Collections.addAll(sammlung, elem1, elem2, elem3, …) etwas einer Sammlung hin, ist frei, wie viele Elemente es sind.

· Wird ein Pfad für das Dateisystem zusammengebaut, ist vorher unbekannt, wie viele Segmente Paths.get( anfang, segment1, segment2, … ) besitzt.

· Startet new ProcessBuilder("kommando", "arg1", "arg1", …).start() ein Hintergrundprogramm, ist der Methode unbekannt, wie viele Argumente dem externen Programm übergeben werden.

Um die Anzahl der Parameter beliebig zu gestalten, sieht Java Methoden mit variabler Argumentanzahl, auch Varargs genannt, vor.

Eine Methode mit variabler Argumentanzahl nutzt die Ellipse (»…«) zur Verdeutlichung, dass eine beliebige Anzahl Argumente angegeben werden dürfen, dazu zählt auch die Angabe keines Elements. Der Typ fällt dabei aber nicht unter den Tisch; er wird ebenfalls angegeben.

System.out.printf() nimmt eine beliebige Anzahl von Argumenten an

Eine Methode mit Varargs haben wir schon einige Male verwendet: printf(…). Die Deklaration ist wie folgt:

class java.io.PrintStream extends FilterOutputStream implements Appendable, Closeable

PrintStream printf(String format, Object… args)
Nimmt eine beliebige Liste von Argumenten an und formatiert sie nach dem gegebenen Formatierungsstring format.

Gültige Aufrufe von printf(…) sind demnach:

Aufruf

Variable Argumentliste

System.out.printf("%n")

ist leer

System.out.printf("%s", "Eins")

besteht aus nur einem Element: "Eins"

System.out.printf("%s,%s,%s", "1", "2", "3")

besteht aus drei Elementen: "1", "2", "3"

Maximum eines Feldes finden

Die Klasse java.lang.Math sieht eine statische max(…)-Methode mit zwei Argumenten vor, doch grundsätzlich könnte die Methode auch beliebig viele Argumente entgegennehmen und von diesen Elementen das Maximum bilden. Die Methode könnte so aussehen:

static int max( int… array ) {
}

max(…) behandelt den Parameter array wie ein Feld. Da wir Argumente vom Typ int fordern, ist array vom Typ int[] und kann so zum Beispiel mit dem erweiterten for durchlaufen werden:

for ( int e : array )

Werden variable Argumentlisten in der Signatur definiert, so dürfen sie nur den letzten Parameter bilden; andernfalls könnte der Compiler bei den Parametern nicht unbedingt zuordnen, was nun ein Vararg und was schon der nächste gefüllte Parameter ist:

public class MaxVarArgs {

  static int max( int… array ) {
    if ( array == null || array.length == 0 )
      throw new IllegalArgumentException( "Array null oder leer" );
    int currentMax = Integer.MIN_VALUE;
    for ( int e : array )
      if ( e > currentMax )
        currentMax = e;
    return currentMax;
  }
  public static void main( String[] args ) {
    System.out.println( max(1, 2, 9, 3) );     // 9
  }
}

Tipp: Vararg-Design. Muss eine Mindestanzahl von Argumenten garantiert werden – bei max(…) sollten das mindestens zwei sein – ist es besser, eine Deklaration wie folgt zu nutzen: max(int first, int second, int… rest).

Compiler baut Array auf

Der Nutzer kann jetzt die Methode aufrufen, ohne ein Feld für die Argumente explizit zu definieren. Er bekommt auch gar nicht mit, dass der Compiler im Hintergrund ein Feld mit vier Elementen angelegt hat. So übergibt der Compiler:

System.out.println( max( new int[] { 1, 2, 9, 3 } ) );

An der Schreibweise lässt sich gut ablesen, dass wir ein Feld auch von Hand übergeben können:

int[] ints = { 1, 2, 9, 3 };
System.out.println( max( ints ) );

Hinweis. Da Varargs als Felder umgesetzt werden, sind überladene Varianten wie max(int… array) und max(int[] array), also einmal mit einem Vararg und einmal mit einem Feld, nicht möglich. Besser ist es hier, immer eine Variante mit Varargs zu nehmen, da diese mächtiger ist. Einige Autoren schreiben auch die Einstiegsmethode main(String[] args) mit variablen Argumenten, also main(String… args). Das ist gültig, denn im Bytecode steht ja ein Array.

Arrays.asList() Beispiel und Hinweis

Beispiel: Gib das größte Element eines Feldes aus.

Integer[] ints = { 3, 9, -1, 0 };

System.out.println( Collections.max( Arrays.asList( ints ) ) );

Zum Ermitteln des Maximums bietet die Utility-Klasse Arrays keine Methode, daher bietet sich die max(…)-Methode von Collections an. Auch etwa zum Ersetzen von Feldelementen bietet Arrays nichts, aber Collections. Sortieren und Füllen kann Arrays aber schon, hier muss asList() nicht einspringen.

Hinweis: Wegen der Generics ist der Parameter-Typ von asList() ein Objekt-Feld, aber niemals ein primitives Feld. In unserem Beispiel von eben würde so etwas wie

int[] ints = { 3, 9, -1, 0 };

Arrays.asList( ints );

zwar kompilieren, aber die Rückgabe von Arrays.asList(ints) ist vom Typ List<int[]>, was bedeutet, die gesamte Liste besteht aus genau einem Element und dieses Element ist das primitive Feld. Zum Glück führt Collections.max(Arrays.asList(ints)) zu einem Compilerfehler, denn von einer List<int[]>, also eine Liste von Feldern, kann max(Collection<? extends T>) kein Maximum ermitteln.

How to put files in a ZIP file with NIO.2

URI p = Paths.get( "c:/Users/Christian/Dropbox/jokes.zip" ).toUri();
URI uri = URI.create( "jar:" + p );

Map<String, String> env = new HashMap<>();
env.put( "create", "true" );
try ( FileSystem zipfs = FileSystems.newFileSystem( uri, env ) ) {
  Files.write( zipfs.getPath( "/j1.txt" ), "The truth is out there. Anybody got the URL?".getBytes() );
  Files.write( zipfs.getPath( "/j2.txt" ), "The more I C, the less I see.".getBytes() );
}

Designfrage: Wie nutzt man GWT-RPC-Aufrufe lokal etwa bei Offline-Anwendungen?

Standardmäßig sieht es ja im “normalen” entfernen RPC-Aufruf so aus: Als erstes die Schnittstelle:

public interface ContactRpcService extends RemoteService {

  Contact getContactById( long id );

}

Dann die zugehörige Async-Schnittstelle:

public interface ContactRpcServiceAsync {

  void getContactById( long id, AsyncCallback<Contact> callback );

}

Der Client hat nun so was wie

ContactRpcServiceAsync contactService = GWT.create( ContactRpcService.class );

contactService.getContactById( contactId, new DefaultCallback<Contact>() {

  @Override protected void handleResponse( Contact response ) {

    ….

  }

} );

DefaultCallback ist eine meine abstrakte Klasse, die AsyncCallback implementiert, aber das ist jetzt nicht so wichtig.

Auch etwa anders mache ich noch, damit ich weniger schreiben muss; ich habe mir eine Klasse Rpc deklariert, mit Konstanten für alle GWT-creates():

public class Rpc {

  private Rpc() {}

  public final static ContactRpcServiceAsync  contactService = GWT.create( ContactRpcService.class );

  // … un ddie Anderen

}

Normalerweise sieht es also bei mir so aus:

Rpc.contactService.getContactById( contactId, new DefaultCallback<Contact>() {

  @Override protected void handleResponse( Contact response ) {

    …

  }

} );

Damit nun Rpc.contactService.getContactById() im Offline-Modus etwas anderes macht, kann man zum Beispiel folgendes tun: Rpc.contactService stammt nicht von GWT.create(), sondern ist ein Proxy. Falls nun der Remote-Fall gewünscht ist, delegiert man an an die echte Rpc-Implementierung, andernfalls an eine lokale Variante.

public class Rpc {

  private Rpc() {}

  public final static ContactRpcServiceAsync  contactService = new ContactRpcServiceAsync() {

    private final ContactRpcServiceAsync delegate = GWT.create( ContactRpcService.class );

    private final ContactRpcServiceAsync local = new ContactRpcServiceAsync() {
      @Override public void getContactById( long id, AsyncCallback<Contact> callback ) {

        // hier alles lokale machen, also etwas aus dem Cache holen. Wenn alles gut geht, und die Daten vorhanden sind, dann aufrufen
        callback.onSuccess( result );
      }

    };

    @Override public void getContactById( long id, AsyncCallback<Contact> callback ) {

      wenn der remote Fall

        delegate.getContactById( id, callback );

      andernfalls

        local.getContactById( id, callback );

    }

  };

}

JPA-Beispiel in wenigen Minuten mit OpenJPA

  1. Beziehe unter http://mvnrepository.com/artifact/org.apache.openjpa/openjpa-all das openjpa-all-2.2.0.jar (etwas mehr als 6 MiB) und setzte es in den Klassenpfad.
  2. Habe einen Datentreiber im Klassenpfad (bei mir den der HSQLDB).
  3. Lege im Projekt einen Ordner META-INF an, platziere dort eine Datei persistence.xml:
  4. <?xml version="1.0" encoding="UTF-8"?>
    <persistence xmlns="http://java.sun.com/xml/ns/persistence"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://java.sun.com/xml/ns/persistence persistence_2_0.xsd"
      version="2.0">
      <persistence-unit name="traida" transaction-type="RESOURCE_LOCAL">
        <class>traida.shared.domain.Contact</class>
        <properties>
          <property name="openjpa.jdbc.DBDictionary" value="hsql"/>
          <property name="javax.persistence.jdbc.url" value="jdbc:hsqldb:file:hsqldbtest;user=sa" />
          <property name="javax.persistence.jdbc.driver" value="org.hsqldb.jdbcDriver" />
          <property name="openjpa.Log" value="DefaultLevel=ERROR, Tool=ERROR" />
          <property name="openjpa.jdbc.SynchronizeMappings" value="buildSchema(ForeignKeys=true)"/>
        </properties>
      </persistence-unit>
    </persistence>

    Mehr zu den Properties unter http://openjpa.apache.org/builds/apache-openjpa-2.2.1-SNAPSHOT/docs/docbook/manual/main.html.

  5. Lege eine Klasse traida.shared.domain.Contact an:
  6. @Entity
    public class Contact {
      @Id
      @GeneratedValue( strategy = GenerationType.IDENTITY )
      public Long id;
      public String name;
      // Setter/Getter ausgelassen
    }

  7. Schreibe eine main(String[])-Methode mit:
  8. EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory( "traida" );
    EntityManager entityManager = entityManagerFactory.createEntityManager();
    entityManager.getTransaction().begin();
    Contact c = new Contact();
    c.name = "Hallo Willi";
    entityManager.persist( c );
    entityManager.getTransaction().commit();
    System.out.println( entityManager.createQuery( "select c from Contact c" ).getResultList() );
    entityManager.close();

  9. Fertig, jetzt freuen.

GunZip-Kommandozeilenprogramm in Java

package com.tutego.insel.io.zip;

import java.io.*;
import java.nio.file.*;
import java.util.zip.GZIPInputStream;

public class gunzip {

  public static void main( String[] args ) {
    if ( args.length != 1 ) {
      System.err.println( "Benutzung: gunzip <source>" );
      return;
    }

    String filename = args[ 0 ];
    Path srcPath, destPath;

    if ( filename.toLowerCase().endsWith( ".gz" ) ) {
      srcPath  = Paths.get( filename );
      destPath = Paths.get( filename.replaceAll( "\\\\.gz$", "" ) );
    }
    else {
      srcPath  = Paths.get( filename + ".gz" );
      destPath = Paths.get( filename );
    }

    try ( InputStream is  = new GZIPInputStream( Files.newInputStream( srcPath ) ) ) {
      Files.copy( is, destPath );
    }
    catch ( IOException e ) {
      System.err.println( "Fehler: Kann nicht entpacken " + filename );
    }
  }
}

gzip Kommandozeilenprogramm mit wenigen Zeilen Quellcode

package com.tutego.insel.io.zip;

import java.io.*;
import java.nio.file.*;
import java.util.zip.GZIPOutputStream;

public class gzip {

  public static void main( String[] args ) {
    if ( args.length != 1 ) {
      System.err.println( "Benutzung: gzip <source>" );
      return;
    }

    try ( OutputStream gos = new GZIPOutputStream( Files.newOutputStream( Paths.get( args[ 0 ] + ".gz" ) ) ) ) {
      Files.copy( Paths.get( args[ 0 ] ), gos );
    }
    catch ( IOException e ) {
      System.err.println( "Fehler: Kann nicht packen " + args[ 0 ] );
    }
  }
}

Frage: Sollte man überall this. verwenden?

Ich verwende es nicht, aus einem guten Grunde: Ein this macht mir explizit deutlich, dass es eine lokale Variable gibt, die vom Namen hier die Objektvariable überdeckt, siehe typisches Beispiel vom Getter:

public void setAge( int age )
{
  this.age = age;
}

Wenn ich this. sehe, erwarte ich einfach, dass es eine Überschattung gibt und ich würde mich wundern, wenn this. einfach "nur so" verwendet wird. Ich finde es wichtig, dass man durch solche Idiome etwas aussagt.

java.io.File und NIO.2-Path: wo beide zusammenpassen und wo nicht

Die Klasse File ist schon immer da gewesen und stark mit dem lokalen Dateisystem verbunden. So findet sich der Typ File weiterhin bei vielen Operationen. Wenn Runtime.exec(String[] cmdarray, String[] envp, File dir) einen Hintergrundprozess startet, dann ist dir genau das Startverzeichnis. Eine Abstraktion auf virtuelle Dateisysteme ist unpassend und File passt als Typ sehr gut. Doch obwohl sich Pfade vom NIO.2-Typ Path schon an einigen Stellen in der Java-API finden lassen, sind doch immer noch viele APIs mit File ausgestattet. Dass ImageIO.read(File input) nur ein File-Objekt annimmt, aber kein Path-Objekt, um ein Bild zu laden ist schade, wo es doch auch eine read(InputStream) und read(URL)-Methode gibt. Die Bibliotheksdesigner haben bisher keine Notwendigkeit gesehen, das nachzubessern, vielleicht aus deswegen nicht, weil Entwickler die Möglichkeit haben, etwa mit Files.newInputStream(path) von einem Pfad einen Eingabestrom zu erfragen. Der Weg ist auch der Beste, denn vom Path ein File-Objekt zu erfragen und dann Methoden aufzurufen, die das File-Objekt annehmen, birgt eine Gefahr: von ein NIO.2-Dateisystem, etwa Zip, ein File-Objekt zu erfragen wird nicht funktionieren, weil es die Datei vom File-Objekt ja gar nicht im lokalen Verzeichnis gibt! Einige Klasse erwarten nur File-Objekte und nichts anderes, also auch kein Strom, und hier zeigt sich, dass diese Klassen nicht auf virtuellen Dateisystemen funktionieren können. Etwa der JFileChooser. Der operiert nur auf dem lokalen Dateisystem, was sich an JFileChooser.getSelectedFile() und JFileChooser.setCurrentDirectory(File dir) ablesen lässt.

Java und Sequenzpunkte in C(++)

Je mehr Freiheiten ein Compiler hat, desto ungenierter kann er optimieren. Besonders Schreibzugriffe interessieren Compiler, denn kann er diese einsparen kann, läuft das Programm später ein bisschen schneller. Damit das Resultat eines Compilers jedoch beherrschbar bleibt, definiert der C(++)-Standard Sequenzpunkte (eng. sequence point), an dem alle Schreibzugriffe klar zugewiesen wurden. (Dass der Compiler später Optimierungen macht ist eine andere Geschichte; die Sequenzpunkte gehören zum semantischen Modell, Optimierungen verändern das nicht). Das Semikolon als Abschluss von Anweisungen bildet zum Beispiel einen Sequenzpunkt. Im Ausdruck wie i = i + 1; j = i; muss der Schreizugriff auf i aufgelöst sein, bevor ein Lesezugriff für die Zuweisung zu j erfolgt. Problematisch ist, dass es gar nicht so viele Sequenzpunkte gibt, und es passieren kann, dass zwischen zwei Sequenzpunkten zwei mehrdeutige Schreibzugriffe auf die gleiche Variable stattfinden. Da das jedoch in C(++) undefiniert ist, kann sich der Compiler so verhalten wie er will – er muss sich ja nun an den Sequenzpunkten so verhalten wie gefordert. Problemfälle sind: (i=j) + i oder, weil ein Inkrement/Dekrement ein Lese-/Schreibzugriff ist, auch i = i++, was ja nichts anderes als i = (i = i + 1) ist. Bedauerlicherweise bildet die Zuweisung keinen Sequenzpunkt. Bei Zuweisungen der Art i = ++i + –i kann alles Mögliche später in i stehen, je nachdem, was der Compiler zu welchem Zeitpunkt ausführt. In Java sind diese Dinge von der Spezifikation klar geregelt, in C(++) ist nur geregelt, dass das Verhalten zwischen den Sequenzpunkten klar sein muss. Doch moderne Compiler erkennen konkurrierende Schreibzugriffe zwischen zwei Sequenzpunkten und mahnen sie (bei entsprechender Warnstufe) an.[1]

 



[1]       Beim GCC ist der Schalter -Wsequence-point (der auch bei -Wall mitgenommen wird), siehe dazu http://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html.

Putziger kleiner OR-Mapper: ORMLite, für JDBC und auch für Android

package tutego;

import java.sql.SQLException;
import com.j256.ormlite.dao.*;
import com.j256.ormlite.db.HsqldbDatabaseType;
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.jdbc.JdbcConnectionSource;
import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.table.*;

@DatabaseTable
class Contact
{
  @DatabaseField( generatedId = true )
  Long id;

  @DatabaseField
  String name;

  // Setter/Getter sparen
}

public class ORMLiteDemo
{
  public static void main( String[] args )
  {
    System.setProperty( "tutegoHsqldbDatabasePath", "TutegoDB" );
    String url = "jdbc:hsqldb:file:${tutegoHsqldbDatabasePath};shutdown=true";
    ConnectionSource connectionSource;
    try {
      connectionSource = new JdbcConnectionSource( url, "sa", "", new HsqldbDatabaseType() );
      Dao<Contact, String> dao = DaoManager.createDao( connectionSource, Contact.class );
//      TableUtils.createTable( connectionSource, Contact.class );

      Contact c1 = new Contact();
      c1.name = "Chris";
      dao.create( c1 );
      Contact c2 = new Contact();
      c2.name = "Juvy";
      dao.create( c2 );

      Contact c3 = dao.queryForId( "1" );
      System.out.println( c3.name );
      connectionSource.close();
    }
    catch ( SQLException e ) {
      e.printStackTrace();
    }
  }
}

1. Klassen annotieren mit den ORMLite-Annotationen oder mit JPA-Annotationen

2. Sind die Tabellen nicht da, muss man TableUtils.createTable( connectionSource, Contact.class ); aufrufen, dann erzeugt ORMLite die Tabellen.

3. Der Rest ist einfach, siehe Beispiel 🙂

Mehr unter http://ormlite.com/.

Neues Eclipse Release: 4.2 (Juno) ist fertig

Download wie üblich unter http://www.eclipse.org/downloads/. Die Neuigkeiten sind groß, http://download.eclipse.org/eclipse/downloads/drops4/R-4.2-201206081400/news/index.html, insbesondere für RCP-Entwickler:

Der 3er Zweig läuft aus.

PS: Unser tutego Eclipse Kurs http://tutego.de/g/ECLPSRCP/ schult auf Wunsch komplett auf Eclipse 4.