Aufzählung JDBCType, Schnittstelle SQLType in Java 8

Neu in Java 8 ist eine Aufzählung JDBCType, die knapp 40 JDBC-Typen enthält und anfängt mit ARRAY und BIGINT. Die Aufzählungstypen implementieren eine Schnittstelle SQLType, die ebenfalls neu in Java 8, und Methoden getName(), getVendor() und getVendorTypeNumber() vorschreibt.

Mit Hilfe von SQLType ist es nun möglich mit nur einer Methoden alle möglichen SQL-Typen zu schreiben. So findet sich in der PreparedStatement-Methode in Java 8 setObject(int parameterIndex, Object x, SQLType targetSqlType) was dann die JDBC-spezifischen Typ-Methoden wie setArray(int parameterIndex, Array x), setBigDecimal(int parameterIndex, BigDecimal x), … ergänzt.

@Native Markierungen in Java-Code für JNI

Zwischen Java-Programmen und nativen Programmen gibt es oft eine Wechselwirkung, dass Java-Programm native Funktionen aufrufen und diese wiederum Java-Methoden aufrufen oder auf Variablen zugreifen. Für den Fall, dass native Methoden auf Java-Konstanten zugreifen gibt es in Java 8 eine Annotation java.lang.annotation.Native, die genau diese Konstanten markiert. Auf diese Weise kann ein Generator-Werkzeug diese Java-Konstanten in native Header-Dateien kopieren, sodass für den Konstantenzugriff kein JNI-Aufruf nötig ist, sondern die Konstante aus der Header-Datei genommen werden kann. @Native hat den Retention-Typ SOURCE, ist also nur für Tools sichtbar, die auf der Code-Ebene arbeiten, etwa javac.

Unterprozess-Status erfragen und das Ende einleiten

Mit Methoden von Process lässt sich der Status des externen Programms erfragen und verändern. Die Methode waitFor(…) lässt den eigenen Thread so lange warten, bis das externe Programm zu Ende ist, oder löst eine InterruptedException aus, wenn das gestartete Programm unterbrochen wurde. Der Rückgabewert von waitFor() ist der Rückgabecode des externen Programms, eine zweite Variante von waitFor(…) wartet eine gegebene Zeit. Wurde das Programm schon beendet, liefert auch exitValue() den Rückgabewert. Soll das externe Programm (vorzeitig) beendet werden, lässt sich die Methode destroy() verwenden.

abstract class java.lang.Process

§ boolean isAlive()
Lebt der von der JVM gestartete Unterprozess noch?

§ abstract int exitValue()
Wenn das externe Programm beendet wurde, liefert exitValue() die Rückgabe des gestarteten Programms. Ist die Rückgabe 0, deutet das auf ein normales Ende hin. Läuft das Programm noch, gibt es eine IllegalThreadStateException.

§ abstract void destroy()
Beendet das externe Programm.

§ Process destroyForcibly()
Im Moment wie destroy(), kann von Unterklassen anders implementiert werden. Seit Java 8.

§ abstract void waitFor() throws InterruptedException
Wartet auf das Ende des externen Programms (ist es schon beendet, muss nicht gewartet werden), sonst blockiert die Methode, und liefert dann abschließend den exitValue().

§ boolean waitFor(long timeout, TimeUnit unit) throws InterruptedException
Wartet die angegebene Zeit auf das Ende des gestarteten Programms. Wurde das externe Programm schon beendet, kehrt die Methode sofort zurück und liefert true; den Exit-Code liefert exitValue() weil hier, anders als bei waitFor() der Rückgabecode ein boolean ist. Läuft das Programm noch, und ist es nicht nach timeout Zeiteinheiten beendet, kehrt die Methode mit false zurück. Die Rückgabe ist true, wenn das gestartete Programm in dem Zeitfenster beendet wurde. Hinweis: Die Methode bricht das externe Programm nicht ab, wenn nach überschreiten der Zeit noch läuft. Diese Logik muss ein Programmierer übernehmen, und if ( ! waitFor(…) ) mit destroy() kombinieren.

Überlauf in Java 8 erkennen

In Java 8 kommen neue Methoden hinzu, die eine Überlauferkennung ermöglichen. Die Methoden gibt es in Math und StrictMath:

class java.lang.Math

class java.lang.StrictMath

· static int addExact(int x, int y)

· static long addExact(long x, long y)

· static int subtractExact(int x, int y)

· static long subtractExact(long x, long y)

· static int multiplyExact(int x, int y)

· static long multiplyExact(long x, long y)

· static int toIntExact(long value)

Alle Methoden werfen eine ArithmeticException, falls die Operation nicht durchführbar ist, die letzte zum Beispiel, wenn (int)value != value ist. Leider deklariert Java keine Unterklassen wie UnderflowException oder OverflowException, und Java meldet nur alles vom Typ ArithmeticException mit der Fehlermeldung „xxx overflow“, auch wenn es eigentlich ein Unterlauf ist.

Beispiel: Von der kleinsten Ganzzahl mit subtractExact(…) eins abzuziehen führt zur Ausnahme:

subtractExact( Integer.MIN_VALUE, 1 ); // ArithmeticException

In Math, aber nicht in StrictMath, gibt es weiterhin:

class java.lang.Math

· static int incrementExact(int a)

· static long incrementExact(long a)

· static int decrementExact(int a)

· static long decrementExact(long a)

· static int negateExact(int a)

· static long negateExact(long a)

Kann wegen des Wertebereiches die Operationen nicht durchgeführt werden, folgt wieder eine ArithmeticException.

Vergleich mit C#: C# verhält sich genauso wie Java und reagiert standardmäßig nicht auf Überlauf. Es gibt jedoch spezielle checked-Blöcke, die eine OverflowException melden, wenn es bei arithmetischen Grundoperationen zu einem Überlauf kommt. Folgendes löst diese Ausnahme aus: checked { int val = int.MaxValue; val++; }. Solche checked-Blöcke gibt es in Java nicht, wer diese besondere Überlaufkontrolle braucht, muss die Methoden nutzen, und ein val++ dann auch umschreiben zu Math.addExact(val, 1) bzw. Math.incrementExact(val);

Math.nextUp(…) und nextDown(…)

Was kommt nach und vor 1:

System.out.printf( „%.16f%n“, Math.nextUp( 1 ) );
System.out.printf( „%.16f%n“, Math.nextDown( 1 ) );
System.out.printf( „%.16f%n“, Math.nextAfter( 1, Double.POSITIVE_INFINITY ) );
System.out.printf( „%.16f%n“, Math.nextAfter( 1, Double.NEGATIVE_INFINITY ) );

Die Ausgabe ist:

1,000000119209289

0,9999999403953552

1,0000001192092896

0,9999999403953552

nextUp(d) ist eine Abkürzung für nextAfter(d, Double.POSITIVE_INFINITY) und nextDown(d) eine Abkürzung für nextAfter(d, Double.NEGATIVE_INFINITY). Ist das zweite Argument von Math.nextAfter(…) größer als das erste, dann wird die nächstgrößere Zahl zurückgegeben, ist sie kleiner, dann die nächstkleinere Zahl. Bei Gleichheit kommt die gleiche Zahl zurück.

Division mit Rundung Richtung negativ unendlich, alternativer Restwert

Die Ganzzahldivision in Java ist simpel gestrickt. Vereinfacht ausgedrückt: Konvertiere die Ganzzahlen in Fließkommazahlen, führe die Division durch und schneide alles hinter dem Komma ab. So ergeben zum Beispiel 3 / 2 = 1 und 9 / 2 = 4. Bei negativem Ergebnis, durch entweder negativen Dividenden oder Divisor, das gleiche Spiel: -9 / 2 = -4 und 9 / -2 = -4. Schauen wir uns einmal die Rundungen an.

Ist das Ergebnis einer Division positiv und mit Nachkommaanteil, so wird das Ergebnis durch das Abschneiden der Nachkommastellen ein wenig kleiner, also abgerundet. Wäre 3/2 bei Fließkommazahlen 1,5, ist es bei einer Ganzzahldivision abgerundet 1. Bei negativen Ergebnissen einer Division ist das genau anders herum. Denn durch das Abschneiden der Nachkommastellen wird die Zahl etwas größer. -3/2 ist genau genommen -1,5, aber bei der Ganzzahldivision -1. Doch -1 ist größer als -1,5. Java wendet ein Verfahren an, was gegen null rundet.

Methoden für Division gegen minus unendlich, floorDiv(…)

In Java 8 hat die Mathe-Klasse zwei neue Methoden bekommen, die bei negativem Ergebnis einer Division nicht gegen null runden, sondern gegen negativ unendlich, also auch in Richtung der kleineren Zahl, wie es bei den positiven Ergebnissen ist.

class java.lang.Math

class java.lang.StrictMath

  • static int floorDiv(int x, int y)
  • static long floorDiv(long x, long y)

Ganz praktisch heißt das: 4/2 = Math.floorDiv(4, 3) = 1 ist, aber wo -4 / 3 = -1 ergibt, liefert Math.floorDiv(-4, 3) = -2.

Methoden für Division gegen minus unendlich, floorMod(…)

Die Division taucht indirekt auch bei der Berechnung des Restwertes auf. Zur Erinnerung: der Zusammenhang zwischen Division a/b und Restwert a%b (a heißt Dividend, b Divisor) ist (int)(a/b) * b + (a%b) = a. In der Gleichung gibt es eine Division, doch da es mit a / b und floorDiv(a, b) zwei Arten von Divisionen gibt, muss es folglich auch zwei Arten von Restwertbildung geben, die sich dann unterscheiden, wenn die Vorzeichen unterschiedlich sind. Neben a % b gibt es daher die Bibliotheksmethode floorMod(a, b) und der Zusammenhang zwischen floorMod(…) und floorDiv(…) ist: floorDiv(a, b) * b + floorMod(a, b) == b. Nach einer Umformung der Gleichung folgt floorMod(a, b) = a – (floorDiv(a, b) * b). Das Ergebnis ist im Bereich -abs(b) und abs(b) und das Vorzeichen des Ergebnisses bestimmt der Divisor b (beim %-Operator ist es der Dividend a).

Die Javdoc zeigt ein Beispiel mit den Werten 4 und 3 und unterschiedlichen Vorzeichen aus:

floorMod(…)-Methode

%-Operator

floorMod(+4, +3) == +1

+4 % +3 == +1

floorMod(+4, -3) == -2

+4 % -3 == +1

floorMod(-4, +3) == +2

-4 % +3 == -1

floorMod(-4, -3) == -1

-4 % -3 == -1

Ergebnis unterschiedlicher Restwertbildung

Sind die Vorzeichen für a und b identisch, so ist auch das Ergebnis von floorMod(a, b) und a % b gleich. Die Beispiele in der Tabelle macht auch den Unterschied im Ergebnis-Vorzeichen deutlich, welches einmal vom Dividenden (%) und einem vom Divisor (floorMod(…)) kommt. Die komplett anderen Ereignisse (vom Vorzeichen einmal abgesehen) beim Paar (+4,-3) und (-4,+3) ergeben sich ganz einfach aus unterschiedlichen Ergebnissen der Division: floorMod(+4, -3) = +4 – (floorDiv(+4, -3) * -3) = +4 – (-2 * -3) = +4 – +6 = -2, während +4 % -3 = +4 – (+4/-3 * -3) = +4 – (-1 * -3) = +4 – 3 = +1.

class java.lang.Math

class java.lang.StrictMath

  • static int floorMod(int x, int y)
  • static long floorMod(long x, long y)

Google Guava Closeables

To shorten things and not to repeat ourselves (usually the closing always looks the same) Google Commons offers the utility class com.google.common.io.Closeables. In this class you can find two static helper methods close(Closeable) and closeQuietly(Closeable). Both take an argument of type Closeable—like a FileInputStream—and call the close() method on this object eventually. If the argument is null, nothing happens; in our hand-coded version we use an extra if (out != null) to prevent a NullPointerException from out.close() if out is null.

The following example uses Closeable.closeQuietly(Closeable) to close a stream. Any potential IOExceptions caused by close() is swallowed by closeQuietly().

package com.tutego.googlecommon.io;

import java.io.*;

import java.util.logging.*;

import com.google.common.io.*;

public class CloseablesDemo {

private static final Logger log = Logger.getLogger( CloseablesDemo.class.getName() );

public static void main( String[] args ) {

InputStream in = null;

try {

in = Resources.getResource( CloseablesDemo.class, „test.txt“ ).openStream();

BufferedReader br = new BufferedReader( new InputStreamReader( in, „utf-8“ ) );

System.out.println( br.readLine() );

} catch ( IOException e ) {

log.log( Level.SEVERE, „IOException thrown while reading line“, e );

} finally {

Closeables.closeQuietly( in );

}

}

}

Using closeQuietly() shortens a program but it does not notify about any exception caused by the inner close() method. A lot of programmers ignore this exception and shortens there closing block to

try { out.close(); } catch ( Exception e ) {}

This style is dangerous, usually not for readers but for all modifying writers. If a writing stream can’t be closed the IOException shows a severe problem that probably the output is not complete and data is missing. For that reason the utility class Closeable offers a second message, close() which, in contrast to closeQuietly() first is denoted by an IOException clause and secondly controls with a boolean parameter if in case of an IOException caused by close() this exception should be swallowed or rethrown.

To summarize these methods:

class Closeables

static void close(Closeable closeable, boolean swallowIOException) throws IOException

Closes the closeable if it is not null. The boolean parameter controls whether an exception will be rethrown (pass false for swallowIOException) or swallowed (pass true).

static void closeQuietly Closeable closeable)

If closeable is not null this method calls closeable.close(). If close() throws an IOException this exception is swallowed by closeQuietly().Because closeQuietly(closeable) swallows the exception it is internally written as close(closeable, true).

Rekursiv nach Dateien/Ordnern suchen mit Files.find(…)

Neu in Java 8 ist die Methode find(…) in Files um Dateien nach gewissen Kriterien zu finden.

final class java.nio.file.Files

  • static Stream<Path> find(Path start, int maxDepth, BiPredicate<Path,BasicFileAttributes> matcher, FileVisitOption… options) throws IOException
    Sucht einen Verzeichnisbaum rekursiv ab und wendet auf jede Path den Filter (Prädikat) an. Falls der Filter zusagt, kommt der Path in den Ergebnis-Stream.

Beispiel und Hinweis: Finde alle Ordner unter dem Standard-Windows Bilder-Verzeichnis und gib sie aus:

Files.find( Paths.get( System.getProperty( „user.home“ ) )

.resolve( „Pictures“ ),

Integer.MAX_VALUE,

(p,attr) -> Files.isReadable( p ) && attr.isDirectory()

).forEach( System.out::println );

Intern greift find(…) auf den gleichen Mechanismus wie walk(…) zurück, doch ist eine Eigenimplementierung mit Hilfe von walk(…) mitunter besser, da wir beim visitFileFailed(…) Fehler ignorieren können – bei find(…) führen Fehler direkt zum Abbruch. Bei Windows führt eine rekursive Suche schnell zu einem java.nio.file.AccessDeniedException durch einen Ordner, bei dem Java nicht dran darf und dann ist mit find(…) sofort Schluss.

Rekursives Ablaufen des Verzeichnisbaums mit Stream oder FileVisitor

Die Utility-Klasse Files bietet vier statische Methoden (zwei mehr in Java 8), die, bei einem Startordner beginnend, die Verzeichnisse rekursiv ablaufen.

final class java.nio.file.Files

  • static Stream<Path> walk(Path start, FileVisitOption… options) throws IOException
  • static Stream<Path> walk(Path start, int maxDepth, FileVisitOption… options) throws IOException
  • static Path walkFileTree(Path start, FileVisitor<? super Path> visitor)
  • static Path walkFileTree(Path start, Set<FileVisitOption> options, int maxDepth, FileVisitor<? super Path> visitor)

Bei allen Varianten bestimmt der erste Parameter den Startordner. Während walk(…)einen java.util.stream.Stream liefert (die Methoden sind neu in Java 8), erwarten die anderen beiden walkFileTree(…)-Methoden ein Objekt mit Callback-Methoden, die walkFileTree(…) beim Ablaufen des Verzeichnisbaums aufruft

Verzeichnislistings (DirectoryStream/Stream) holen

In der Klasse Files finden sich vier Methoden (eine mehr unter Java 8), um zu einem gegebenen Verzeichnis alle Dateien und Unterverzeichnisse aufzulisten:

final class java.nio.file.Files

  • static Stream<Path> list(Path dir) throws IOException (neu in Java 8)
  • static DirectoryStream<Path> newDirectoryStream(Path dir) throws IOException
  • static DirectoryStream<Path> newDirectoryStream(Path dir,
    DirectoryStream.Filter<? super Path> filter) throws IOException
  • static DirectoryStream<Path> newDirectoryStream(Path dir, String glob) throws IOException

Die Rückgabe DirectoryStream<T> ist ein Closeable (und somit AutoCloseable) sowie Iterable<T>, und so unterscheidet sich die Möglichkeit zur Anfrage der Dateien im Ordner grundsätzlich von der Methode list(…) in der Klasse File, die immer alle Dateien in einem Feld auf einmal zurückliefert. Bei einem DirectoryStream wird Element für Element über den Iterator geholt; trotz des Namensanhangs „Stream“ ist der DirectoryStream kein Strom im Sinne von java.util.stream. Ein Stream<String> hingegen liefert die kompakte Methode list(Path), sie nutzt intern einen DirectoryStream.

try ( DirectoryStream<Path> files =
Files.newDirectoryStream( Paths.get( „c:/“ ) ) ) {
  for ( Path path : files )
    System.out.println( path.getFileName() );
}

Aus der Tatsache, dass die Dateien und Unterverzeichnisse nicht in einem Rutsch geholt werden, leitet sich die Konsequenz ab, dass der DirectoryStream/Stream<String> geschlossen werden muss, da nicht klar ist, ob der Benutzer wirklich alle Dateien abholt oder nach den ersten 10 Einträgen aufhört. Die Schnittstelle DirectoryStream erweitert die Schnittstelle Closeable (und die ist AutoCloseable, weshalb unser Beispiel ein try-mit-Ressourcen nutzt) und Stream implementiert AutoCloseable, daher ist es guter Stil, den DirectoryStream/Stream am Ende zu schließen, um blockierte Ressourcen freizugeben. try-mit-Ressourcen gibt immer etwaige Ressourcen frei, auch wenn es beim Ablaufen des Verzeichnisses zu einer Ausnahme kam.

Dateiinhalte lesen/schreiben mit Utility-Klasse Files

Da die Klasse Path nur Pfade, aber keine Dateiinformationen wie die Länge oder Änderungszeit repräsentiert und Path auch keine Möglichkeit bietet, Dateien anzulegen und zu löschen, übernimmt die Klasse Files diese Aufgaben.

Einfaches Einlesen und Schreiben von Dateien

Mit den Methoden readAllBytes(…), readAllLines(…), lines(…) bzw. write(…) kann Files einfach ein Dateiinhalt einlesen oder Strings bzw. ein Byte-Feld schreiben.

URI uri = ListAllLines.class.getResource( „/lyrics.txt“ ).toURI();
Path path = Paths.get( uri );
System.out.printf( „Datei ‚%s‘ mit Länge %d Byte(s) hat folgende Zeilen:%n“, path.getFileName(), Files.size( path ) );
int lineCnt = 1;
for ( String line : Files.readAllLines( path /*, StandardCharsets.UTF_8 vor Java 8 */) )
  System.out.println( lineCnt++ + „: “ + line );

final class java.nio.file.Files

  • static long size(Path path) throws IOException
    Liefert die Größe der Datei.
  • static byte[] readAllBytes(Path path) throws IOException
    Liest die Datei komplett in ein Byte-Feld ein.
  • static List<String> readAllLines(Path path) throws IOException (Java 8)
    static List<String> readAllLines(Path path, Charset cs) throws IOException
  • Liest Zeile für Zeile die Datei ein und liefert eine Liste dieser Zeilen. Optional ist die Angabe einer Kodierung, standardmäßig ist es StandardCharsets.UTF_8.
  • static Path write(Path path, byte[] bytes, OpenOption… options) throws IOException
    Schreibt eine Byte-Feld in eine Datei.
  • static Path write(Path path, Iterable<? extends CharSequence> lines,
    OpenOption… options) throws IOException (Java 8)
  • static Path write(Path path, Iterable<? extends CharSequence> lines, Charset cs,
    OpenOption… options) throws IOException
    Schreibt alle Zeilen aus dem Iterable in eine Datei. Optional ist die Kodierung, die StandardCharsets.UTF_8 ist, wenn nicht anders angegeben.
  • static Stream<String> lines(Path path)
  • Stream<String> lines(Path path, Charset cs)
    Liefert einen Stream von Zeilen einer Datei. Optional ist die Angabe der Kodierung, die sonst standardmäßig StandardCharsets.UTF_8 ist. Beide Methoden sind neu in Java 8.

Die Aufzählung OpenOption ist ein Vararg, und daher sind Argumente nicht zwingend nötig. StandardOpenOption ist eine Aufzählung vom Typ OpenOption mit Konstanten wie APPEND, CREATE, …

Hinweis: Auch wenn es naheliegt, die Files-Methode zum Einlesen mit einem Path-Objekt zu füttern, das ein HTTP-URI repräsentiert, funktioniert dies nicht. So liefert schon die erste Zeile des Programms eine Ausnahme des Typs »java.nio.file.FileSystemNotFoundException: Provider „http“ not installed«.

Path path = Paths.get( new URI „http://tutego.de/javabuch/aufgaben/bond.txt“ ) );
List<String> content = Files.readAllBytes( path );
System.out.println( content );

Vielleicht kommt in der Zukunft ein Standard-Provider von Oracle, doch es ist davon auszugehen, dass quelloffene Lösungen diese Lücke schließen werden. Schwer zu programmieren sind Dateisystem-Provider nämlich nicht.

Datenströme kopieren

Sollen die Daten nicht direkt aus einer Datei in eine byte-Feld/String-Liste gehen bzw. aus einer byte-Feld/String-Sammlung in eine Datei, sondern von einer Datei in einen Datenstrom, so bieten sich zwei copy(…)-Methoden an:

final class java.nio.file.Files

  • static long copy(InputStream in, Path target, CopyOption… options)
    Entleert den Eingabestrom und kopiert die Daten in die Datei.
  • static long copy(Path source, OutputStream out)
    Kopiert alle Daten aus der Datei in den Ausgabestrom.

Zeichenorientierte Datenströme über Files beziehen

Neben den statischen Files-Methoden newOutputStream(…) und newInputStream(…) gibt es zwei Methoden, die zeichenorientierte Ströme liefern, also Writer/Reader.

final abstract java.nio.file.Files

  • static BufferedReader newBufferedReader(Path path, Charset cs)
    throws IOException
  • static BufferedWriter newBufferedWriter(Path path, Charset cs, OpenOption… options)
    throws IOException
    Liefert einen Unicode-zeichenlesenden Ein-/Ausgabestrom. Das Charset-Objekts bestimmt, in welcher Zeichenkodierung sich die Texte befinden, damit sie korrekt in Unicode konvertiert werden.
  • static BufferedReader newBufferedReader(Path path)
    throws IOException
    Entspricht newBufferedReader(path, StandardCharsets.UTF_8). Erst in Java 8.
  • static BufferedWriter newBufferedWriter(Path path, OpenOption… options)
    throws IOException
    Entspricht Files.newBufferedWriter(path, StandardCharsets.UTF_8, options). Erst in Java 8,

BufferedReader und BufferedWriter sind Unterklassen von Reader/Writer die zum Zwecke der Optimierung Dateien im internen Puffer zwischenspeichern.

MAC-Adressen auslesen

Die MAC-Adresse (von Media-Access-Control) ist eine (im Idealfall) eindeutige Adresse eines Netzwerkgeräts. MAC-Adressen sind für Ethernet-Verbindungen essenziell, da auf der physikalischen Übertragungsebene Signale zu einer gewünschten Netzwerkkarte aufgebaut werden. Wegen der Eindeutigkeit eignen sie sich gut als Schlüssel, und es ist interessant, auch in Java diese Adresse auszulesen. Das geht mit der Klasse NetworkInterface recht unkompliziert. Alle lokale Netzwerkschnittstellen liefert NetworkInterface.getNetworkInterfaces(), ist die IP-Adresse bekannt, können wir NetworkInterface.getByInetAddress(InetAddress) nutzen.

for ( NetworkInterface ni : Collections.list( NetworkInterface.getNetworkInterfaces() ) ) {
  byte[] adr = ni.getHardwareAddress();
  if ( adr == null || adr.length != 6 )
    continue;
  String mac = String.format( "%02X:%02X:%02X:%02X:%02X:%02X",
                                    adr[0], adr[1], adr[2], adr[3], adr[4], adr[5] );
  System.out.println( mac );
}

Auf der Windows Kommandozeile liefert ipconfig /all alle MAC-Adressen, die dort „physikalische Adresse” heißen.

JavaFX: Put a draggable, resizable polygon on an image

import java.util.concurrent.atomic.AtomicInteger;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.EventHandler;
import javafx.scene.Cursor;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Polygon;
import javafx.stage.Stage;

class DrawingPane extends Pane
{
  public DrawingPane( Image image, Polygon poly )
  {
    poly.setFill( Color.web( "ANTIQUEWHITE", 0.8 ) );
    poly.setStroke( Color.web( "ANTIQUEWHITE" ) );
    poly.setStrokeWidth( 2 );

    getChildren().addAll( new ImageView( image ), poly );

    for ( int i = 0; i < poly.getPoints().size(); i += 2 ) {
      Circle circle = new Circle( poly.getPoints().get( i ), poly.getPoints().get( i + 1 ), 5 );
      circle.setFill( Color.web( "PERU", 0.8 ) );
      circle.setStroke( Color.PERU );
      circle.setStrokeWidth( 2 );

      AtomicInteger polyCoordinateIndex = new AtomicInteger( i );
      circle.centerXProperty().addListener( new ChangeListener<Number>() {
        @Override
        public void changed( ObservableValue<? extends Number> observable, Number oldValue, Number newValue ) {
          poly.getPoints().set( polyCoordinateIndex.get(), newValue.doubleValue() );
        }
      } );
      circle.centerYProperty().addListener( new ChangeListener<Number>() {
        @Override
        public void changed( ObservableValue<? extends Number> observable, Number oldValue, Number newValue ) {
          poly.getPoints().set( polyCoordinateIndex.get() + 1, (Double) newValue );
        }
      } );
      setDragHandler( circle );
      getChildren().add( circle );
    }
  }

  private double dragDeltaX, dragDeltaY; 

  private void setDragHandler( Circle circle )
  {
    circle.setOnMousePressed( new EventHandler<MouseEvent>() {
      @Override public void handle( MouseEvent mouseEvent ) {
        dragDeltaX = circle.getCenterX() - mouseEvent.getSceneX();
        dragDeltaY = circle.getCenterY() - mouseEvent.getSceneY();
      }
    } );

    circle.setOnMouseDragged( new EventHandler<MouseEvent>() {
      @Override public void handle( MouseEvent mouseEvent ) {
        circle.setCenterX( mouseEvent.getSceneX() + dragDeltaX );
        circle.setCenterY( mouseEvent.getSceneY() + dragDeltaY );
        circle.setCursor( Cursor.MOVE );
      }
    } );

    circle.setOnMouseEntered( new EventHandler<MouseEvent>() {
      @Override public void handle( MouseEvent mouseEvent ) {
        circle.setCursor( Cursor.HAND );
      }
    } );

    circle.setOnMouseReleased( new EventHandler<MouseEvent>() {
      @Override public void handle( MouseEvent mouseEvent ) {
        circle.setCursor( Cursor.HAND );
      }
    } );
  }
}

public class JavafxDemo extends Application
{
  @Override
  public void start( Stage stage )
  {
    Image image = new Image( "http://tours-tv.com/uploads/maps/map-Medizinische-Hochschule-Hannover-karta.jpg" );
    Polygon poly = new Polygon( 10, 10, 100, 10, 200, 100, 50, 200 );

    stage.setScene( new Scene( new DrawingPane( image, poly ), 450, 300 ) );
    stage.show();
  }

  public static void main( String[] args )
  {
    launch( args );
  }
}