mirror of
https://github.com/jneug/zeichenmaschine.git
synced 2026-04-14 06:33:34 +02:00
Compare commits
47 Commits
v0.0.34-SN
...
particels
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ab9701629 | ||
|
|
3196914564 | ||
|
|
a7747aea70 | ||
|
|
1d41bf36c5 | ||
|
|
595bdd7556 | ||
|
|
df10d38184 | ||
| fc3039b484 | |||
| 6d1d47fed0 | |||
| 3342db6f79 | |||
| 3f07b9ee7e | |||
| 4e7adf26f7 | |||
| 02085c83fc | |||
| ef248a8580 | |||
| 39014fe82e | |||
| e451a2f087 | |||
| 936043bf85 | |||
| d75f67c0fa | |||
| adbc29dabe | |||
| b687483e6d | |||
| 19bacd15e9 | |||
| 2d4abf6f0d | |||
| cefe7c8cfa | |||
| b04e68c7bd | |||
| 25ce3a35e9 | |||
| 44d0f79c6c | |||
| 43195aa63c | |||
| 6cc23de620 | |||
| 836571ca95 | |||
| 5232057b15 | |||
| f0a3c65552 | |||
| 2c40e1ba31 | |||
| 26e3593f2c | |||
| 13cad69e1d | |||
| bd718ba27d | |||
| 1895378978 | |||
| 2f845bdcd9 | |||
| 788ed888e9 | |||
| ce3ffee4da | |||
| 74c85e0f61 | |||
| c7b2a520c4 | |||
| c5c046521b | |||
| 9834b9c389 | |||
| eaaca6b90f | |||
| 1275af55f3 | |||
| 632038030e | |||
| cec17f0d7c | |||
| b29532bf6e |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -34,6 +34,7 @@ hs_err_pid*
|
|||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
||||||
.gradle
|
.gradle
|
||||||
|
local.properties
|
||||||
**/build/
|
**/build/
|
||||||
!src/**/build/
|
!src/**/build/
|
||||||
|
|
||||||
|
|||||||
19
CHANGELOG.md
19
CHANGELOG.md
@@ -6,6 +6,25 @@ und diese Projekt folgt [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## Added
|
||||||
|
- Dokumentation erweitert.
|
||||||
|
- Caching-Mechanismen in Klasse `util.Cache` ausgelagert.
|
||||||
|
- `util.io.ImageLoader` und `util.io.FontLoader` verwenden `Cache`.
|
||||||
|
- `mouseWheelMoved` Eventhandler für Mausrad.
|
||||||
|
- `DrawingLayer.imageRotate(...)` Methoden, um Bilder um ihr Zentrum gedreht zu zeichnen.
|
||||||
|
|
||||||
|
## Changed
|
||||||
|
- Die Methoden in `Validator` erwarten nun als zweiten Parameter den Namen des Parameters, der geprüft wird.
|
||||||
|
- `DrawingLayer.image(...)` mit Größenänderung umbenannt zu `imageScale(...)`.
|
||||||
|
- Klassen in `schule.ngb.zm.util.io` werfen nun nur eine Warnung ohne Stack-Trace, wenn die Ressource nicht gefunden werden konnte.
|
||||||
|
|
||||||
|
## Fixed
|
||||||
|
- `Constants.choice(int...)` und `Constants.choice(double...)` wiederhergestellt.
|
||||||
|
- Timing-Problem beim Aufruf von `AudioListener.playbackStopped()` in `Sound` behoben.
|
||||||
|
|
||||||
|
## Removed
|
||||||
|
- `layers.Shape2DLayer` ist nur noch im Test-Paket verfügbar.
|
||||||
|
|
||||||
## Version 0.0.34
|
## Version 0.0.34
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
115
build.gradle
115
build.gradle
@@ -1,10 +1,11 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'idea'
|
id 'idea'
|
||||||
id 'java-library'
|
id 'java-library'
|
||||||
|
id 'org.hidetake.ssh' version '2.10.1'
|
||||||
}
|
}
|
||||||
|
|
||||||
group 'schule.ngb'
|
group 'schule.ngb'
|
||||||
version '0.0.34-SNAPSHOT'
|
version '0.0.35-SNAPSHOT'
|
||||||
|
|
||||||
java {
|
java {
|
||||||
withSourcesJar()
|
withSourcesJar()
|
||||||
@@ -19,6 +20,15 @@ repositories {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
remotes {
|
||||||
|
uberspace {
|
||||||
|
host = 'westphal.uberspace.de'
|
||||||
|
user = 'ngb'
|
||||||
|
identity = file("${System.properties['user.home']}/.ssh/uberspace_rsa")
|
||||||
|
knownHosts = allowAnyHosts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
runtimeOnly 'com.googlecode.soundlibs:jlayer:1.0.1.4'
|
runtimeOnly 'com.googlecode.soundlibs:jlayer:1.0.1.4'
|
||||||
runtimeOnly 'com.googlecode.soundlibs:tritonus-share:0.3.7.4'
|
runtimeOnly 'com.googlecode.soundlibs:tritonus-share:0.3.7.4'
|
||||||
@@ -33,16 +43,111 @@ dependencies {
|
|||||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
|
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
|
||||||
}
|
}
|
||||||
|
|
||||||
test {
|
jar {
|
||||||
useJUnitPlatform()
|
manifest {
|
||||||
|
attributes 'Class-Path': '.'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register('jarMP3SPI', Jar) {
|
tasks.register('jarMP3SPI', Jar) {
|
||||||
archiveClassifier = 'all'
|
group "build"
|
||||||
|
description "Build jar with MP3SPI included"
|
||||||
|
|
||||||
|
archiveClassifier = 'mp3spi'
|
||||||
duplicatesStrategy = 'exclude'
|
duplicatesStrategy = 'exclude'
|
||||||
archivesBaseName = 'zeichenmaschine-mp3spi'
|
// archivesBaseName = 'zeichenmaschine-mp3spi'
|
||||||
from {
|
from {
|
||||||
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
|
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
|
||||||
}
|
}
|
||||||
with jar
|
with jar
|
||||||
}
|
}
|
||||||
|
|
||||||
|
task buildAll {
|
||||||
|
group "build"
|
||||||
|
description "Build all jar packages"
|
||||||
|
|
||||||
|
dependsOn 'jar'
|
||||||
|
dependsOn 'jarMP3SPI'
|
||||||
|
dependsOn 'sourcesJar'
|
||||||
|
dependsOn 'javadocJar'
|
||||||
|
}
|
||||||
|
|
||||||
|
javadoc {
|
||||||
|
options {
|
||||||
|
encoding = "UTF-8"
|
||||||
|
overview = "src/resources/java/overview.html"
|
||||||
|
// title = "Die Zeichenmaschine"
|
||||||
|
|
||||||
|
// options.links 'https://docs.oracle.com/javase/8/docs/api/'
|
||||||
|
// options.links 'https://docs.oracle.com/javaee/7/api'
|
||||||
|
options.links 'https://docs.oracle.com/en/java/javase/11/docs/api'
|
||||||
|
}
|
||||||
|
options.addStringOption("charset", "UTF-8")
|
||||||
|
}
|
||||||
|
|
||||||
|
task mkdocs(type: Exec) {
|
||||||
|
group "documentation"
|
||||||
|
description "Build MKDocs site"
|
||||||
|
|
||||||
|
workingDir "${projectDir}"
|
||||||
|
commandLine ".venv/bin/python", "-m", "mkdocs", "build"
|
||||||
|
}
|
||||||
|
|
||||||
|
task buildDocs {
|
||||||
|
group "documentation"
|
||||||
|
description "Run all documentation tasks"
|
||||||
|
|
||||||
|
dependsOn 'javadoc'
|
||||||
|
dependsOn 'javadocJar'
|
||||||
|
dependsOn 'mkdocs'
|
||||||
|
}
|
||||||
|
|
||||||
|
task zipSite(type: Zip) {
|
||||||
|
group "documentation"
|
||||||
|
description "Create zip archives for documentations"
|
||||||
|
|
||||||
|
dependsOn 'mkdocs'
|
||||||
|
|
||||||
|
from fileTree("${buildDir}/docs/site")
|
||||||
|
exclude '*.py'
|
||||||
|
exclude '__pycache__'
|
||||||
|
archiveName 'site.zip'
|
||||||
|
destinationDir(file("${buildDir}/docs"))
|
||||||
|
}
|
||||||
|
|
||||||
|
task zipJavadoc(type: Zip) {
|
||||||
|
group "documentation"
|
||||||
|
description "Create zip archives for javadoc"
|
||||||
|
|
||||||
|
dependsOn 'javadoc'
|
||||||
|
|
||||||
|
from fileTree("${buildDir}/docs/javadoc")
|
||||||
|
archiveName 'javadoc.zip'
|
||||||
|
destinationDir(file("${buildDir}/docs"))
|
||||||
|
}
|
||||||
|
|
||||||
|
task uploadDocs {
|
||||||
|
group "documentation"
|
||||||
|
description "Run all documentation tasks and upload artifacts to zeichenmaschine.xyz"
|
||||||
|
|
||||||
|
dependsOn 'zipSite'
|
||||||
|
dependsOn 'zipJavadoc'
|
||||||
|
|
||||||
|
doLast {
|
||||||
|
ssh.run {
|
||||||
|
session(remotes.uberspace) {
|
||||||
|
execute 'rm -rf /var/www/virtual/ngb/zeichenmaschine.xyz/*', ignoreError: true
|
||||||
|
|
||||||
|
put from: "${buildDir}/docs/site.zip", into: '/var/www/virtual/ngb/zeichenmaschine.xyz', ignoreError: true
|
||||||
|
execute 'unzip -o -q /var/www/virtual/ngb/zeichenmaschine.xyz/site.zip -d /var/www/virtual/ngb/zeichenmaschine.xyz'
|
||||||
|
|
||||||
|
put from: "${buildDir}/docs/javadoc.zip", into: '/var/www/virtual/ngb/zeichenmaschine.xyz', ignoreError: true
|
||||||
|
execute 'unzip -o -q /var/www/virtual/ngb/zeichenmaschine.xyz/javadoc.zip -d /var/www/virtual/ngb/zeichenmaschine.xyz/docs'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
|
|
||||||
<figure markdown>
|
|
||||||
{ width=128 }
|
|
||||||
</figure>
|
|
||||||
|
|
||||||
<h1 class="title">Zeichenmaschine.xyz</h1>
|
|
||||||
<h2 class="subtitle">Eine kleine Java-Bibliothek für grafische Programmierung im
|
|
||||||
Informatikunterricht.</h2>
|
|
||||||
|
|
||||||
## Projektidee
|
|
||||||
|
|
||||||
Die **Zeichenmaschine** ist eine für den Informatikunterricht entwickelte Bibliothek,
|
|
||||||
die unter anderem an [Processing](https://processing.org/) angelehnt ist. Die
|
|
||||||
Bibliothek soll einige der üblichen Anfängerschwierigkeiten mit Java vereinfachen
|
|
||||||
und für Schülerinnen und Schüler im Unterricht nutzbar machen.
|
|
||||||
|
|
||||||
!!! warning
|
|
||||||
|
|
||||||
Das Projekt befindet sich noch in der Entwicklungsphase und auch wenn die
|
|
||||||
aktuelle Version schon funktionsfähig ist und einen Großteil der angestrebten
|
|
||||||
Funktionen enthält, ist noch keine stabile Version 1.0 erreicht. Vor allem
|
|
||||||
am Umfang und konsistenten Design der APIs gilt es noch zu arbeiten und es
|
|
||||||
können sich Änderungen ergeben.
|
|
||||||
|
|
||||||
Feedback und Vorschläge zu diesem Prozess (oder auch eine Beteiligung an der
|
|
||||||
Entwicklung) können sehr gerne über [Github](https://github.com/jneug) oder
|
|
||||||
[Mastodon](https://bildung.social/@ngb) an mich kommuniziert werden.
|
|
||||||
|
|
||||||
(Gleiches gilt für diese Webseite zum Projekt.)
|
|
||||||
|
|
||||||
## Dokumentation
|
|
||||||
* [Schnellstart](quickstart.md)
|
|
||||||
* [Installation](installation.md)
|
|
||||||
* {{ javadoc_link() }}
|
|
||||||
|
|
||||||
## Über die Zeichenmaschine
|
|
||||||
|
|
||||||
!!! info
|
|
||||||
|
|
||||||
In der Zeichenmaschine werden bewusst nur englischsprachige Bezeichner für
|
|
||||||
Klassen, Methoden und Variablen verwendet. Ausnahme sind einzelne Klassen,
|
|
||||||
die im Zusammnehang mit dem Namen der Bibliothek stehen, wie die
|
|
||||||
Hauptklasse `Zeichenmaschine`.
|
|
||||||
71
docs/index.md
Normal file
71
docs/index.md
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<figure markdown>
|
||||||
|
{ width=128 }
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
<h1 class="title">Zeichenmaschine.xyz</h1>
|
||||||
|
<h2 class="subtitle">Eine kleine Java-Bibliothek für grafische Programmierung im
|
||||||
|
Informatikunterricht.</h2>
|
||||||
|
|
||||||
|
## Projektidee
|
||||||
|
|
||||||
|
Die **Zeichenmaschine** ist eine für den Informatikunterricht entwickelte
|
||||||
|
Bibliothek, die unter anderem an [Processing](https://processing.org/) angelehnt
|
||||||
|
ist. Die Bibliothek soll einige der üblichen Anfängerschwierigkeiten mit Java
|
||||||
|
vereinfachen und grafische Ausgaben für Schülerinnen und Schüler im Unterricht
|
||||||
|
leichter nutzbar machen.
|
||||||
|
|
||||||
|
!!! warning
|
||||||
|
|
||||||
|
Das Projekt befindet sich noch in der Entwicklungsphase und auch wenn die
|
||||||
|
aktuelle Version schon funktionsfähig ist und einen Großteil der angestrebten
|
||||||
|
Funktionen enthält, ist noch keine stabile Version 1.0 erreicht. Vor allem
|
||||||
|
am Umfang und konsistenten Design der APIs gilt es noch zu arbeiten und es
|
||||||
|
können sich Änderungen ergeben.
|
||||||
|
|
||||||
|
Feedback und Vorschläge zu diesem Prozess (oder auch eine Beteiligung an der
|
||||||
|
Entwicklung) können sehr gerne über [Github](https://github.com/jneug) oder
|
||||||
|
[Mastodon](https://bildung.social/@ngb) an mich kommuniziert werden.
|
||||||
|
|
||||||
|
(Gleiches gilt für diese Webseite zum Projekt.)
|
||||||
|
|
||||||
|
## Dokumentation
|
||||||
|
|
||||||
|
* [Schnellstart](schnellstart.md)
|
||||||
|
* [Installation](installation.md)
|
||||||
|
* [Javadoc]({{ javadoc() }})
|
||||||
|
|
||||||
|
## Über die Zeichenmaschine
|
||||||
|
|
||||||
|
Die _Zeichenmaschine_ ist aus dem Wunsch entstanden, nach einer Einführung in
|
||||||
|
die Grundlagen der Programmiersprache Java mit Processing, einen Übergang zur
|
||||||
|
objektorientierten Modellierung und Programmierung mit BlueJ zu leichter zu
|
||||||
|
ermöglichen. Mit Processing kann zwar auch objektorientiert programmiert werden,
|
||||||
|
aber mit Blick auf die Sekundarstufe II in NRW ist ein Wechsel zu einer
|
||||||
|
generellen Programmierumgebung wie BlueJ wünschenswert.
|
||||||
|
|
||||||
|
Die Motivation von Processing, schnell grafische Ergebnisse der eigenen
|
||||||
|
Programme zu sehen, sollte aber für den Übergang erhalten bleiben. So ist eine
|
||||||
|
kleine Bibliothek mit minimalen Abhängigkeiten entstanden, die an Processing
|
||||||
|
angelehnt einfache Schnittstellen bereitstellte, um Programmen eine grafische
|
||||||
|
Ausgabe zu ermöglichen, ohne viel "Boilerplate" Code schreiben zu müssen.
|
||||||
|
|
||||||
|
Aus diesen Anfängen ist nach und nach eine umfassende Grafikbibliothek
|
||||||
|
entstanden, die als _Zeichenmaschine_ veröffentlicht wurde.
|
||||||
|
|
||||||
|
### Was die Zeichenmaschine nicht ist
|
||||||
|
|
||||||
|
Die Bibliothek hat nicht den Anspruch, ein Konkurrent zu Processing oder
|
||||||
|
anderen, seit Jahren etablierten und ausgereiften, Grafiksystemen zu sein. Vor
|
||||||
|
allem ist die _Zeichenmaschine_ keine vollwertige Game Engine, die auf die
|
||||||
|
Ausführung komplexer Spiele spezialisiert ist. Für diese Zwecke gibt es genügend
|
||||||
|
Alternativen, von deren Nutzung gar nicht abgeraten werden soll.
|
||||||
|
|
||||||
|
## Aufbau der Zeichenmaschine
|
||||||
|
|
||||||
|
!!! info
|
||||||
|
|
||||||
|
In der Zeichenmaschine werden bewusst nur englischsprachige Bezeichner für
|
||||||
|
Klassen, Methoden und Variablen verwendet. Ausnahme sind einzelne Klassen,
|
||||||
|
die im Zusammnehang mit dem Namen der Bibliothek stehen, wie die
|
||||||
|
Hauptklasse `Zeichenmaschine`.
|
||||||
|
|
||||||
@@ -2,10 +2,9 @@
|
|||||||
|
|
||||||
Um ein einfaches Projekt mit der **Zeichenmaschine** aufzusetzen ist nicht mehr
|
Um ein einfaches Projekt mit der **Zeichenmaschine** aufzusetzen ist nicht mehr
|
||||||
nötig, als
|
nötig, als
|
||||||
die [JAR-Datei der aktuellen Version](https://github.com/jneug/zeichenmaschine/release/latest)
|
die [JAR-Datei der aktuellen Version](https://github.com/jneug/zeichenmaschine/releases/latest)
|
||||||
herunterzuladen und dem *Classpath* des Projekts hinzuzufügen. Beschreibungen
|
herunterzuladen und dem *Classpath* des Projekts hinzuzufügen. Beschreibungen
|
||||||
für
|
für verschiedene Entwicklungsumgebungen sind hier aufgelistet.
|
||||||
verschiedene Entwicklungsumgebungen sind hier aufgelistet.
|
|
||||||
|
|
||||||
## Integration in Entwicklungsumgebungen
|
## Integration in Entwicklungsumgebungen
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ erstellt und in einem Fenster mit dem Titel „Shapes“ angezeigt.
|
|||||||
|
|
||||||
### Formen zeichnen
|
### Formen zeichnen
|
||||||
|
|
||||||
Eine Zeichenmaschine hat verschiedene Möglichkeiten, Inhalte in das
|
Eine Zeichenmaschine hat verschiedene Möglichkeiten Inhalte in das
|
||||||
Zeichenfenster zu zeichnen. Um ein einfaches statisches Bild zu erzeugen,
|
Zeichenfenster zu zeichnen. Um ein einfaches statisches Bild zu erzeugen,
|
||||||
überschreiben wir die {{ jdl("schule.ngb.zm.Zeichenmaschine", "draw()",
|
überschreiben wir die {{ jdl("schule.ngb.zm.Zeichenmaschine", "draw()",
|
||||||
c=False) }} Methode.
|
c=False) }} Methode.
|
||||||
@@ -146,7 +146,7 @@ Im Beispiel setzen wir nun die Grundeinstellungen in der `setup()` Methode. In
|
|||||||
## Interaktionen mit der Maus: Whack-a-mole
|
## Interaktionen mit der Maus: Whack-a-mole
|
||||||
|
|
||||||
Mit der Zeichenmaschine lassen sich Interaktionen mit der Maus leicht umsetzen.
|
Mit der Zeichenmaschine lassen sich Interaktionen mit der Maus leicht umsetzen.
|
||||||
Wor wollen das Beispielprogramm zu einem
|
Wir wollen das Beispielprogramm zu einem
|
||||||
[Whac-A-Mole](https://de.wikipedia.org/wiki/Whac-A-Mole) Spiel erweitern.
|
[Whac-A-Mole](https://de.wikipedia.org/wiki/Whac-A-Mole) Spiel erweitern.
|
||||||
|
|
||||||
Auf der Zeichenfläche wird nur noch ein gelber Kreis an einer zufälligen Stelle
|
Auf der Zeichenfläche wird nur noch ein gelber Kreis an einer zufälligen Stelle
|
||||||
@@ -219,7 +219,7 @@ aber auch abnehmen und stellt eine Methode dafür bereit
|
|||||||
Um auf einen Mausklick zu reagieren, ergänzen wir die
|
Um auf einen Mausklick zu reagieren, ergänzen wir die
|
||||||
{{ jdm("Zeichenmaschine", "mouseClicked()") }} Methode:
|
{{ jdm("Zeichenmaschine", "mouseClicked()") }} Methode:
|
||||||
|
|
||||||
```
|
```java
|
||||||
@Override
|
@Override
|
||||||
public void mouseClicked() {
|
public void mouseClicked() {
|
||||||
if( distance(moleX, moleY, mouseX, mouseY) < moleRadius ) {
|
if( distance(moleX, moleY, mouseX, mouseY) < moleRadius ) {
|
||||||
@@ -290,8 +290,8 @@ angeklickt wird.
|
|||||||
## Ein paar Details zur Zeichenmaschine
|
## Ein paar Details zur Zeichenmaschine
|
||||||
|
|
||||||
Die _Zeichenmaschine_ wurde stark von der kreativen Programmierumgebung
|
Die _Zeichenmaschine_ wurde stark von der kreativen Programmierumgebung
|
||||||
[Processing](https://processing.org) inspiriert. Wenn Du Processing schon
|
[Processing](https://processing.org) inspiriert. Wenn du Processing schon
|
||||||
kennst, dann werden Dir einige der Konzepte der _Zeichenmaschine_ schon bekannt
|
kennst, dann werden dir einige der Konzepte der _Zeichenmaschine_ schon bekannt
|
||||||
vorkommen.
|
vorkommen.
|
||||||
|
|
||||||
### Farben
|
### Farben
|
||||||
@@ -392,14 +392,14 @@ Sekunde aufgerufen wird. Normalerweise passiert dies genau 60-mal pro Sekunde.
|
|||||||
### Lebenszeit eines Kreises
|
### Lebenszeit eines Kreises
|
||||||
|
|
||||||
Jeder Kreis soll drei Sekunden zu sehen sein. Daher fügen wir eine neue
|
Jeder Kreis soll drei Sekunden zu sehen sein. Daher fügen wir eine neue
|
||||||
Objektvariable namens `moleTime` ein, die zunächst auf drei steht. Da wir auch
|
Objektvariable namens `moleTime` ein, die zunächst auf Drei steht. Da wir auch
|
||||||
Bruchteile von Skeunden abziehen wollen, wählen wir als Datentyp `double`:
|
Bruchteile von Sekunden abziehen wollen, wählen wir als Datentyp `double`:
|
||||||
|
|
||||||
```Java
|
```Java
|
||||||
private double moleTime=3.0;
|
private double moleTime=3.0;
|
||||||
```
|
```
|
||||||
|
|
||||||
Der Parameter `delta`, der `update()` Methode ist der Zeitraum in Sekunden, seit
|
Der Parameter `delta` der `update()` Methode ist der Zeitraum in Sekunden seit
|
||||||
dem letzten Frame. Subtrahieren wir diesen Wert bei jedem Frame von `moleTime`,
|
dem letzten Frame. Subtrahieren wir diesen Wert bei jedem Frame von `moleTime`,
|
||||||
wird der Wert immer kleiner. Dann müssen wir nur noch prüfen, ob er kleiner Null
|
wird der Wert immer kleiner. Dann müssen wir nur noch prüfen, ob er kleiner Null
|
||||||
ist und in dem Fall den Kreis auf eine neue Position springen lassen.
|
ist und in dem Fall den Kreis auf eine neue Position springen lassen.
|
||||||
@@ -489,7 +489,7 @@ drawing.circle(moleX,moleY,moleRadius*(moleTime/3.0));
|
|||||||
|
|
||||||
### Punktezähler
|
### Punktezähler
|
||||||
|
|
||||||
Zum Schluss wollen wir noch bei jedem Treffer mit der Maus die Punkte Zählen und
|
Zum Schluss wollen wir noch bei jedem Treffer mit der Maus die Punkte zählen und
|
||||||
als Text auf die Zeichenfläche schreiben.
|
als Text auf die Zeichenfläche schreiben.
|
||||||
|
|
||||||
Dazu ergänzen wir eine weitere Objektvariable `score`, die in `mouseClicked()`
|
Dazu ergänzen wir eine weitere Objektvariable `score`, die in `mouseClicked()`
|
||||||
@@ -589,18 +589,18 @@ drawing.setFillColor(BLACK);
|
|||||||
|
|
||||||
## Wie es weitergehen kann
|
## Wie es weitergehen kann
|
||||||
|
|
||||||
In diesem Schnellstart-Tutorial hast Du die Grundlagen der _Zeichenmaschine_
|
In diesem Schnellstart-Tutorial hast du die Grundlagen der _Zeichenmaschine_
|
||||||
gelernt. Um weiterzumachen, kannst Du versuchen, das Whack-a-mole Spiel um diese
|
gelernt. Um weiterzumachen, kannst du versuchen, das Whack-a-mole Spiel um diese
|
||||||
Funktionen zu erweitern:
|
Funktionen zu erweitern:
|
||||||
|
|
||||||
- Mehrere "Maulwürfe" gleichzeitig.
|
- Mehrere "Maulwürfe" gleichzeitig.
|
||||||
- Unterschiedliche Zeiten pro Maulwurf.
|
- Unterschiedliche Zeiten pro Maulwurf.
|
||||||
|
|
||||||
Wenn Du mehr über die Möglichkeiten lernen möchtest, die Dir die
|
Wenn du mehr über die Möglichkeiten lernen möchtest, die dir die
|
||||||
_Zeichenmaschine_ bereitstellt, kannst Du Dir die weiteren Tutorials in dieser
|
_Zeichenmaschine_ bereitstellt, kannst du dir die weiteren Tutorials in dieser
|
||||||
Dokumentation ansehen. Ein guter Startpunkt ist das
|
Dokumentation ansehen. Ein guter Startpunkt ist das
|
||||||
[Aquarium](tutorials/aquarium/aquarium1.md).
|
[Aquarium](tutorials/aquarium/aquarium1.md).
|
||||||
|
|
||||||
Viele verschiedene Beispiele, ohne detailliertes Tutorial, findest Du in der
|
Viele verschiedene Beispiele, ohne detailliertes Tutorial, findest du in der
|
||||||
Kategorie Beispiele und auf GitHub im Repository
|
Kategorie Beispiele und auf GitHub im Repository
|
||||||
[jneug/zeichenmaschine-examples](https://github.com/jneug/zeichenmaschine-examples).
|
[jneug/zeichenmaschine-examples](https://github.com/jneug/zeichenmaschine-examples).
|
||||||
@@ -8,7 +8,7 @@ site_dir: build/docs/site
|
|||||||
|
|
||||||
theme:
|
theme:
|
||||||
name: material
|
name: material
|
||||||
custom_dir: docs/home_override/
|
# custom_dir: docs/home_override/
|
||||||
language: de
|
language: de
|
||||||
logo: assets/icon_64.png
|
logo: assets/icon_64.png
|
||||||
favicon: assets/icon_32.png
|
favicon: assets/icon_32.png
|
||||||
@@ -37,7 +37,7 @@ extra_css:
|
|||||||
- assets/zmstyles.css
|
- assets/zmstyles.css
|
||||||
|
|
||||||
nav:
|
nav:
|
||||||
- Einführung: einfuehrung.md
|
- Einführung: index.md
|
||||||
- Schnellstart: schnellstart.md
|
- Schnellstart: schnellstart.md
|
||||||
- Installation: installation.md
|
- Installation: installation.md
|
||||||
- Tutorials:
|
- Tutorials:
|
||||||
|
|||||||
@@ -34,6 +34,11 @@ public abstract class BasicDrawable extends Constants implements Strokeable, Fil
|
|||||||
*/
|
*/
|
||||||
protected Options.StrokeType strokeType = SOLID;
|
protected Options.StrokeType strokeType = SOLID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Art der Kantenverbindungen von Linien.
|
||||||
|
*/
|
||||||
|
protected Options.StrokeJoin strokeJoin = MITER;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cache für den aktuellen {@code Stroke} der Kontur. Wird nach Änderung
|
* Cache für den aktuellen {@code Stroke} der Kontur. Wird nach Änderung
|
||||||
* einer der Kontureigenschaften auf {@code null} gesetzt und beim nächsten
|
* einer der Kontureigenschaften auf {@code null} gesetzt und beim nächsten
|
||||||
@@ -53,6 +58,7 @@ public abstract class BasicDrawable extends Constants implements Strokeable, Fil
|
|||||||
*/
|
*/
|
||||||
protected MultipleGradientPaint fill = null;
|
protected MultipleGradientPaint fill = null;
|
||||||
|
|
||||||
|
// TODO: Add TexturePaint fill (https://docs.oracle.com/javase/8/docs//api/java/awt/TexturePaint.html)
|
||||||
|
|
||||||
// Implementierung Drawable Interface
|
// Implementierung Drawable Interface
|
||||||
|
|
||||||
@@ -154,7 +160,7 @@ public abstract class BasicDrawable extends Constants implements Strokeable, Fil
|
|||||||
@Override
|
@Override
|
||||||
public Stroke getStroke() {
|
public Stroke getStroke() {
|
||||||
if( stroke == null ) {
|
if( stroke == null ) {
|
||||||
stroke = Strokeable.createStroke(strokeType, strokeWeight);
|
stroke = Strokeable.createStroke(strokeType, strokeWeight, strokeJoin);
|
||||||
}
|
}
|
||||||
return stroke;
|
return stroke;
|
||||||
}
|
}
|
||||||
@@ -191,4 +197,15 @@ public abstract class BasicDrawable extends Constants implements Strokeable, Fil
|
|||||||
this.stroke = null;
|
this.stroke = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Options.StrokeJoin getStrokeJoin() {
|
||||||
|
return strokeJoin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setStrokeJoin( Options.StrokeJoin join ) {
|
||||||
|
strokeJoin = join;
|
||||||
|
this.stroke = null;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -576,6 +576,18 @@ public class Color implements Paint {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public double compare( Color color ) {
|
||||||
|
double maxDist = 764.8333151739665;
|
||||||
|
|
||||||
|
// see: https://www.compuphase.com/cmetric.htm
|
||||||
|
long rmean = (getRed() + color.getRed()) / 2;
|
||||||
|
long r = getRed() - color.getRed();
|
||||||
|
long g = getGreen() - color.getGreen();
|
||||||
|
long b = getBlue() - color.getBlue();
|
||||||
|
|
||||||
|
return 1.0 - (Math.sqrt((((512+rmean)*r*r)>>8) + 4*g*g + (((767-rmean)*b*b)>>8)) / maxDist);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prüft, ob ein anderes Objekt zu diesem gleich ist.
|
* Prüft, ob ein anderes Objekt zu diesem gleich ist.
|
||||||
* <p>
|
* <p>
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
package schule.ngb.zm;
|
package schule.ngb.zm;
|
||||||
|
|
||||||
import schule.ngb.zm.anim.Easing;
|
import schule.ngb.zm.anim.Easing;
|
||||||
import schule.ngb.zm.util.Validator;
|
|
||||||
import schule.ngb.zm.util.io.ImageLoader;
|
|
||||||
import schule.ngb.zm.util.Noise;
|
import schule.ngb.zm.util.Noise;
|
||||||
|
import schule.ngb.zm.util.io.ImageLoader;
|
||||||
|
|
||||||
import java.awt.Cursor;
|
import java.awt.Cursor;
|
||||||
import java.awt.Font;
|
import java.awt.Font;
|
||||||
@@ -12,6 +11,7 @@ import java.awt.event.MouseEvent;
|
|||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.function.DoubleUnaryOperator;
|
import java.util.function.DoubleUnaryOperator;
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ import java.util.function.DoubleUnaryOperator;
|
|||||||
* aktuell gehalten werden (beispielsweise {@link #runtime}).
|
* aktuell gehalten werden (beispielsweise {@link #runtime}).
|
||||||
* <p>
|
* <p>
|
||||||
* Für die Implementierung eigener Klassen ist es oft hilfreich von
|
* Für die Implementierung eigener Klassen ist es oft hilfreich von
|
||||||
* {@code Constants} zu erben, um die Methoden und Konstanten einfach im
|
* {@code Constants} zu erben, um die Methoden und Konstanten einfacher im
|
||||||
* Programm nutzen zu können.
|
* Programm nutzen zu können.
|
||||||
* <pre><code>
|
* <pre><code>
|
||||||
* class MyClass extends Constants {
|
* class MyClass extends Constants {
|
||||||
@@ -68,7 +68,7 @@ public class Constants {
|
|||||||
/**
|
/**
|
||||||
* Patchversion der Zeichenmaschine.
|
* Patchversion der Zeichenmaschine.
|
||||||
*/
|
*/
|
||||||
public static final int APP_VERSION_REV = 32;
|
public static final int APP_VERSION_REV = 35;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Version der Zeichenmaschine als Text-String.
|
* Version der Zeichenmaschine als Text-String.
|
||||||
@@ -171,6 +171,21 @@ public class Constants {
|
|||||||
*/
|
*/
|
||||||
public static final Options.StrokeType DOTTED = Options.StrokeType.DOTTED;
|
public static final Options.StrokeType DOTTED = Options.StrokeType.DOTTED;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Option für abgerundete Kantenverbindungen von Konturen und Linien.
|
||||||
|
*/
|
||||||
|
public static final Options.StrokeJoin ROUND = Options.StrokeJoin.ROUND;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Option für abgeschnittene Kantenverbindungen von Konturen und Linien.
|
||||||
|
*/
|
||||||
|
public static final Options.StrokeJoin BEVEL = Options.StrokeJoin.BEVEL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Option für eckige Kantenverbindungen von Konturen und Linien.
|
||||||
|
*/
|
||||||
|
public static final Options.StrokeJoin MITER = Options.StrokeJoin.MITER;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Option für Pfeile mit Strichen als Kopf.
|
* Option für Pfeile mit Strichen als Kopf.
|
||||||
*/
|
*/
|
||||||
@@ -1269,7 +1284,7 @@ public class Constants {
|
|||||||
*
|
*
|
||||||
* @return Die {@code Random}-Instanz.
|
* @return Die {@code Random}-Instanz.
|
||||||
*/
|
*/
|
||||||
private static Random getRandom() {
|
public static Random getRandom() {
|
||||||
if( random == null ) {
|
if( random == null ) {
|
||||||
random = new Random();
|
random = new Random();
|
||||||
}
|
}
|
||||||
@@ -1321,7 +1336,8 @@ public class Constants {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Erzeugt eine ganze Pseudozufallszahl zwischen {@code 0} und {@code max}.
|
* Erzeugt eine ganze Pseudozufallszahl zwischen {@code 0} und {@code max}
|
||||||
|
* (einschließlich der Grenzen).
|
||||||
*
|
*
|
||||||
* @param max Obere Grenze.
|
* @param max Obere Grenze.
|
||||||
* @return Eine Zufallszahl.
|
* @return Eine Zufallszahl.
|
||||||
@@ -1332,7 +1348,7 @@ public class Constants {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Erzeugt eine ganze Pseudozufallsganzzahl zwischen {@code min} und
|
* Erzeugt eine ganze Pseudozufallsganzzahl zwischen {@code min} und
|
||||||
* {@code max}.
|
* {@code max} (einschließlich der Grenzen).
|
||||||
*
|
*
|
||||||
* @param min Untere Grenze.
|
* @param min Untere Grenze.
|
||||||
* @param max Obere Grenze.
|
* @param max Obere Grenze.
|
||||||
@@ -1389,26 +1405,6 @@ public class Constants {
|
|||||||
return getRandom().nextGaussian();
|
return getRandom().nextGaussian();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Wählt ein zufälliges Element aus dem Array aus.
|
|
||||||
*
|
|
||||||
* @param values Ein Array mit Werten, die zur Auswahl stehen.
|
|
||||||
* @return Ein zufälliges Element aus dem Array.
|
|
||||||
*/
|
|
||||||
/*public static final int choice( int... values ) {
|
|
||||||
return values[random(0, values.length - 1)];
|
|
||||||
}*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wählt ein zufälliges Element aus dem Array aus.
|
|
||||||
*
|
|
||||||
* @param values Ein Array mit Werten, die zur Auswahl stehen.
|
|
||||||
* @return Ein zufälliges Element aus dem Array.
|
|
||||||
*/
|
|
||||||
/*public static final double choice( double... values ) {
|
|
||||||
return values[random(0, values.length - 1)];
|
|
||||||
}*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wählt ein zufälliges Element aus dem Array aus.
|
* Wählt ein zufälliges Element aus dem Array aus.
|
||||||
*
|
*
|
||||||
@@ -1416,6 +1412,7 @@ public class Constants {
|
|||||||
* @param <T> Datentyp des Elements.
|
* @param <T> Datentyp des Elements.
|
||||||
* @return Ein zufälliges Element aus dem Array.
|
* @return Ein zufälliges Element aus dem Array.
|
||||||
*/
|
*/
|
||||||
|
@SafeVarargs
|
||||||
public static final <T> T choice( T... values ) {
|
public static final <T> T choice( T... values ) {
|
||||||
return values[random(0, values.length - 1)];
|
return values[random(0, values.length - 1)];
|
||||||
}
|
}
|
||||||
@@ -1570,6 +1567,18 @@ public class Constants {
|
|||||||
return valueList.toArray(values);
|
return valueList.toArray(values);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bringt die Werte im Array in eine zufällige Reihenfolge.
|
||||||
|
*
|
||||||
|
* @param values Ein Array mit Werte, die gemischt werden sollen.
|
||||||
|
* @param <T> Datentyp der Elemente.
|
||||||
|
* @return Das Array in zufälliger Reihenfolge.
|
||||||
|
*/
|
||||||
|
public static final <T> List<T> shuffle( List<T> values ) {
|
||||||
|
Collections.shuffle(values, random);
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Geteilte {@code Noise}-Instanz zur Erzeugung von Perlin-Noise.
|
* Geteilte {@code Noise}-Instanz zur Erzeugung von Perlin-Noise.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package schule.ngb.zm;
|
package schule.ngb.zm;
|
||||||
|
|
||||||
|
import java.awt.BasicStroke;
|
||||||
import java.awt.geom.Arc2D;
|
import java.awt.geom.Arc2D;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -31,6 +32,36 @@ public final class Options {
|
|||||||
DOTTED
|
DOTTED
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Linienstile für Konturlinien.
|
||||||
|
*/
|
||||||
|
public enum StrokeJoin {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abgerundete Verbindungen.
|
||||||
|
*/
|
||||||
|
ROUND(BasicStroke.JOIN_ROUND),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abgeschnittene Verbindungen.
|
||||||
|
*/
|
||||||
|
BEVEL(BasicStroke.JOIN_BEVEL),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Eckige Verbindungen.
|
||||||
|
*/
|
||||||
|
MITER(BasicStroke.JOIN_MITER);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der entsprechende Wert der Konstanten in {@link java.awt}
|
||||||
|
*/
|
||||||
|
public final int awt_type;
|
||||||
|
|
||||||
|
StrokeJoin( int type ) {
|
||||||
|
awt_type = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stile für Pfeilspitzen.
|
* Stile für Pfeilspitzen.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ public interface Strokeable extends Drawable {
|
|||||||
* @param weight Die Dicke der Konturlinie.
|
* @param weight Die Dicke der Konturlinie.
|
||||||
*/
|
*/
|
||||||
default void setStrokeWeight( double weight ) {
|
default void setStrokeWeight( double weight ) {
|
||||||
setStroke(createStroke(getStrokeType(), weight));
|
setStroke(createStroke(getStrokeType(), weight, getStrokeJoin()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -193,7 +193,26 @@ public interface Strokeable extends Drawable {
|
|||||||
* @see Options.StrokeType
|
* @see Options.StrokeType
|
||||||
*/
|
*/
|
||||||
default void setStrokeType( Options.StrokeType type ) {
|
default void setStrokeType( Options.StrokeType type ) {
|
||||||
setStroke(createStroke(type, getStrokeWeight()));
|
setStroke(createStroke(type, getStrokeWeight(), getStrokeJoin()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt die Art der Konturverbindungen zurück.
|
||||||
|
*
|
||||||
|
* @return Die aktuelle Art der Konturverbindungen.
|
||||||
|
* @see Options.StrokeJoin
|
||||||
|
*/
|
||||||
|
Options.StrokeJoin getStrokeJoin();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt den Typ der Konturverbindungen. Erlaubte Werte sind {@link Constants#ROUND},
|
||||||
|
* {@link Constants#MITER} und {@link Constants#BEVEL}.
|
||||||
|
*
|
||||||
|
* @param join Eine der möglichen Konturverbindungen.
|
||||||
|
* @see Options.StrokeJoin
|
||||||
|
*/
|
||||||
|
default void setStrokeJoin( Options.StrokeJoin join ) {
|
||||||
|
setStroke(createStroke(getStrokeType(), getStrokeWeight(), join));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -201,28 +220,30 @@ public interface Strokeable extends Drawable {
|
|||||||
* Kontureigenschaften zu erstellen. Der aktuelle {@code Stroke} wird
|
* Kontureigenschaften zu erstellen. Der aktuelle {@code Stroke} wird
|
||||||
* zwischengespeichert.
|
* zwischengespeichert.
|
||||||
*
|
*
|
||||||
|
* @param strokeType
|
||||||
|
* @param strokeWeight
|
||||||
* @return Ein {@code Stroke} mit den passenden Kontureigenschaften.
|
* @return Ein {@code Stroke} mit den passenden Kontureigenschaften.
|
||||||
*/
|
*/
|
||||||
static Stroke createStroke( Options.StrokeType strokeType, double strokeWeight ) {
|
static Stroke createStroke( Options.StrokeType strokeType, double strokeWeight, Options.StrokeJoin strokeJoin ) {
|
||||||
switch( strokeType ) {
|
switch( strokeType ) {
|
||||||
case DOTTED:
|
case DOTTED:
|
||||||
return new BasicStroke(
|
return new BasicStroke(
|
||||||
(float) strokeWeight,
|
(float) strokeWeight,
|
||||||
BasicStroke.CAP_ROUND,
|
BasicStroke.CAP_ROUND,
|
||||||
BasicStroke.JOIN_ROUND,
|
strokeJoin.awt_type,
|
||||||
10.0f, new float[]{1.0f, 5.0f}, 0.0f);
|
10.0f, new float[]{1.0f, 5.0f}, 0.0f);
|
||||||
case DASHED:
|
case DASHED:
|
||||||
return new BasicStroke(
|
return new BasicStroke(
|
||||||
(float) strokeWeight,
|
(float) strokeWeight,
|
||||||
BasicStroke.CAP_ROUND,
|
BasicStroke.CAP_ROUND,
|
||||||
BasicStroke.JOIN_ROUND,
|
strokeJoin.awt_type,
|
||||||
10.0f, new float[]{5.0f}, 0.0f);
|
10.0f, new float[]{5.0f}, 0.0f);
|
||||||
case SOLID:
|
case SOLID:
|
||||||
default:
|
default:
|
||||||
return new BasicStroke(
|
return new BasicStroke(
|
||||||
(float) strokeWeight,
|
(float) strokeWeight,
|
||||||
BasicStroke.CAP_ROUND,
|
BasicStroke.CAP_ROUND,
|
||||||
BasicStroke.JOIN_ROUND);
|
strokeJoin.awt_type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -191,6 +191,22 @@ public class Vector extends Point2D.Double {
|
|||||||
return new Vector(x, y);
|
return new Vector(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getIntX() {
|
||||||
|
return (int)this.x;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRoundedX() {
|
||||||
|
return (int)Math.round(this.x);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getIntY() {
|
||||||
|
return (int)this.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRoundedY() {
|
||||||
|
return (int)Math.round(this.y);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setzt die Komponenten dieses Vektors neu.
|
* Setzt die Komponenten dieses Vektors neu.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package schule.ngb.zm;
|
|||||||
|
|
||||||
import schule.ngb.zm.util.Log;
|
import schule.ngb.zm.util.Log;
|
||||||
import schule.ngb.zm.util.Validator;
|
import schule.ngb.zm.util.Validator;
|
||||||
|
import schule.ngb.zm.util.io.ImageLoader;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
@@ -10,6 +11,7 @@ import java.awt.event.KeyAdapter;
|
|||||||
import java.awt.event.KeyEvent;
|
import java.awt.event.KeyEvent;
|
||||||
import java.awt.event.KeyListener;
|
import java.awt.event.KeyListener;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
@@ -26,7 +28,7 @@ public class Zeichenfenster extends JFrame {
|
|||||||
/**
|
/**
|
||||||
* Setzt das Look and Feel auf den Standard des Systems.
|
* Setzt das Look and Feel auf den Standard des Systems.
|
||||||
* <p>
|
* <p>
|
||||||
* Sollte einmalig vor erstellen des erstyen Programmfensters aufgerufen
|
* Sollte einmalig vor Erstellen des ersten Programmfensters aufgerufen
|
||||||
* werden.
|
* werden.
|
||||||
*/
|
*/
|
||||||
public static final void setLookAndFeel() {
|
public static final void setLookAndFeel() {
|
||||||
@@ -156,7 +158,7 @@ public class Zeichenfenster extends JFrame {
|
|||||||
* @param displayDevice Das Anzeigegerät für das Fenster.
|
* @param displayDevice Das Anzeigegerät für das Fenster.
|
||||||
*/
|
*/
|
||||||
public Zeichenfenster( Zeichenleinwand canvas, String title, GraphicsDevice displayDevice ) {
|
public Zeichenfenster( Zeichenleinwand canvas, String title, GraphicsDevice displayDevice ) {
|
||||||
super(Validator.requireNotNull(displayDevice).getDefaultConfiguration());
|
super(Validator.requireNotNull(displayDevice, "displayDevice").getDefaultConfiguration());
|
||||||
this.displayDevice = displayDevice;
|
this.displayDevice = displayDevice;
|
||||||
|
|
||||||
Validator.requireNotNull(canvas, "Every Zeichenfenster needs a Zeichenleinwand, but got <null>.");
|
Validator.requireNotNull(canvas, "Every Zeichenfenster needs a Zeichenleinwand, but got <null>.");
|
||||||
@@ -174,12 +176,14 @@ public class Zeichenfenster extends JFrame {
|
|||||||
// Das Icon des Fensters ändern
|
// Das Icon des Fensters ändern
|
||||||
try {
|
try {
|
||||||
if( Zeichenmaschine.MACOS ) {
|
if( Zeichenmaschine.MACOS ) {
|
||||||
URL iconUrl = Zeichenmaschine.class.getResource("icon_512.png");
|
InputStream iconStream = this.getClass().getResourceAsStream("icon_512.png");
|
||||||
if( iconUrl != null ) {
|
if( iconStream != null ) {
|
||||||
Image icon = ImageIO.read(iconUrl);
|
Image icon = ImageIO.read(iconStream);
|
||||||
// Dock Icon in macOS setzen
|
// Dock Icon in macOS setzen
|
||||||
Taskbar taskbar = Taskbar.getTaskbar();
|
Taskbar taskbar = Taskbar.getTaskbar();
|
||||||
taskbar.setIconImage(icon);
|
taskbar.setIconImage(icon);
|
||||||
|
} else {
|
||||||
|
LOG.warn("Could not load dock-icon");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ArrayList<Image> icons = new ArrayList<>(4);
|
ArrayList<Image> icons = new ArrayList<>(4);
|
||||||
@@ -190,8 +194,12 @@ public class Zeichenfenster extends JFrame {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if( icons.isEmpty() ) {
|
||||||
|
LOG.warn("Could not load dock-icon");
|
||||||
|
} else {
|
||||||
this.setIconImages(icons);
|
this.setIconImages(icons);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch( IllegalArgumentException | IOException e ) {
|
} catch( IllegalArgumentException | IOException e ) {
|
||||||
LOG.warn("Could not load image icons: %s", e.getMessage());
|
LOG.warn("Could not load image icons: %s", e.getMessage());
|
||||||
} catch( SecurityException | UnsupportedOperationException macex ) {
|
} catch( SecurityException | UnsupportedOperationException macex ) {
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ public class Zeichenmaschine extends Constants {
|
|||||||
*/
|
*/
|
||||||
private boolean running;
|
private boolean running;
|
||||||
|
|
||||||
private boolean terminateImediately = false;
|
private boolean terminateImmediately = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ob die ZM nach dem nächsten Frame pausiert werden soll.
|
* Ob die ZM nach dem nächsten Frame pausiert werden soll.
|
||||||
@@ -545,7 +545,7 @@ public class Zeichenmaschine extends Constants {
|
|||||||
|
|
||||||
if( running ) {
|
if( running ) {
|
||||||
running = false;
|
running = false;
|
||||||
terminateImediately = true;
|
terminateImmediately = true;
|
||||||
quitAfterShutdown = true;
|
quitAfterShutdown = true;
|
||||||
mainThread.interrupt();
|
mainThread.interrupt();
|
||||||
} else {
|
} else {
|
||||||
@@ -769,6 +769,23 @@ public class Zeichenmaschine extends Constants {
|
|||||||
framesPerSecond = framesPerSecondInternal;
|
framesPerSecond = framesPerSecondInternal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt aus dem aktuellen Inhalt der {@link Zeichenleinwand} ein neues
|
||||||
|
* {@link BufferedImage}.
|
||||||
|
*/
|
||||||
|
public final BufferedImage getImage() {
|
||||||
|
BufferedImage img = ImageLoader.createImage(canvas.getWidth(), canvas.getHeight());
|
||||||
|
|
||||||
|
Graphics2D g = img.createGraphics();
|
||||||
|
// TODO: Transparente Hintergründe beim Speichern von png erlauben
|
||||||
|
g.setColor(DEFAULT_BACKGROUND.getJavaColor());
|
||||||
|
g.fillRect(0, 0, img.getWidth(), img.getHeight());
|
||||||
|
canvas.draw(g);
|
||||||
|
g.dispose();
|
||||||
|
|
||||||
|
return img;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Speichert den aktuellen Inhalt der {@link Zeichenleinwand} in einer
|
* Speichert den aktuellen Inhalt der {@link Zeichenleinwand} in einer
|
||||||
* Bilddatei auf der Festplatte. Zur Auswahl der Zieldatei wird dem Nutzer
|
* Bilddatei auf der Festplatte. Zur Auswahl der Zieldatei wird dem Nutzer
|
||||||
@@ -794,23 +811,15 @@ public class Zeichenmaschine extends Constants {
|
|||||||
* Bilddatei im angegebenen Dateipfad auf der Festplatte.
|
* Bilddatei im angegebenen Dateipfad auf der Festplatte.
|
||||||
*/
|
*/
|
||||||
public final void saveImage( String filepath ) {
|
public final void saveImage( String filepath ) {
|
||||||
BufferedImage img = ImageLoader.createImage(canvas.getWidth(), canvas.getHeight());
|
|
||||||
|
|
||||||
Graphics2D g = img.createGraphics();
|
|
||||||
g.setColor(DEFAULT_BACKGROUND.getJavaColor());
|
|
||||||
g.fillRect(0, 0, img.getWidth(), img.getHeight());
|
|
||||||
canvas.draw(g);
|
|
||||||
g.dispose();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ImageLoader.saveImage(img, new File(filepath), true);
|
ImageLoader.saveImage(getImage(), new File(filepath), true);
|
||||||
} catch( IOException ex ) {
|
} catch( IOException ex ) {
|
||||||
ex.printStackTrace();
|
ex.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Erstellt eine Momentanaufnahme des aktuellen Inhalts der
|
* Erstellt eine Momentaufnahme des aktuellen Inhalts der
|
||||||
* {@link Zeichenleinwand} und erstellt daraus eine
|
* {@link Zeichenleinwand} und erstellt daraus eine
|
||||||
* {@link ImageLayer Bildebene}. Die Ebene wird automatisch der
|
* {@link ImageLayer Bildebene}. Die Ebene wird automatisch der
|
||||||
* {@link Zeichenleinwand} vor dem {@link #background} hinzugefügt.
|
* {@link Zeichenleinwand} vor dem {@link #background} hinzugefügt.
|
||||||
@@ -1177,6 +1186,9 @@ public class Zeichenmaschine extends Constants {
|
|||||||
//saveMousePosition(evt);
|
//saveMousePosition(evt);
|
||||||
mouseMoved(evt);
|
mouseMoved(evt);
|
||||||
break;
|
break;
|
||||||
|
case MouseEvent.MOUSE_WHEEL:
|
||||||
|
mouseWheelMoved(evt);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1220,6 +1232,14 @@ public class Zeichenmaschine extends Constants {
|
|||||||
// Intentionally left blank
|
// Intentionally left blank
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void mouseWheelMoved( MouseEvent e ) {
|
||||||
|
mouseMoved();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void mouseWheelMoved() {
|
||||||
|
// Intentionally left blank
|
||||||
|
}
|
||||||
|
|
||||||
private void saveMousePosition( MouseEvent event ) {
|
private void saveMousePosition( MouseEvent event ) {
|
||||||
if( mouseEvent != null && event.getComponent() == canvas ) {
|
if( mouseEvent != null && event.getComponent() == canvas ) {
|
||||||
pmouseX = mouseX;
|
pmouseX = mouseX;
|
||||||
@@ -1387,7 +1407,7 @@ public class Zeichenmaschine extends Constants {
|
|||||||
|
|
||||||
if( Thread.interrupted() ) {
|
if( Thread.interrupted() ) {
|
||||||
running = false;
|
running = false;
|
||||||
terminateImediately = true;
|
terminateImmediately = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1443,7 +1463,7 @@ public class Zeichenmaschine extends Constants {
|
|||||||
}
|
}
|
||||||
state = Options.AppState.STOPPED;
|
state = Options.AppState.STOPPED;
|
||||||
// Shutdown the updateThread
|
// Shutdown the updateThread
|
||||||
while( !terminateImediately && updateThreadExecutor.isRunning() ) {
|
while( !terminateImmediately && updateThreadExecutor.isRunning() ) {
|
||||||
Thread.yield();
|
Thread.yield();
|
||||||
}
|
}
|
||||||
updateThreadExecutor.shutdownNow();
|
updateThreadExecutor.shutdownNow();
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ public abstract class Animation<T> extends Constants implements Updatable {
|
|||||||
|
|
||||||
public Animation( DoubleUnaryOperator easing ) {
|
public Animation( DoubleUnaryOperator easing ) {
|
||||||
this.runtime = Constants.DEFAULT_ANIM_RUNTIME;
|
this.runtime = Constants.DEFAULT_ANIM_RUNTIME;
|
||||||
this.easing = Validator.requireNotNull(easing);
|
this.easing = Validator.requireNotNull(easing, "easing");
|
||||||
}
|
}
|
||||||
|
|
||||||
public Animation( int runtime ) {
|
public Animation( int runtime ) {
|
||||||
@@ -34,7 +34,7 @@ public abstract class Animation<T> extends Constants implements Updatable {
|
|||||||
|
|
||||||
public Animation( int runtime, DoubleUnaryOperator easing ) {
|
public Animation( int runtime, DoubleUnaryOperator easing ) {
|
||||||
this.runtime = runtime;
|
this.runtime = runtime;
|
||||||
this.easing = Validator.requireNotNull(easing);
|
this.easing = Validator.requireNotNull(easing, "easing");
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getRuntime() {
|
public int getRuntime() {
|
||||||
@@ -50,7 +50,7 @@ public abstract class Animation<T> extends Constants implements Updatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setEasing( DoubleUnaryOperator pEasing ) {
|
public void setEasing( DoubleUnaryOperator pEasing ) {
|
||||||
this.easing = pEasing;
|
this.easing = Validator.requireNotNull(pEasing, "easing");
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract T getAnimationTarget();
|
public abstract T getAnimationTarget();
|
||||||
@@ -61,7 +61,7 @@ public abstract class Animation<T> extends Constants implements Updatable {
|
|||||||
running = true;
|
running = true;
|
||||||
finished = false;
|
finished = false;
|
||||||
animate(easing.applyAsDouble(0.0));
|
animate(easing.applyAsDouble(0.0));
|
||||||
initializeEventDispatcher().dispatchEvent("start", this);
|
dispatchEvent("start");
|
||||||
}
|
}
|
||||||
|
|
||||||
public final void stop() {
|
public final void stop() {
|
||||||
@@ -70,7 +70,7 @@ public abstract class Animation<T> extends Constants implements Updatable {
|
|||||||
animate(easing.applyAsDouble((double) elapsedTime / (double) runtime));
|
animate(easing.applyAsDouble((double) elapsedTime / (double) runtime));
|
||||||
this.finish();
|
this.finish();
|
||||||
finished = true;
|
finished = true;
|
||||||
initializeEventDispatcher().dispatchEvent("stop", this);
|
dispatchEvent("stop");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
@@ -100,10 +100,9 @@ public abstract class Animation<T> extends Constants implements Updatable {
|
|||||||
|
|
||||||
double t = (double) elapsedTime / (double) runtime;
|
double t = (double) elapsedTime / (double) runtime;
|
||||||
if( t >= 1.0 ) {
|
if( t >= 1.0 ) {
|
||||||
running = false;
|
|
||||||
stop();
|
stop();
|
||||||
} else {
|
} else {
|
||||||
animate(easing.applyAsDouble(t));
|
animate(getEasing().applyAsDouble(t));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,7 +117,7 @@ public abstract class Animation<T> extends Constants implements Updatable {
|
|||||||
* e = Constants.limit(e, 0, 1);
|
* e = Constants.limit(e, 0, 1);
|
||||||
* </code></pre>
|
* </code></pre>
|
||||||
*
|
*
|
||||||
* @param e Fortschritt der Animation nachdem die Easingfunktion angewandt
|
* @param e Fortschritt der Animation, nachdem die Easing-Funktion angewandt
|
||||||
* wurde.
|
* wurde.
|
||||||
*/
|
*/
|
||||||
public abstract void animate( double e );
|
public abstract void animate( double e );
|
||||||
@@ -134,6 +133,12 @@ public abstract class Animation<T> extends Constants implements Updatable {
|
|||||||
return eventDispatcher;
|
return eventDispatcher;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void dispatchEvent( String type ) {
|
||||||
|
if( eventDispatcher != null ) {
|
||||||
|
eventDispatcher.dispatchEvent(type, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void addListener( AnimationListener listener ) {
|
public void addListener( AnimationListener listener ) {
|
||||||
initializeEventDispatcher().addListener(listener);
|
initializeEventDispatcher().addListener(listener);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,13 +4,19 @@ import schule.ngb.zm.util.Validator;
|
|||||||
|
|
||||||
import java.util.function.DoubleUnaryOperator;
|
import java.util.function.DoubleUnaryOperator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Eine Wrapper Animation, um die Werte einer anderen Animation (Laufzeit, Easing) zu überschrieben,
|
||||||
|
* ohne die Werte der Originalanimation zu verändern.
|
||||||
|
*
|
||||||
|
* @param <S> Art des Animierten Objektes.
|
||||||
|
*/
|
||||||
public class AnimationFacade<S> extends Animation<S> {
|
public class AnimationFacade<S> extends Animation<S> {
|
||||||
|
|
||||||
private Animation<S> anim;
|
private final Animation<S> anim;
|
||||||
|
|
||||||
public AnimationFacade( Animation<S> anim, int runtime, DoubleUnaryOperator easing ) {
|
public AnimationFacade( Animation<S> anim, int runtime, DoubleUnaryOperator easing ) {
|
||||||
super(runtime, easing);
|
super(runtime, easing);
|
||||||
this.anim = Validator.requireNotNull(anim);
|
this.anim = Validator.requireNotNull(anim, "anim");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,23 +1,28 @@
|
|||||||
package schule.ngb.zm.anim;
|
package schule.ngb.zm.anim;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.DoubleUnaryOperator;
|
import java.util.function.DoubleUnaryOperator;
|
||||||
|
|
||||||
|
// TODO: (ngb) Maybe use AnimationFacade to override runtime?
|
||||||
@SuppressWarnings( "unused" )
|
@SuppressWarnings( "unused" )
|
||||||
public class AnimationGroup<T> extends Animation<T> {
|
public class AnimationGroup<T> extends Animation<T> {
|
||||||
|
|
||||||
List<Animation<T>> anims;
|
private final List<Animation<T>> anims;
|
||||||
|
|
||||||
|
private final boolean overrideEasing;
|
||||||
private boolean overrideEasing = false;
|
|
||||||
|
|
||||||
private int overrideRuntime = -1;
|
private int overrideRuntime = -1;
|
||||||
|
|
||||||
private int lag = 0;
|
private final int lag;
|
||||||
|
|
||||||
private int active = 0;
|
private int active = 0;
|
||||||
|
|
||||||
|
public AnimationGroup( Animation<T>... anims ) {
|
||||||
|
this(0, -1, null, Arrays.asList(anims));
|
||||||
|
}
|
||||||
|
|
||||||
public AnimationGroup( Collection<Animation<T>> anims ) {
|
public AnimationGroup( Collection<Animation<T>> anims ) {
|
||||||
this(0, -1, null, anims);
|
this(0, -1, null, anims);
|
||||||
}
|
}
|
||||||
@@ -43,6 +48,8 @@ public class AnimationGroup<T> extends Animation<T> {
|
|||||||
if( easing != null ) {
|
if( easing != null ) {
|
||||||
this.easing = easing;
|
this.easing = easing;
|
||||||
overrideEasing = true;
|
overrideEasing = true;
|
||||||
|
} else {
|
||||||
|
overrideEasing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if( runtime > 0 ) {
|
if( runtime > 0 ) {
|
||||||
@@ -65,52 +72,110 @@ public class AnimationGroup<T> extends Animation<T> {
|
|||||||
return anim.getAnimationTarget();
|
return anim.getAnimationTarget();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if( this.finished ) {
|
||||||
return anims.get(anims.size() - 1).getAnimationTarget();
|
return anims.get(anims.size() - 1).getAnimationTarget();
|
||||||
|
} else {
|
||||||
|
return anims.get(0).getAnimationTarget();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void update( double delta ) {
|
public DoubleUnaryOperator getEasing() {
|
||||||
elapsedTime += (int) (delta * 1000);
|
for( Animation<T> anim : anims ) {
|
||||||
// Animation is done. Stop all Animations.
|
if( anim.isActive() ) {
|
||||||
if( elapsedTime > runtime ) {
|
return anim.getEasing();
|
||||||
for( int i = 0; i < anims.size(); i++ ) {
|
|
||||||
if( anims.get(i).isActive() ) {
|
|
||||||
anims.get(i).elapsedTime = anims.get(i).runtime;
|
|
||||||
anims.get(i).stop();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
running = false;
|
if( this.finished ) {
|
||||||
this.stop();
|
return anims.get(anims.size() - 1).getEasing();
|
||||||
}
|
|
||||||
|
|
||||||
while( active < anims.size() && elapsedTime >= active * lag ) {
|
|
||||||
anims.get(active).start();
|
|
||||||
active += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
for( int i = 0; i < active; i++ ) {
|
|
||||||
double t = 0.0;
|
|
||||||
if( overrideRuntime > 0 ) {
|
|
||||||
t = (double) (elapsedTime - i*lag) / (double) overrideRuntime;
|
|
||||||
} else {
|
} else {
|
||||||
t = (double) (elapsedTime - i*lag) / (double) anims.get(i).getRuntime();
|
return anims.get(0).getEasing();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if( t >= 1.0 ) {
|
// @Override
|
||||||
anims.get(i).elapsedTime = anims.get(i).runtime;
|
// public void update( double delta ) {
|
||||||
anims.get(i).stop();
|
// elapsedTime += (int) (delta * 1000);
|
||||||
} else {
|
//
|
||||||
double e = overrideEasing ?
|
// // Animation is done. Stop all Animations.
|
||||||
easing.applyAsDouble(t) :
|
// if( elapsedTime > runtime ) {
|
||||||
anims.get(i).easing.applyAsDouble(t);
|
// for( int i = 0; i < anims.size(); i++ ) {
|
||||||
|
// if( anims.get(i).isActive() ) {
|
||||||
|
// anims.get(i).elapsedTime = anims.get(i).runtime;
|
||||||
|
// anims.get(i).stop();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// elapsedTime = runtime;
|
||||||
|
// running = false;
|
||||||
|
// this.stop();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// while( active < anims.size() && elapsedTime >= active * lag ) {
|
||||||
|
// anims.get(active).start();
|
||||||
|
// active += 1;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// for( int i = 0; i < active; i++ ) {
|
||||||
|
// double t = 0.0;
|
||||||
|
// if( overrideRuntime > 0 ) {
|
||||||
|
// t = (double) (elapsedTime - i*lag) / (double) overrideRuntime;
|
||||||
|
// } else {
|
||||||
|
// t = (double) (elapsedTime - i*lag) / (double) anims.get(i).getRuntime();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if( t >= 1.0 ) {
|
||||||
|
// anims.get(i).elapsedTime = anims.get(i).runtime;
|
||||||
|
// anims.get(i).stop();
|
||||||
|
// } else {
|
||||||
|
// double e = overrideEasing ?
|
||||||
|
// easing.applyAsDouble(t) :
|
||||||
|
// anims.get(i).easing.applyAsDouble(t);
|
||||||
|
//
|
||||||
|
// anims.get(i).animate(e);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
anims.get(i).animate(e);
|
|
||||||
|
@Override
|
||||||
|
public void finish() {
|
||||||
|
for( Animation<T> anim : anims ) {
|
||||||
|
if( anim.isActive() ) {
|
||||||
|
anim.elapsedTime = anim.runtime;
|
||||||
|
anim.stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void animate( double e ) {
|
public void animate( double e ) {
|
||||||
|
while( active < anims.size() && elapsedTime >= active * lag ) {
|
||||||
|
anims.get(active).start();
|
||||||
|
active += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for( int i = 0; i < active; i++ ) {
|
||||||
|
Animation<T> curAnim = anims.get(i);
|
||||||
|
|
||||||
|
double curRuntime = curAnim.getRuntime();
|
||||||
|
if( overrideRuntime > 0 ) {
|
||||||
|
curRuntime = overrideRuntime;
|
||||||
|
}
|
||||||
|
|
||||||
|
double t = (double) (elapsedTime - i * lag) / (double) curRuntime;
|
||||||
|
if( t >= 1.0 ) {
|
||||||
|
curAnim.elapsedTime = curAnim.getRuntime();
|
||||||
|
curAnim.stop();
|
||||||
|
} else {
|
||||||
|
e = overrideEasing ?
|
||||||
|
easing.applyAsDouble(t) :
|
||||||
|
curAnim.easing.applyAsDouble(t);
|
||||||
|
|
||||||
|
curAnim.elapsedTime = (elapsedTime - i * lag);
|
||||||
|
curAnim.animate(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
144
src/main/java/schule/ngb/zm/anim/AnimationSequence.java
Normal file
144
src/main/java/schule/ngb/zm/anim/AnimationSequence.java
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
package schule.ngb.zm.anim;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.DoubleUnaryOperator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Führt eine Liste von Animationen nacheinander aus. Jede Animation startet direkt nachdem die
|
||||||
|
* davor geendet ist. Optional kann zwischen dem Ende einer und dem Start der nächsten Animation
|
||||||
|
* ein
|
||||||
|
* <var>lag</var> eingefügt werden.
|
||||||
|
*
|
||||||
|
* @param <T> Die Art des animierten Objektes.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings( "unused" )
|
||||||
|
public class AnimationSequence<T> extends Animation<T> {
|
||||||
|
|
||||||
|
private final List<Animation<T>> anims;
|
||||||
|
|
||||||
|
private final int lag;
|
||||||
|
|
||||||
|
private int currentAnimationIndex = -1, currentStart = -1, nextStart = -1;
|
||||||
|
|
||||||
|
@SafeVarargs
|
||||||
|
public AnimationSequence( Animation<T>... anims ) {
|
||||||
|
this(0, Arrays.asList(anims));
|
||||||
|
}
|
||||||
|
|
||||||
|
public AnimationSequence( Collection<Animation<T>> anims ) {
|
||||||
|
this(0, anims);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AnimationSequence( int lag, Collection<Animation<T>> anims ) {
|
||||||
|
super(Easing::linear);
|
||||||
|
|
||||||
|
this.anims = List.copyOf(anims);
|
||||||
|
this.lag = lag;
|
||||||
|
|
||||||
|
this.runtime = (anims.size() - 1) * lag + anims.stream().mapToInt(Animation::getRuntime).sum();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T getAnimationTarget() {
|
||||||
|
for( Animation<T> anim : anims ) {
|
||||||
|
if( anim.isActive() ) {
|
||||||
|
return anim.getAnimationTarget();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if( this.finished ) {
|
||||||
|
return anims.get(anims.size() - 1).getAnimationTarget();
|
||||||
|
} else {
|
||||||
|
return anims.get(0).getAnimationTarget();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DoubleUnaryOperator getEasing() {
|
||||||
|
for( Animation<T> anim : anims ) {
|
||||||
|
if( anim.isActive() ) {
|
||||||
|
return anim.getEasing();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if( this.finished ) {
|
||||||
|
return anims.get(anims.size() - 1).getEasing();
|
||||||
|
} else {
|
||||||
|
return anims.get(0).getEasing();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Override
|
||||||
|
// public void update( double delta ) {
|
||||||
|
// elapsedTime += (int) (delta * 1000);
|
||||||
|
//
|
||||||
|
// // Animation is done. Stop all Animations.
|
||||||
|
// if( elapsedTime > runtime ) {
|
||||||
|
// for( int i = 0; i < anims.size(); i++ ) {
|
||||||
|
// if( anims.get(i).isActive() ) {
|
||||||
|
// anims.get(i).elapsedTime = anims.get(i).runtime;
|
||||||
|
// anims.get(i).stop();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// elapsedTime = runtime;
|
||||||
|
// running = false;
|
||||||
|
// this.stop();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Animation<T> curAnim = null;
|
||||||
|
// if( elapsedTime > nextStart ) {
|
||||||
|
// currentAnimation += 1;
|
||||||
|
// curAnim = anims.get(currentAnimation);
|
||||||
|
// currentStart = nextStart;
|
||||||
|
// nextStart += lag + curAnim.getRuntime();
|
||||||
|
// curAnim.start();
|
||||||
|
// } else {
|
||||||
|
// curAnim = anims.get(currentAnimation);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Calculate delta for current animation
|
||||||
|
// double t = (double) (elapsedTime - currentStart) / (double) curAnim.getRuntime();
|
||||||
|
// if( t >= 1.0 ) {
|
||||||
|
// curAnim.elapsedTime = curAnim.runtime;
|
||||||
|
// curAnim.stop();
|
||||||
|
// } else {
|
||||||
|
// curAnim.animate(curAnim.easing.applyAsDouble(t));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void finish() {
|
||||||
|
for( Animation<T> anim : anims ) {
|
||||||
|
if( anim.isActive() ) {
|
||||||
|
anim.elapsedTime = anim.runtime;
|
||||||
|
anim.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void animate( double e ) {
|
||||||
|
Animation<T> curAnim = null;
|
||||||
|
if( running && elapsedTime > nextStart ) {
|
||||||
|
currentAnimationIndex += 1;
|
||||||
|
curAnim = anims.get(currentAnimationIndex);
|
||||||
|
currentStart = nextStart;
|
||||||
|
nextStart += lag + curAnim.getRuntime();
|
||||||
|
curAnim.start();
|
||||||
|
} else {
|
||||||
|
curAnim = anims.get(currentAnimationIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate delta for current animation
|
||||||
|
double t = (double) (elapsedTime - currentStart) / (double) curAnim.getRuntime();
|
||||||
|
if( t >= 1.0 ) {
|
||||||
|
curAnim.elapsedTime = curAnim.runtime;
|
||||||
|
curAnim.stop();
|
||||||
|
} else {
|
||||||
|
curAnim.elapsedTime = (elapsedTime - currentStart);
|
||||||
|
curAnim.animate(curAnim.easing.applyAsDouble(t));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -123,8 +123,8 @@ public class Animations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static final <T> Future<T> animateProperty( T target, final double from, final double to, int runtime, DoubleUnaryOperator easing, DoubleConsumer propSetter ) {
|
public static final <T> Future<T> animateProperty( T target, final double from, final double to, int runtime, DoubleUnaryOperator easing, DoubleConsumer propSetter ) {
|
||||||
Validator.requireNotNull(target);
|
Validator.requireNotNull(target, "target");
|
||||||
Validator.requireNotNull(propSetter);
|
Validator.requireNotNull(propSetter, "propSetter");
|
||||||
return play(target, runtime, easing, ( e ) -> propSetter.accept(Constants.interpolate(from, to, e)));
|
return play(target, runtime, easing, ( e ) -> propSetter.accept(Constants.interpolate(from, to, e)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,33 +7,87 @@ import schule.ngb.zm.shapes.Shape;
|
|||||||
|
|
||||||
import java.util.function.DoubleUnaryOperator;
|
import java.util.function.DoubleUnaryOperator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Animates the {@code target} in a circular motion centered at (<var>cx</var>, <var>cy</var>).
|
||||||
|
*/
|
||||||
public class CircleAnimation extends Animation<Shape> {
|
public class CircleAnimation extends Animation<Shape> {
|
||||||
|
|
||||||
private Shape object;
|
private final Shape target;
|
||||||
|
|
||||||
private double centerx, centery, radius, startangle;
|
private final double centerX, centerY, rotateTo;
|
||||||
|
|
||||||
public CircleAnimation( Shape target, double cx, double cy, int runtime, DoubleUnaryOperator easing ) {
|
private double rotationRadius, startAngle;
|
||||||
|
|
||||||
|
private final boolean rotateRight;
|
||||||
|
|
||||||
|
public CircleAnimation( Shape target, double cx, double cy ) {
|
||||||
|
this(target, cx, cy, 360, true, DEFAULT_ANIM_RUNTIME, DEFAULT_EASING);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CircleAnimation( Shape target, double cx, double cy, double rotateTo ) {
|
||||||
|
this(target, cx, cy, rotateTo, true, DEFAULT_ANIM_RUNTIME, DEFAULT_EASING);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CircleAnimation( Shape target, double cx, double cy, boolean rotateRight ) {
|
||||||
|
this(target, cx, cy, 360, rotateRight, DEFAULT_ANIM_RUNTIME, DEFAULT_EASING);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CircleAnimation( Shape target, double cx, double cy, double rotateTo, boolean rotateRight ) {
|
||||||
|
this(target, cx, cy, rotateTo, rotateRight, DEFAULT_ANIM_RUNTIME, DEFAULT_EASING);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CircleAnimation( Shape target, double cx, double cy, int runtime ) {
|
||||||
|
this(target, cx, cy, 360, true, runtime, DEFAULT_EASING);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CircleAnimation( Shape target, double cx, double cy, boolean rotateRight, int runtime ) {
|
||||||
|
this(target, cx, cy, 360, rotateRight, runtime, DEFAULT_EASING);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CircleAnimation( Shape target, double cx, double cy, DoubleUnaryOperator easing ) {
|
||||||
|
this(target, cx, cy, 360, true, DEFAULT_ANIM_RUNTIME, easing);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CircleAnimation( Shape target, double cx, double cy, boolean rotateRight, DoubleUnaryOperator easing ) {
|
||||||
|
this(target, cx, cy, 360, rotateRight, DEFAULT_ANIM_RUNTIME, easing);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CircleAnimation( Shape target, double cx, double cy, double rotateTo, int runtime, DoubleUnaryOperator easing ) {
|
||||||
|
this(target, cx, cy, rotateTo, true, runtime, easing);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CircleAnimation( Shape target, double cx, double cy, double rotateTo, boolean rotateRight, int runtime, DoubleUnaryOperator easing ) {
|
||||||
super(runtime, easing);
|
super(runtime, easing);
|
||||||
object = target;
|
this.target = target;
|
||||||
centerx = cx;
|
this.centerX = cx;
|
||||||
centery = cy;
|
this.centerY = cy;
|
||||||
Vector vec = new Vector(target.getX(), target.getY()).sub(cx, cy);
|
this.rotateTo = Constants.radians(Constants.limit(rotateTo, 0, 360));
|
||||||
startangle = vec.heading();
|
this.rotateRight = rotateRight;
|
||||||
radius = vec.length();
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize() {
|
||||||
|
Vector vec = new Vector(target.getX(), target.getY()).sub(centerX, centerY);
|
||||||
|
startAngle = vec.heading();
|
||||||
|
rotationRadius = vec.length();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Shape getAnimationTarget() {
|
public Shape getAnimationTarget() {
|
||||||
return object;
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void animate( double e ) {
|
public void animate( double e ) {
|
||||||
double angle = startangle + Constants.radians(Constants.interpolate(0, 360, e));
|
double angle = startAngle;
|
||||||
double x = centerx + radius * Constants.cos(angle);
|
if( rotateRight ) {
|
||||||
double y = centery + radius * Constants.sin(angle);
|
angle += Constants.interpolate(0, rotateTo, e);
|
||||||
object.moveTo(x, y);
|
} else {
|
||||||
|
angle -= Constants.interpolate(0, rotateTo, e);
|
||||||
|
}
|
||||||
|
double x = centerX + rotationRadius * Constants.cos(angle);
|
||||||
|
double y = centerY + rotationRadius * Constants.sin(angle);
|
||||||
|
target.moveTo(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ public class ContinousAnimation<T> extends Animation<T> {
|
|||||||
private int lag = 0;
|
private int lag = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Speichert eine Approximation der aktuellen Steigung der Easing-Funktion,
|
* Speichert eine Approximation der aktuellen Steigung der Easing-Funktion, um im Fall
|
||||||
* um im Fall {@code easeInOnly == true} nach dem ersten Durchlauf die
|
* {@code easeInOnly == true} nach dem ersten Durchlauf die passende Geschwindigkeit
|
||||||
* passende Geschwindigkeit beizubehalten.
|
* beizubehalten.
|
||||||
*/
|
*/
|
||||||
private double m = 1.0, lastEase = 0.0;
|
private double m = 1.0, lastEase = 0.0;
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ public class ContinousAnimation<T> extends Animation<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ContinousAnimation( Animation<T> baseAnimation, int lag, boolean easeInOnly ) {
|
private ContinousAnimation( Animation<T> baseAnimation, int lag, boolean easeInOnly ) {
|
||||||
super(baseAnimation.getRuntime(), baseAnimation.getEasing());
|
super(baseAnimation.getRuntime() + lag, baseAnimation.getEasing());
|
||||||
this.baseAnimation = baseAnimation;
|
this.baseAnimation = baseAnimation;
|
||||||
this.lag = lag;
|
this.lag = lag;
|
||||||
this.easeInOnly = easeInOnly;
|
this.easeInOnly = easeInOnly;
|
||||||
@@ -40,35 +40,80 @@ public class ContinousAnimation<T> extends Animation<T> {
|
|||||||
return baseAnimation.getAnimationTarget();
|
return baseAnimation.getAnimationTarget();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRuntime() {
|
||||||
|
return Integer.MAX_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Override
|
||||||
|
// public void update( double delta ) {
|
||||||
|
// elapsedTime += (int) (delta * 1000);
|
||||||
|
// if( elapsedTime >= runtime + lag ) {
|
||||||
|
// elapsedTime %= (runtime + lag);
|
||||||
|
//
|
||||||
|
// if( easeInOnly && easing != null ) {
|
||||||
|
// easing = null;
|
||||||
|
// // runtime = (int)((1.0/m)*(runtime + lag));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// double t = (double) elapsedTime / (double) runtime;
|
||||||
|
// if( t >= 1.0 ) {
|
||||||
|
// t = 1.0;
|
||||||
|
// }
|
||||||
|
// if( easing != null ) {
|
||||||
|
// double e = easing.applyAsDouble(t);
|
||||||
|
// animate(e);
|
||||||
|
// m = (e-lastEase)/(delta*1000/(asDouble(runtime)));
|
||||||
|
// lastEase = e;
|
||||||
|
// } else {
|
||||||
|
// animate(t);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void finish() {
|
||||||
|
baseAnimation.elapsedTime = baseAnimation.getRuntime();
|
||||||
|
baseAnimation.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize() {
|
||||||
|
baseAnimation.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRuntime( int pRuntime ) {
|
||||||
|
baseAnimation.setRuntime(pRuntime);
|
||||||
|
runtime = pRuntime + lag;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void update( double delta ) {
|
public void update( double delta ) {
|
||||||
elapsedTime += (int) (delta * 1000);
|
int currentRuntime = elapsedTime + (int) (delta * 1000);
|
||||||
if( elapsedTime >= runtime + lag ) {
|
if( currentRuntime >= runtime + lag ) {
|
||||||
elapsedTime %= (runtime + lag);
|
elapsedTime = currentRuntime % (runtime + lag);
|
||||||
|
|
||||||
if( easeInOnly && easing != null ) {
|
if( easeInOnly && easing != null ) {
|
||||||
easing = null;
|
easing = Easing.linear();
|
||||||
// runtime = (int)((1.0/m)*(runtime + lag));
|
// runtime = (int)((1.0/m)*(runtime + lag));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
double t = (double) elapsedTime / (double) runtime;
|
super.update(delta);
|
||||||
if( t >= 1.0 ) {
|
|
||||||
t = 1.0;
|
|
||||||
}
|
|
||||||
if( easing != null ) {
|
|
||||||
double e = easing.applyAsDouble(t);
|
|
||||||
animate(e);
|
|
||||||
m = (e-lastEase)/(delta*1000/(asDouble(runtime)));
|
|
||||||
lastEase = e;
|
|
||||||
} else {
|
|
||||||
animate(t);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void animate( double e ) {
|
public void animate( double e ) {
|
||||||
|
// double t = (double) elapsedTime / (double) runtime;
|
||||||
|
// if( t >= 1.0 ) {
|
||||||
|
// t = 1.0;
|
||||||
|
// }
|
||||||
|
baseAnimation.elapsedTime = elapsedTime;
|
||||||
baseAnimation.animate(e);
|
baseAnimation.animate(e);
|
||||||
|
m = (e - lastEase) / (delta * 1000 / (asDouble(runtime)));
|
||||||
|
lastEase = e;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,32 +13,51 @@ public class FadeAnimation extends Animation<Shape> {
|
|||||||
|
|
||||||
public static final int FADE_OUT = 0;
|
public static final int FADE_OUT = 0;
|
||||||
|
|
||||||
private Shape object;
|
private final Shape target;
|
||||||
|
|
||||||
|
private final int targetAlpha;
|
||||||
|
|
||||||
private Color fill, stroke;
|
private Color fill, stroke;
|
||||||
|
|
||||||
private int fillAlpha, strokeAlpha, tAlpha;
|
private int fillAlpha, strokeAlpha;
|
||||||
|
|
||||||
public FadeAnimation( Shape object, int alpha, int runtime, DoubleUnaryOperator easing ) {
|
public FadeAnimation( Shape target, int targetAlpha ) {
|
||||||
|
this(target, targetAlpha, DEFAULT_ANIM_RUNTIME, DEFAULT_EASING);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FadeAnimation( Shape target, int targetAlpha, int runtime ) {
|
||||||
|
this(target, targetAlpha, runtime, DEFAULT_EASING);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FadeAnimation( Shape target, int runtime, DoubleUnaryOperator easing ) {
|
||||||
|
this(target, 0, runtime, easing);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FadeAnimation( Shape target, int targetAlpha, int runtime, DoubleUnaryOperator easing ) {
|
||||||
super(runtime, easing);
|
super(runtime, easing);
|
||||||
|
|
||||||
this.object = object;
|
this.target = target;
|
||||||
fill = object.getFillColor();
|
this.targetAlpha = targetAlpha;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize() {
|
||||||
|
fill = target.getFillColor();
|
||||||
fillAlpha = fill.getAlpha();
|
fillAlpha = fill.getAlpha();
|
||||||
stroke = object.getStrokeColor();
|
stroke = target.getStrokeColor();
|
||||||
strokeAlpha = stroke.getAlpha();
|
strokeAlpha = stroke.getAlpha();
|
||||||
tAlpha = alpha;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Shape getAnimationTarget() {
|
public Shape getAnimationTarget() {
|
||||||
return object;
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void animate( double e ) {
|
public void animate( double e ) {
|
||||||
object.setFillColor(new Color(fill, (int) Constants.interpolate(fillAlpha, tAlpha, e)));
|
target.setFillColor(fill, (int) Constants.interpolate(fillAlpha, targetAlpha, e));
|
||||||
object.setStrokeColor(new Color(stroke, (int) Constants.interpolate(strokeAlpha, tAlpha, e)));
|
target.setStrokeColor(stroke, (int) Constants.interpolate(strokeAlpha, targetAlpha, e));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,28 @@
|
|||||||
package schule.ngb.zm.anim;
|
package schule.ngb.zm.anim;
|
||||||
|
|
||||||
import schule.ngb.zm.Color;
|
import schule.ngb.zm.Color;
|
||||||
import schule.ngb.zm.Constants;
|
|
||||||
import schule.ngb.zm.shapes.Shape;
|
import schule.ngb.zm.shapes.Shape;
|
||||||
|
|
||||||
import java.util.function.DoubleUnaryOperator;
|
import java.util.function.DoubleUnaryOperator;
|
||||||
|
|
||||||
public class FillAnimation extends Animation<Shape> {
|
public class FillAnimation extends Animation<Shape> {
|
||||||
|
|
||||||
private Shape object;
|
private final Shape object;
|
||||||
|
|
||||||
private Color oFill, tFill;
|
private Color originFill;
|
||||||
|
|
||||||
public FillAnimation( Shape object, Color newFill, int runtime, DoubleUnaryOperator easing ) {
|
private final Color targetFill;
|
||||||
|
|
||||||
|
public FillAnimation( Shape target, Color newFill, int runtime, DoubleUnaryOperator easing ) {
|
||||||
super(runtime, easing);
|
super(runtime, easing);
|
||||||
|
|
||||||
this.object = object;
|
this.object = target;
|
||||||
oFill = object.getFillColor();
|
targetFill = newFill;
|
||||||
tFill = newFill;
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize() {
|
||||||
|
originFill = object.getFillColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -27,7 +32,7 @@ public class FillAnimation extends Animation<Shape> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void animate( double e ) {
|
public void animate( double e ) {
|
||||||
object.setFillColor(Color.interpolate(oFill, tFill, e));
|
object.setFillColor(Color.interpolate(originFill, targetFill, e));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,31 @@
|
|||||||
package schule.ngb.zm.anim;
|
package schule.ngb.zm.anim;
|
||||||
|
|
||||||
import schule.ngb.zm.Color;
|
|
||||||
import schule.ngb.zm.Constants;
|
import schule.ngb.zm.Constants;
|
||||||
import schule.ngb.zm.shapes.Circle;
|
|
||||||
import schule.ngb.zm.shapes.Ellipse;
|
|
||||||
import schule.ngb.zm.shapes.Rectangle;
|
|
||||||
import schule.ngb.zm.shapes.Shape;
|
import schule.ngb.zm.shapes.Shape;
|
||||||
|
|
||||||
import java.util.function.DoubleUnaryOperator;
|
import java.util.function.DoubleUnaryOperator;
|
||||||
|
|
||||||
public class MoveAnimation extends Animation<Shape> {
|
public class MoveAnimation extends Animation<Shape> {
|
||||||
|
|
||||||
private Shape object;
|
private final Shape object;
|
||||||
|
|
||||||
private double oX, oY, tX, tY;
|
private final double targetX, targetY;
|
||||||
|
|
||||||
public MoveAnimation( Shape object, double x, double y, int runtime, DoubleUnaryOperator easing ) {
|
private double originX, originY;
|
||||||
|
|
||||||
|
|
||||||
|
public MoveAnimation( Shape target, double targetX, double targetY, int runtime, DoubleUnaryOperator easing ) {
|
||||||
super(runtime, easing);
|
super(runtime, easing);
|
||||||
|
|
||||||
this.object = object;
|
this.object = target;
|
||||||
oX = object.getX();
|
this.targetX = targetX;
|
||||||
oY = object.getY();
|
this.targetY = targetY;
|
||||||
tX = x;
|
}
|
||||||
tY = y;
|
|
||||||
|
@Override
|
||||||
|
public void initialize() {
|
||||||
|
originX = object.getX();
|
||||||
|
originY = object.getY();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -32,8 +35,8 @@ public class MoveAnimation extends Animation<Shape> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void animate( double e ) {
|
public void animate( double e ) {
|
||||||
object.setX(Constants.interpolate(oX, tX, e));
|
object.setX(Constants.interpolate(originX, targetX, e));
|
||||||
object.setY(Constants.interpolate(oY, tY, e));
|
object.setY(Constants.interpolate(originY, targetY, e));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
12
src/main/java/schule/ngb/zm/anim/package-info.java
Normal file
12
src/main/java/schule/ngb/zm/anim/package-info.java
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* Dieses Paket enthält Klassen zur Animation von
|
||||||
|
* {@link schule.ngb.zm.shapes.Shape} Objekten auf einem
|
||||||
|
* {@link schule.ngb.zm.layers.ShapesLayer}.
|
||||||
|
* <p>
|
||||||
|
* Mit den Animationsklassen lassen sich neben {@code Shape} Objekten aber auch
|
||||||
|
* andere Objekte animieren.
|
||||||
|
* <p>
|
||||||
|
* Das Paket setzt auf den funktionalen Programmierschnittstellen von Java auf
|
||||||
|
* und kann als Einführung in das Paradigma dienen.
|
||||||
|
*/
|
||||||
|
package schule.ngb.zm.anim;
|
||||||
@@ -404,6 +404,11 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
|
|||||||
return shapeDelegate.getStrokeType();
|
return shapeDelegate.getStrokeType();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Options.StrokeJoin getStrokeJoin() {
|
||||||
|
return shapeDelegate.getStrokeJoin();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setzt den Typ der Kontur. Erlaubte Werte sind {@link #DASHED},
|
* Setzt den Typ der Kontur. Erlaubte Werte sind {@link #DASHED},
|
||||||
* {@link #DOTTED} und {@link #SOLID}.
|
* {@link #DOTTED} und {@link #SOLID}.
|
||||||
@@ -923,12 +928,43 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void curveTo( double ctrlX, double ctrlY, double x, double y ) {
|
||||||
|
if( !pathStarted ) {
|
||||||
|
path.moveTo(x, y);
|
||||||
|
pathStarted = true;
|
||||||
|
} else {
|
||||||
|
path.quadTo(
|
||||||
|
ctrlX, ctrlY,
|
||||||
|
x, y
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void curveTo( double ctrlX1, double ctrlY1, double ctrlX2, double ctrlY2, double x, double y ) {
|
||||||
|
if( !pathStarted ) {
|
||||||
|
path.moveTo(x, y);
|
||||||
|
pathStarted = true;
|
||||||
|
} else {
|
||||||
|
path.curveTo(
|
||||||
|
ctrlX1, ctrlY1,
|
||||||
|
ctrlX2, ctrlY2,
|
||||||
|
x, y
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Beendet eine zuvor {@link #beginShape() begonnene} Freihand-Form und
|
* Beendet eine zuvor {@link #beginShape() begonnene} Freihand-Form und
|
||||||
* zeichent sie auf die Zeichenebene.
|
* zeichent sie auf die Zeichenebene.
|
||||||
*/
|
*/
|
||||||
public void endShape() {
|
public void endShape() {
|
||||||
|
endShape(CLOSED);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void endShape( Options.PathType closingType ) {
|
||||||
|
if( closingType == Options.PathType.CLOSED ) {
|
||||||
path.closePath();
|
path.closePath();
|
||||||
|
}
|
||||||
path.trimToSize();
|
path.trimToSize();
|
||||||
pathStarted = false;
|
pathStarted = false;
|
||||||
|
|
||||||
@@ -949,7 +985,7 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
|
|||||||
* @see ImageLoader#loadImage(String)
|
* @see ImageLoader#loadImage(String)
|
||||||
*/
|
*/
|
||||||
public void image( String imageSource, double x, double y ) {
|
public void image( String imageSource, double x, double y ) {
|
||||||
image(ImageLoader.loadImage(imageSource), x, y, 1.0, shapeDelegate.getAnchor());
|
imageScale(ImageLoader.loadImage(imageSource), x, y, 1.0, shapeDelegate.getAnchor());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -966,7 +1002,7 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
|
|||||||
* @see ImageLoader#loadImage(String)
|
* @see ImageLoader#loadImage(String)
|
||||||
*/
|
*/
|
||||||
public void image( String imageSource, double x, double y, Options.Direction anchor ) {
|
public void image( String imageSource, double x, double y, Options.Direction anchor ) {
|
||||||
image(ImageLoader.loadImage(imageSource), x, y, 1.0, anchor);
|
imageScale(ImageLoader.loadImage(imageSource), x, y, 1.0, anchor);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -974,8 +1010,9 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
|
|||||||
* Koordinaten auf die Zeichenebene. Das Bild wird um den angegebenen Faktor
|
* Koordinaten auf die Zeichenebene. Das Bild wird um den angegebenen Faktor
|
||||||
* skaliert.
|
* skaliert.
|
||||||
* <p>
|
* <p>
|
||||||
* Siehe {@link #image(Image, double, double, double, Options.Direction)}
|
* Siehe
|
||||||
* für mehr Details.
|
* {@link #imageScale(Image, double, double, double, Options.Direction)} für
|
||||||
|
* mehr Details.
|
||||||
*
|
*
|
||||||
* @param imageSource Die Bildquelle.
|
* @param imageSource Die Bildquelle.
|
||||||
* @param x x-Koordinate des Ankerpunktes.
|
* @param x x-Koordinate des Ankerpunktes.
|
||||||
@@ -983,8 +1020,8 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
|
|||||||
* @param scale Der Skalierungsfaktor des Bildes.
|
* @param scale Der Skalierungsfaktor des Bildes.
|
||||||
* @see ImageLoader#loadImage(String)
|
* @see ImageLoader#loadImage(String)
|
||||||
*/
|
*/
|
||||||
public void image( String imageSource, double x, double y, double scale ) {
|
public void imageScale( String imageSource, double x, double y, double scale ) {
|
||||||
image(ImageLoader.loadImage(imageSource), x, y, scale, shapeDelegate.getAnchor());
|
imageScale(ImageLoader.loadImage(imageSource), x, y, scale, shapeDelegate.getAnchor());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -992,8 +1029,9 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
|
|||||||
* Koordinaten auf die Zeichenebene. Das Bild wird um den angegebenen Faktor
|
* Koordinaten auf die Zeichenebene. Das Bild wird um den angegebenen Faktor
|
||||||
* skaliert und der angegebene Ankerpunkt verwendet.
|
* skaliert und der angegebene Ankerpunkt verwendet.
|
||||||
* <p>
|
* <p>
|
||||||
* Siehe {@link #image(Image, double, double, double, Options.Direction)}
|
* Siehe
|
||||||
* für mehr Details.
|
* {@link #imageScale(Image, double, double, double, Options.Direction)} für
|
||||||
|
* mehr Details.
|
||||||
*
|
*
|
||||||
* @param imageSource Die Bildquelle.
|
* @param imageSource Die Bildquelle.
|
||||||
* @param x x-Koordinate des Ankerpunktes.
|
* @param x x-Koordinate des Ankerpunktes.
|
||||||
@@ -1002,8 +1040,8 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
|
|||||||
* @param anchor Der Ankerpunkt.
|
* @param anchor Der Ankerpunkt.
|
||||||
* @see ImageLoader#loadImage(String)
|
* @see ImageLoader#loadImage(String)
|
||||||
*/
|
*/
|
||||||
public void image( String imageSource, double x, double y, double scale, Options.Direction anchor ) {
|
public void imageScale( String imageSource, double x, double y, double scale, Options.Direction anchor ) {
|
||||||
image(ImageLoader.loadImage(imageSource), x, y, scale, anchor);
|
imageScale(ImageLoader.loadImage(imageSource), x, y, scale, anchor);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1015,23 +1053,24 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
|
|||||||
* @param y y-Koordinate des Ankerpunktes.
|
* @param y y-Koordinate des Ankerpunktes.
|
||||||
*/
|
*/
|
||||||
public void image( Image image, double x, double y ) {
|
public void image( Image image, double x, double y ) {
|
||||||
image(image, x, y, 1.0, shapeDelegate.getAnchor());
|
imageScale(image, x, y, 1.0, shapeDelegate.getAnchor());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Zeichnet das angegebene Bild an den angegebenen Koordinaten auf die
|
* Zeichnet das angegebene Bild an den angegebenen Koordinaten auf die
|
||||||
* Zeichenebene. Das Bild wird um den angegebenen Faktor skaliert.
|
* Zeichenebene. Das Bild wird um den angegebenen Faktor skaliert.
|
||||||
* <p>
|
* <p>
|
||||||
* Siehe {@link #image(Image, double, double, double, Options.Direction)}
|
* Siehe
|
||||||
* für mehr Details.
|
* {@link #imageScale(Image, double, double, double, Options.Direction)} für
|
||||||
|
* mehr Details.
|
||||||
*
|
*
|
||||||
* @param image Das vorher geladene Bild.
|
* @param image Das vorher geladene Bild.
|
||||||
* @param x x-Koordinate des Ankerpunktes.
|
* @param x x-Koordinate des Ankerpunktes.
|
||||||
* @param y y-Koordinate des Ankerpunktes.
|
* @param y y-Koordinate des Ankerpunktes.
|
||||||
* @param scale Der Skalierungsfaktor des Bildes.
|
* @param scale Der Skalierungsfaktor des Bildes.
|
||||||
*/
|
*/
|
||||||
public void image( Image image, double x, double y, double scale ) {
|
public void imageScale( Image image, double x, double y, double scale ) {
|
||||||
image(image, x, y, scale, shapeDelegate.getAnchor());
|
imageScale(image, x, y, scale, shapeDelegate.getAnchor());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1046,8 +1085,8 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
|
|||||||
* Das Seitenverhältnis wird immer beibehalten.
|
* Das Seitenverhältnis wird immer beibehalten.
|
||||||
* <p>
|
* <p>
|
||||||
* Soll das Bild innerhalb eines vorgegebenen Rechtecks liegen, sollte
|
* Soll das Bild innerhalb eines vorgegebenen Rechtecks liegen, sollte
|
||||||
* {@link #image(Image, double, double, double, double, Options.Direction)}
|
* {@link #imageScale(Image, double, double, double, double,
|
||||||
* verwendet werden.
|
* Options.Direction)} verwendet werden.
|
||||||
*
|
*
|
||||||
* @param image Das vorher geladene Bild.
|
* @param image Das vorher geladene Bild.
|
||||||
* @param x x-Koordinate des Ankerpunktes.
|
* @param x x-Koordinate des Ankerpunktes.
|
||||||
@@ -1055,7 +1094,7 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
|
|||||||
* @param scale Der Skalierungsfaktor des Bildes.
|
* @param scale Der Skalierungsfaktor des Bildes.
|
||||||
* @param anchor Der Ankerpunkt.
|
* @param anchor Der Ankerpunkt.
|
||||||
*/
|
*/
|
||||||
public void image( Image image, double x, double y, double scale, Options.Direction anchor ) {
|
public void imageScale( Image image, double x, double y, double scale, Options.Direction anchor ) {
|
||||||
/*if( image != null ) {
|
/*if( image != null ) {
|
||||||
double neww = image.getWidth(null) * scale;
|
double neww = image.getWidth(null) * scale;
|
||||||
double newh = image.getHeight(null) * scale;
|
double newh = image.getHeight(null) * scale;
|
||||||
@@ -1064,7 +1103,7 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
|
|||||||
}*/
|
}*/
|
||||||
double neww = image.getWidth(null) * scale;
|
double neww = image.getWidth(null) * scale;
|
||||||
double newh = image.getHeight(null) * scale;
|
double newh = image.getHeight(null) * scale;
|
||||||
image(image, x, y, neww, newh, anchor);
|
imageScale(image, x, y, neww, newh, anchor);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1072,8 +1111,8 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
|
|||||||
* Koordinaten in der angegebenen Größe auf die Zeichenebene.
|
* Koordinaten in der angegebenen Größe auf die Zeichenebene.
|
||||||
* <p>
|
* <p>
|
||||||
* Siehe
|
* Siehe
|
||||||
* {@link #image(Image, double, double, double, double, Options.Direction)}
|
* {@link #imageScale(Image, double, double, double, double,
|
||||||
* für mehr Details.
|
* Options.Direction)} für mehr Details.
|
||||||
*
|
*
|
||||||
* @param imageSource Die Bildquelle.
|
* @param imageSource Die Bildquelle.
|
||||||
* @param x x-Koordinate des Ankerpunktes.
|
* @param x x-Koordinate des Ankerpunktes.
|
||||||
@@ -1082,8 +1121,8 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
|
|||||||
* @param height Höhe des Bildes auf der Zeichenebene oder 0.
|
* @param height Höhe des Bildes auf der Zeichenebene oder 0.
|
||||||
* @see ImageLoader#loadImage(String)
|
* @see ImageLoader#loadImage(String)
|
||||||
*/
|
*/
|
||||||
public void image( String imageSource, double x, double y, double width, double height ) {
|
public void imageScale( String imageSource, double x, double y, double width, double height ) {
|
||||||
image(ImageLoader.loadImage(imageSource), x, y, width, height, shapeDelegate.getAnchor());
|
imageScale(ImageLoader.loadImage(imageSource), x, y, width, height, shapeDelegate.getAnchor());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1092,8 +1131,8 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
|
|||||||
* angegebene Ankerpunkt verwendet.
|
* angegebene Ankerpunkt verwendet.
|
||||||
* <p>
|
* <p>
|
||||||
* Siehe
|
* Siehe
|
||||||
* {@link #image(Image, double, double, double, double, Options.Direction)}
|
* {@link #imageScale(Image, double, double, double, double,
|
||||||
* für mehr Details.
|
* Options.Direction)} für mehr Details.
|
||||||
*
|
*
|
||||||
* @param imageSource Die Bildquelle.
|
* @param imageSource Die Bildquelle.
|
||||||
* @param x x-Koordinate des Ankerpunktes.
|
* @param x x-Koordinate des Ankerpunktes.
|
||||||
@@ -1103,8 +1142,8 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
|
|||||||
* @param anchor Der Ankerpunkt.
|
* @param anchor Der Ankerpunkt.
|
||||||
* @see ImageLoader#loadImage(String)
|
* @see ImageLoader#loadImage(String)
|
||||||
*/
|
*/
|
||||||
public void image( String imageSource, double x, double y, double width, double height, Options.Direction anchor ) {
|
public void imageScale( String imageSource, double x, double y, double width, double height, Options.Direction anchor ) {
|
||||||
image(ImageLoader.loadImage(imageSource), x, y, width, height, anchor);
|
imageScale(ImageLoader.loadImage(imageSource), x, y, width, height, anchor);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1112,8 +1151,8 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
|
|||||||
* angegebenen Größe auf die Zeichenebene.
|
* angegebenen Größe auf die Zeichenebene.
|
||||||
* <p>
|
* <p>
|
||||||
* Siehe
|
* Siehe
|
||||||
* {@link #image(Image, double, double, double, double, Options.Direction)}
|
* {@link #imageScale(Image, double, double, double, double,
|
||||||
* für mehr Details.
|
* Options.Direction)} für mehr Details.
|
||||||
*
|
*
|
||||||
* @param image Ein Bild-Objekt.
|
* @param image Ein Bild-Objekt.
|
||||||
* @param x x-Koordinate des Ankerpunktes.
|
* @param x x-Koordinate des Ankerpunktes.
|
||||||
@@ -1121,8 +1160,8 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
|
|||||||
* @param width Breite des Bildes auf der Zeichenebene oder 0.
|
* @param width Breite des Bildes auf der Zeichenebene oder 0.
|
||||||
* @param height Höhe des Bildes auf der Zeichenebene oder 0.
|
* @param height Höhe des Bildes auf der Zeichenebene oder 0.
|
||||||
*/
|
*/
|
||||||
public void image( Image image, double x, double y, double width, double height ) {
|
public void imageScale( Image image, double x, double y, double width, double height ) {
|
||||||
image(image, x, y, width, height, shapeDelegate.getAnchor());
|
imageScale(image, x, y, width, height, shapeDelegate.getAnchor());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1140,7 +1179,7 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
|
|||||||
* <p>
|
* <p>
|
||||||
* Soll die Bildgröße unter Beachtung der Abmessungen um einen Faktor
|
* Soll die Bildgröße unter Beachtung der Abmessungen um einen Faktor
|
||||||
* verändert werden, sollte
|
* verändert werden, sollte
|
||||||
* {@link #image(Image, double, double, double, Options.Direction)}
|
* {@link #imageScale(Image, double, double, double, Options.Direction)}
|
||||||
* verwendet werden.
|
* verwendet werden.
|
||||||
*
|
*
|
||||||
* @param image Ein Bild-Objekt.
|
* @param image Ein Bild-Objekt.
|
||||||
@@ -1150,17 +1189,163 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
|
|||||||
* @param height Höhe des Bildes auf der Zeichenebene oder 0.
|
* @param height Höhe des Bildes auf der Zeichenebene oder 0.
|
||||||
* @param anchor Der Ankerpunkt.
|
* @param anchor Der Ankerpunkt.
|
||||||
*/
|
*/
|
||||||
public void image( Image image, double x, double y, double width, double height, Options.Direction anchor ) {
|
public void imageScale( Image image, double x, double y, double width, double height, Options.Direction anchor ) {
|
||||||
|
imageRotateAndScale(image, x, y, 0, width, height, anchor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zeichnet das Bild von der angegebenen Bildquelle an den angegebenen
|
||||||
|
* Koordinaten mit der angegebenen Drehung auf die Zeichenebene.
|
||||||
|
* <p>
|
||||||
|
* Das Bild wird um seinen Mittelpunkt als Rotationszentrum gedreht.
|
||||||
|
*
|
||||||
|
* @param imageSource Die Bildquelle.
|
||||||
|
* @param x x-Koordinate des Ankerpunktes.
|
||||||
|
* @param y y-Koordinate des Ankerpunktes.
|
||||||
|
* @param angle Winkel in Grad.
|
||||||
|
*/
|
||||||
|
public void imageRotate( String imageSource, double x, double y, double angle ) {
|
||||||
|
imageRotate(ImageLoader.loadImage(imageSource), x, y, angle, shapeDelegate.getAnchor());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zeichnet das Bild von der angegebenen Bildquelle an den angegebenen
|
||||||
|
* Koordinaten mit der angegebenen Drehung auf die Zeichenebene. Der
|
||||||
|
* angegebene Ankerpunkt wird verwendet.
|
||||||
|
* <p>
|
||||||
|
* Das Bild wird um seinen Mittelpunkt als Rotationszentrum gedreht.
|
||||||
|
*
|
||||||
|
* @param imageSource Die Bildquelle.
|
||||||
|
* @param x x-Koordinate des Ankerpunktes.
|
||||||
|
* @param y y-Koordinate des Ankerpunktes.
|
||||||
|
* @param angle Winkel in Grad.
|
||||||
|
* @param anchor Der Ankerpunkt.
|
||||||
|
*/
|
||||||
|
public void imageRotate( String imageSource, double x, double y, double angle, Options.Direction anchor ) {
|
||||||
|
imageRotate(ImageLoader.loadImage(imageSource), x, y, angle, anchor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zeichnet das angegebene Bild an den angegebenen Koordinaten mit der
|
||||||
|
* angegebenen Drehung auf die Zeichenebene.
|
||||||
|
* <p>
|
||||||
|
* Das Bild wird um seinen Mittelpunkt als Rotationszentrum gedreht.
|
||||||
|
*
|
||||||
|
* @param image Ein Bild-Objekt.
|
||||||
|
* @param x x-Koordinate des Ankerpunktes.
|
||||||
|
* @param y y-Koordinate des Ankerpunktes.
|
||||||
|
* @param angle Winkel in Grad.
|
||||||
|
*/
|
||||||
|
public void imageRotate( Image image, double x, double y, double angle ) {
|
||||||
|
imageRotateAndScale(image, x, y, angle, image.getWidth(null), image.getHeight(null), shapeDelegate.getAnchor());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zeichnet das angegebene Bild an den angegebenen Koordinaten mit der
|
||||||
|
* angegebenen Drehung auf die Zeichenebene. Der angegebene Ankerpunkt wird
|
||||||
|
* verwendet.
|
||||||
|
* <p>
|
||||||
|
* Das Bild wird um seinen Mittelpunkt als Rotationszentrum gedreht.
|
||||||
|
*
|
||||||
|
* @param image Ein Bild-Objekt.
|
||||||
|
* @param x x-Koordinate des Ankerpunktes.
|
||||||
|
* @param y y-Koordinate des Ankerpunktes.
|
||||||
|
* @param angle Winkel in Grad.
|
||||||
|
* @param anchor Der Ankerpunkt.
|
||||||
|
*/
|
||||||
|
public void imageRotate( Image image, double x, double y, double angle, Options.Direction anchor ) {
|
||||||
|
imageRotateAndScale(image, x, y, angle, image.getWidth(null), image.getHeight(null), anchor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zeichnet das Bild von der angegebenen Bildquelle an den angegebenen
|
||||||
|
* Koordinaten mit der angegebenen Drehung in der angegebenen Größe auf die
|
||||||
|
* Zeichenebene.
|
||||||
|
*
|
||||||
|
* @param imageSource Die Bildquelle.
|
||||||
|
* @param x x-Koordinate des Ankerpunktes.
|
||||||
|
* @param y y-Koordinate des Ankerpunktes.
|
||||||
|
* @param angle Winkel in Grad.
|
||||||
|
* @param width Breite des Bildes auf der Zeichenebene oder 0.
|
||||||
|
* @param height Höhe des Bildes auf der Zeichenebene oder 0.
|
||||||
|
* @see #imageRotate(String, double, double, double)
|
||||||
|
* @see #imageScale(Image, double, double, double)
|
||||||
|
*/
|
||||||
|
public void imageRotateAndScale( String imageSource, double x, double y, double angle, double width, double height ) {
|
||||||
|
imageRotateAndScale(ImageLoader.loadImage(imageSource), x, y, angle, width, height, shapeDelegate.getAnchor());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zeichnet das Bild von der angegebenen Bildquelle an den angegebenen
|
||||||
|
* Koordinaten mit der angegebenen Drehung in der angegebenen Größe auf die
|
||||||
|
* Zeichenebene. Der angegebene Ankerpunkt wird verwendet.
|
||||||
|
*
|
||||||
|
* @param imageSource Die Bildquelle.
|
||||||
|
* @param x x-Koordinate des Ankerpunktes.
|
||||||
|
* @param y y-Koordinate des Ankerpunktes.
|
||||||
|
* @param angle Winkel in Grad.
|
||||||
|
* @param width Breite des Bildes auf der Zeichenebene oder 0.
|
||||||
|
* @param height Höhe des Bildes auf der Zeichenebene oder 0.
|
||||||
|
* @param anchor Der Ankerpunkt.
|
||||||
|
* @see #imageRotate(String, double, double, double)
|
||||||
|
* @see #imageScale(Image, double, double, double)
|
||||||
|
*/
|
||||||
|
public void imageRotateAndScale( String imageSource, double x, double y, double angle, double width, double height, Options.Direction anchor ) {
|
||||||
|
imageRotateAndScale(ImageLoader.loadImage(imageSource), x, y, angle, width, height, anchor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zeichnet das angegebene Bild an den angegebenen Koordinaten mit der
|
||||||
|
* angegebenen Drehung in der angegebenen Größe auf die Zeichenebene. Der
|
||||||
|
* angegebene Ankerpunkt wird verwendet.
|
||||||
|
*
|
||||||
|
* @param image Ein Bild-Objekt.
|
||||||
|
* @param x x-Koordinate des Ankerpunktes.
|
||||||
|
* @param y y-Koordinate des Ankerpunktes.
|
||||||
|
* @param angle Winkel in Grad.
|
||||||
|
* @param width Breite des Bildes auf der Zeichenebene oder 0.
|
||||||
|
* @param height Höhe des Bildes auf der Zeichenebene oder 0.
|
||||||
|
* @see #imageRotate(String, double, double, double)
|
||||||
|
* @see #imageScale(Image, double, double, double)
|
||||||
|
*/
|
||||||
|
public void imageRotateAndScale( Image image, double x, double y, double angle, double width, double height ) {
|
||||||
|
imageRotateAndScale(image, x, y, angle, width, height, shapeDelegate.getAnchor());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zeichnet das angegebene Bild an den angegebenen Koordinaten mit der
|
||||||
|
* angegebenen Drehung in der angegebenen Größe auf die Zeichenebene. Der
|
||||||
|
* angegebene Ankerpunkt wird verwendet.
|
||||||
|
*
|
||||||
|
* @param image Ein Bild-Objekt.
|
||||||
|
* @param x x-Koordinate des Ankerpunktes.
|
||||||
|
* @param y y-Koordinate des Ankerpunktes.
|
||||||
|
* @param angle Winkel in Grad.
|
||||||
|
* @param width Breite des Bildes auf der Zeichenebene oder 0.
|
||||||
|
* @param height Höhe des Bildes auf der Zeichenebene oder 0.
|
||||||
|
* @param anchor Der Ankerpunkt.
|
||||||
|
* @see #imageRotate(String, double, double, double)
|
||||||
|
* @see #imageScale(Image, double, double, double)
|
||||||
|
*/
|
||||||
|
public void imageRotateAndScale( Image image, double x, double y, double angle, double width, double height, Options.Direction anchor ) {
|
||||||
// TODO: Use Validator or at least LOG a message if image == null?
|
// TODO: Use Validator or at least LOG a message if image == null?
|
||||||
if( image != null ) {
|
if( image != null ) {
|
||||||
|
AffineTransform orig = drawing.getTransform();
|
||||||
|
|
||||||
|
int imgWidth = image.getWidth(null);
|
||||||
|
int imgHeight = image.getHeight(null);
|
||||||
|
|
||||||
if( width == 0 ) {
|
if( width == 0 ) {
|
||||||
width = (height / image.getHeight(null)) * image.getWidth(null);
|
width = (height / imgHeight) * imgWidth;
|
||||||
} else if( height == 0 ) {
|
} else if( height == 0 ) {
|
||||||
height = (width / image.getWidth(null)) * image.getHeight(null);
|
height = (width / imgWidth) * imgHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
Point2D.Double anchorPoint = getOriginPoint(x, y, width, height, anchor);
|
Point2D.Double anchorPoint = getOriginPoint(x, y, width, height, anchor);
|
||||||
|
drawing.rotate(Math.toRadians(angle), anchorPoint.x + width / 2, anchorPoint.y + height / 2);
|
||||||
drawing.drawImage(image, (int) anchorPoint.x, (int) anchorPoint.y, (int) width, (int) height, null);
|
drawing.drawImage(image, (int) anchorPoint.x, (int) anchorPoint.y, (int) width, (int) height, null);
|
||||||
|
|
||||||
|
drawing.setTransform(orig);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package schule.ngb.zm.layers;
|
package schule.ngb.zm.layers;
|
||||||
|
|
||||||
import schule.ngb.zm.Layer;
|
import schule.ngb.zm.Layer;
|
||||||
|
import schule.ngb.zm.Updatable;
|
||||||
import schule.ngb.zm.anim.Animation;
|
import schule.ngb.zm.anim.Animation;
|
||||||
import schule.ngb.zm.anim.AnimationFacade;
|
import schule.ngb.zm.anim.AnimationFacade;
|
||||||
import schule.ngb.zm.anim.Easing;
|
import schule.ngb.zm.anim.Easing;
|
||||||
@@ -24,20 +25,26 @@ public class ShapesLayer extends Layer {
|
|||||||
*/
|
*/
|
||||||
protected boolean clearBeforeDraw = true;
|
protected boolean clearBeforeDraw = true;
|
||||||
|
|
||||||
private final List<Shape> shapes;
|
protected boolean updateShapes = true;
|
||||||
|
|
||||||
|
protected final List<Shape> shapes;
|
||||||
|
|
||||||
private final List<Animation<? extends Shape>> animations;
|
private final List<Animation<? extends Shape>> animations;
|
||||||
|
|
||||||
|
private final List<Updatable> updatables;
|
||||||
|
|
||||||
public ShapesLayer() {
|
public ShapesLayer() {
|
||||||
super();
|
super();
|
||||||
shapes = new LinkedList<>();
|
shapes = new LinkedList<>();
|
||||||
animations = new LinkedList<>();
|
animations = new LinkedList<>();
|
||||||
|
updatables = new LinkedList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ShapesLayer( int width, int height ) {
|
public ShapesLayer( int width, int height ) {
|
||||||
super(width, height);
|
super(width, height);
|
||||||
shapes = new LinkedList<>();
|
shapes = new LinkedList<>();
|
||||||
animations = new LinkedList<>();
|
animations = new LinkedList<>();
|
||||||
|
updatables = new LinkedList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Shape getShape( int index ) {
|
public Shape getShape( int index ) {
|
||||||
@@ -70,12 +77,24 @@ public class ShapesLayer extends Layer {
|
|||||||
public void add( Shape... shapes ) {
|
public void add( Shape... shapes ) {
|
||||||
synchronized( this.shapes ) {
|
synchronized( this.shapes ) {
|
||||||
Collections.addAll(this.shapes, shapes);
|
Collections.addAll(this.shapes, shapes);
|
||||||
|
|
||||||
|
for( Shape s : shapes ) {
|
||||||
|
if( Updatable.class.isInstance(s) ) {
|
||||||
|
updatables.add((Updatable) s);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void add( Collection<Shape> shapes ) {
|
public void add( Collection<Shape> shapes ) {
|
||||||
synchronized( this.shapes ) {
|
synchronized( this.shapes ) {
|
||||||
this.shapes.addAll(shapes);
|
this.shapes.addAll(shapes);
|
||||||
|
|
||||||
|
for( Shape s : shapes ) {
|
||||||
|
if( Updatable.class.isInstance(s) ) {
|
||||||
|
updatables.add((Updatable) s);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,6 +158,27 @@ public class ShapesLayer extends Layer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void update( double delta ) {
|
public void update( double delta ) {
|
||||||
|
if( updateShapes ) {
|
||||||
|
synchronized( shapes ) {
|
||||||
|
List<Updatable> uit = List.copyOf(updatables);
|
||||||
|
for( Updatable u : uit ) {
|
||||||
|
if( u.isActive() ) {
|
||||||
|
u.update(delta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Iterator<Updatable> uit = updatables.iterator();
|
||||||
|
while( uit.hasNext() ) {
|
||||||
|
Updatable u = uit.next();
|
||||||
|
if( u.isActive() ) {
|
||||||
|
u.update(delta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
Iterator<Animation<? extends Shape>> it = animations.iterator();
|
Iterator<Animation<? extends Shape>> it = animations.iterator();
|
||||||
while( it.hasNext() ) {
|
while( it.hasNext() ) {
|
||||||
Animation<? extends Shape> anim = it.next();
|
Animation<? extends Shape> anim = it.next();
|
||||||
|
|||||||
@@ -230,6 +230,11 @@ public class TurtleLayer extends Layer implements Strokeable, Fillable {
|
|||||||
return mainTurtle.getStrokeType();
|
return mainTurtle.getStrokeType();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Options.StrokeJoin getStrokeJoin() {
|
||||||
|
return mainTurtle.getStrokeJoin();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setStrokeType( Options.StrokeType type ) {
|
public void setStrokeType( Options.StrokeType type ) {
|
||||||
mainTurtle.setStrokeType(type);
|
mainTurtle.setStrokeType(type);
|
||||||
|
|||||||
8
src/main/java/schule/ngb/zm/layers/package-info.java
Normal file
8
src/main/java/schule/ngb/zm/layers/package-info.java
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* Dieses Paket enthält implementationen der abstrakten
|
||||||
|
* {@link schule.ngb.zm.Layer} Klasse.
|
||||||
|
* <p>
|
||||||
|
* {@code Layer} sind Ebenen, die der {@link schule.ngb.zm.Zeichenleinwand}
|
||||||
|
* hinzugefügt und pro Frame gerendert werden.
|
||||||
|
*/
|
||||||
|
package schule.ngb.zm.layers;
|
||||||
@@ -84,7 +84,7 @@ public class Music implements Audio {
|
|||||||
* @see ResourceStreamProvider#getResourceURL(String)
|
* @see ResourceStreamProvider#getResourceURL(String)
|
||||||
*/
|
*/
|
||||||
public Music( String audioSource ) {
|
public Music( String audioSource ) {
|
||||||
Validator.requireNotNull(audioSource);
|
Validator.requireNotNull(audioSource, "audioSource");
|
||||||
this.audioSource = audioSource;
|
this.audioSource = audioSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ public class Sound implements Audio {
|
|||||||
* @see ResourceStreamProvider#getResourceURL(String)
|
* @see ResourceStreamProvider#getResourceURL(String)
|
||||||
*/
|
*/
|
||||||
public Sound( String source ) {
|
public Sound( String source ) {
|
||||||
Validator.requireNotNull(source);
|
Validator.requireNotNull(source, "source");
|
||||||
this.audioSource = source;
|
this.audioSource = source;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,8 +235,8 @@ public class Sound implements Audio {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lädt falls nötig den {@link Clip} für die
|
* Lädt, falls nötig, den {@link Clip} für die
|
||||||
* {@link #audioSource Audioquelle} und startet die Wiedergabe.
|
* {@link #audioSource Audioquelle}.
|
||||||
*
|
*
|
||||||
* @return {@code true}, wenn der Clip geöffnet werden konnte, {@code false}
|
* @return {@code true}, wenn der Clip geöffnet werden konnte, {@code false}
|
||||||
* sonst.
|
* sonst.
|
||||||
@@ -264,10 +264,6 @@ public class Sound implements Audio {
|
|||||||
}
|
}
|
||||||
} else if( event.getType() == LineEvent.Type.STOP ) {
|
} else if( event.getType() == LineEvent.Type.STOP ) {
|
||||||
playbackStopped();
|
playbackStopped();
|
||||||
|
|
||||||
if( eventDispatcher != null ) {
|
|
||||||
eventDispatcher.dispatchEvent("stop", Sound.this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -313,6 +309,10 @@ public class Sound implements Audio {
|
|||||||
private void playbackStopped() {
|
private void playbackStopped() {
|
||||||
playing = false;
|
playing = false;
|
||||||
|
|
||||||
|
if( eventDispatcher != null ) {
|
||||||
|
eventDispatcher.dispatchEvent("stop", Sound.this);
|
||||||
|
}
|
||||||
|
|
||||||
if( disposeAfterPlay ) {
|
if( disposeAfterPlay ) {
|
||||||
this.dispose();
|
this.dispose();
|
||||||
disposeAfterPlay = false;
|
disposeAfterPlay = false;
|
||||||
|
|||||||
7
src/main/java/schule/ngb/zm/media/package-info.java
Normal file
7
src/main/java/schule/ngb/zm/media/package-info.java
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* Dieses Paket enthält Klassen zur Einbettung von Mediendateien.
|
||||||
|
* <p>
|
||||||
|
* Mit Medien sind vor allem Audio und Videodateien gemeint. Aktuell kann die
|
||||||
|
* Zeichenmaschine Audiodateien verwenden.
|
||||||
|
*/
|
||||||
|
package schule.ngb.zm.media;
|
||||||
16
src/main/java/schule/ngb/zm/ml/package-info.java
Normal file
16
src/main/java/schule/ngb/zm/ml/package-info.java
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Dieses Paekt enthält Klassen für Experimente mit Verfahren des maschinellen
|
||||||
|
* Lernens (ML).
|
||||||
|
* <p>
|
||||||
|
* Die hier implementierten Klassen sind eine prototypische Umsetzung von
|
||||||
|
* einfachen neuronalen Netzwerken, mit denen an kleinen Problemstellungen
|
||||||
|
* experimentell Modelle trainiert und angewandt werden können.
|
||||||
|
* <p>
|
||||||
|
* Die Implementierungen sind nicht optimiert und setzen auf native
|
||||||
|
* Java-Methoden. Daher sind sie nur für die Anwendung auf extrem kleine Modelle
|
||||||
|
* in Bildungskontexten gedacht.
|
||||||
|
* <p>
|
||||||
|
* Durch Einbettung wissenschaftlicher Bibliotheken mit optimierten Operationen
|
||||||
|
* lassen sich bessere Ergebnisse erreichen.
|
||||||
|
*/
|
||||||
|
package schule.ngb.zm.ml;
|
||||||
13
src/main/java/schule/ngb/zm/package-info.java
Normal file
13
src/main/java/schule/ngb/zm/package-info.java
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* <h2>Die Zeichenmaschine</h2>
|
||||||
|
* <p>
|
||||||
|
* Die <b>Zeichenmaschine</b> ist eine für den Informatikunterricht entwickelte
|
||||||
|
* Bibliothek, die unter anderem an <a
|
||||||
|
* href="https://processing.org/">Processing</a> angelehnt ist. Die Bibliothek
|
||||||
|
* soll einige der üblichen Anfängerschwierigkeiten mit Java vereinfachen und
|
||||||
|
* für Schülerinnen und Schüler im Unterricht nutzbar machen.
|
||||||
|
* <p>
|
||||||
|
* Eine umfassende Dokumentation ist unter <a
|
||||||
|
* href="https://zeichenmaschine.xyz">zeichenmaschine.xyz</a> verfügbar.
|
||||||
|
*/
|
||||||
|
package schule.ngb.zm;
|
||||||
77
src/main/java/schule/ngb/zm/particles/BasicParticle.java
Normal file
77
src/main/java/schule/ngb/zm/particles/BasicParticle.java
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
package schule.ngb.zm.particles;
|
||||||
|
|
||||||
|
import schule.ngb.zm.Color;
|
||||||
|
import schule.ngb.zm.Vector;
|
||||||
|
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
|
||||||
|
public class BasicParticle extends Particle {
|
||||||
|
|
||||||
|
protected Color color, startColor, finalColor;
|
||||||
|
|
||||||
|
|
||||||
|
public BasicParticle() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BasicParticle( Color startColor ) {
|
||||||
|
this(startColor, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BasicParticle( Color startColor, Color finalColor ) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.color = startColor;
|
||||||
|
this.startColor = startColor;
|
||||||
|
this.finalColor = finalColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Color getColor() {
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColor( Color pColor ) {
|
||||||
|
this.color = pColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Color getStartColor() {
|
||||||
|
return startColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStartColor( Color pStartColor ) {
|
||||||
|
this.startColor = pStartColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Color getFinalColor() {
|
||||||
|
return finalColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFinalColor( Color pFinalColor ) {
|
||||||
|
this.finalColor = pFinalColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void spawn( int pLifetime, Vector pPosition, Vector pVelocity ) {
|
||||||
|
super.spawn(pLifetime, pPosition, pVelocity);
|
||||||
|
this.color = this.startColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update( double delta ) {
|
||||||
|
super.update(delta);
|
||||||
|
|
||||||
|
if( isActive() && startColor != null && finalColor != null ) {
|
||||||
|
double t = 1.0 - lifetime / maxLifetime;
|
||||||
|
this.color = Color.interpolate(startColor, finalColor, t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw( Graphics2D graphics ) {
|
||||||
|
if( isActive() && this.color != null ) {
|
||||||
|
graphics.setColor(this.color.getJavaColor());
|
||||||
|
graphics.fillOval(position.getIntX() - 3, position.getIntY() - 3, 6, 6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package schule.ngb.zm.particles;
|
||||||
|
|
||||||
|
import schule.ngb.zm.Color;
|
||||||
|
|
||||||
|
public class BasicParticleFactory implements ParticleFactory {
|
||||||
|
|
||||||
|
private final Color startColor;
|
||||||
|
|
||||||
|
private final Color finalColor;
|
||||||
|
|
||||||
|
private boolean fadeOut = true;
|
||||||
|
|
||||||
|
public BasicParticleFactory() {
|
||||||
|
this(null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BasicParticleFactory( Color startColor ) {
|
||||||
|
this(startColor, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BasicParticleFactory( Color startColor, Color finalColor ) {
|
||||||
|
this.startColor = startColor;
|
||||||
|
this.finalColor = finalColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFadeOut( boolean pFadeOut ) {
|
||||||
|
this.fadeOut = pFadeOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Particle createParticle() {
|
||||||
|
Color finalClr = finalColor;
|
||||||
|
if( fadeOut ) {
|
||||||
|
if( finalColor != null ) {
|
||||||
|
finalClr = new Color(finalColor, 0);
|
||||||
|
} else if( startColor != null ) {
|
||||||
|
finalClr = new Color(startColor, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new BasicParticle(startColor, finalClr);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package schule.ngb.zm.particles;
|
||||||
|
|
||||||
|
import schule.ngb.zm.util.Log;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
public class GenericParticleFactory<T extends Particle> implements ParticleFactory {
|
||||||
|
|
||||||
|
private final Class<T> type;
|
||||||
|
|
||||||
|
private final Supplier<T> supplier;
|
||||||
|
|
||||||
|
public GenericParticleFactory( Class<T> type, Object... params ) {
|
||||||
|
this.type = type;
|
||||||
|
|
||||||
|
// Create paramTypes array once
|
||||||
|
Class<?>[] paramTypes = new Class<?>[params.length];
|
||||||
|
for( int i = 0; i < params.length; i++ ) {
|
||||||
|
paramTypes[i] = params[i].getClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.supplier = () -> {
|
||||||
|
T p = null;
|
||||||
|
try {
|
||||||
|
p = GenericParticleFactory.this.type.getDeclaredConstructor(paramTypes).newInstance(params);
|
||||||
|
} catch( Exception ex ) {
|
||||||
|
LOG.error( ex,
|
||||||
|
"Unable to create new Particle of type %s",
|
||||||
|
GenericParticleFactory.this.type.getCanonicalName()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public GenericParticleFactory( Supplier<T> supplier ) {
|
||||||
|
this.supplier = supplier;
|
||||||
|
this.type = (Class<T>)supplier.get().getClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Particle createParticle() {
|
||||||
|
return this.supplier.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Log LOG = Log.getLogger(GenericParticleFactory.class);
|
||||||
|
|
||||||
|
}
|
||||||
60
src/main/java/schule/ngb/zm/particles/Particle.java
Normal file
60
src/main/java/schule/ngb/zm/particles/Particle.java
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package schule.ngb.zm.particles;
|
||||||
|
|
||||||
|
import schule.ngb.zm.Drawable;
|
||||||
|
import schule.ngb.zm.Updatable;
|
||||||
|
import schule.ngb.zm.Vector;
|
||||||
|
|
||||||
|
|
||||||
|
public abstract class Particle extends PhysicsObject implements Updatable, Drawable {
|
||||||
|
|
||||||
|
protected double maxLifetime = 0, lifetime = 0;
|
||||||
|
|
||||||
|
|
||||||
|
public Particle() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void spawn( int pLifetime, Vector pPosition, Vector pVelocity ) {
|
||||||
|
this.maxLifetime = pLifetime;
|
||||||
|
this.lifetime = pLifetime;
|
||||||
|
this.position = pPosition.copy();
|
||||||
|
this.velocity = pVelocity.copy();
|
||||||
|
this.acceleration = new Vector();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isActive() {
|
||||||
|
return lifetime > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isVisible() {
|
||||||
|
return isActive();
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getLifetime() {
|
||||||
|
return lifetime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLifetime( double pLifetime ) {
|
||||||
|
this.lifetime = pLifetime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getMaxLifetime() {
|
||||||
|
return maxLifetime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaxLifetime( double pMaxLifetime ) {
|
||||||
|
this.maxLifetime = pMaxLifetime;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update( double delta ) {
|
||||||
|
super.update(delta);
|
||||||
|
// lifetime -= delta;
|
||||||
|
lifetime -= 1;
|
||||||
|
|
||||||
|
// TODO: (ngb) calculate delta based on lifetime?
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
174
src/main/java/schule/ngb/zm/particles/ParticleEmitter.java
Normal file
174
src/main/java/schule/ngb/zm/particles/ParticleEmitter.java
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
package schule.ngb.zm.particles;
|
||||||
|
|
||||||
|
|
||||||
|
import schule.ngb.zm.Drawable;
|
||||||
|
import schule.ngb.zm.Updatable;
|
||||||
|
import schule.ngb.zm.Vector;
|
||||||
|
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
|
||||||
|
public class ParticleEmitter implements Updatable, Drawable {
|
||||||
|
|
||||||
|
protected ParticleFactory particleFactory;
|
||||||
|
|
||||||
|
private int particlesPerFrame;
|
||||||
|
|
||||||
|
private int particleLifetime = 180;
|
||||||
|
|
||||||
|
private Particle[] particles;
|
||||||
|
|
||||||
|
private boolean active = false;
|
||||||
|
|
||||||
|
private Particle nextParticle;
|
||||||
|
|
||||||
|
public Vector position;
|
||||||
|
|
||||||
|
public Vector direction = new Vector();
|
||||||
|
|
||||||
|
public double strength = 100.0;
|
||||||
|
|
||||||
|
public int angle = 0;
|
||||||
|
|
||||||
|
public double randomness = 0.0;
|
||||||
|
|
||||||
|
// private Vortex vortex = null;
|
||||||
|
|
||||||
|
public ParticleEmitter( double pX, double pY, int pParticleLifetime, int pParticlesPerFrame, ParticleFactory pFactory ) {
|
||||||
|
this.position = new Vector(pX, pY);
|
||||||
|
this.particlesPerFrame = pParticlesPerFrame;
|
||||||
|
this.particleLifetime = pParticleLifetime;
|
||||||
|
this.particleFactory = pFactory;
|
||||||
|
|
||||||
|
// Create particle pool
|
||||||
|
this.particles = new Particle[particlesPerFrame * pParticleLifetime];
|
||||||
|
this.direction = Vector.random(8, 16).normalize();
|
||||||
|
|
||||||
|
// vortex = new Vortex(position.copy().add(-10, -10), -.2, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isActive() {
|
||||||
|
return active;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isVisible() {
|
||||||
|
return active;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start() {
|
||||||
|
this.direction.normalize();
|
||||||
|
|
||||||
|
// Partikel initialisieren
|
||||||
|
for( int i = 0; i < particles.length; i++ ) {
|
||||||
|
particles[i] = particleFactory.createParticle();
|
||||||
|
}
|
||||||
|
|
||||||
|
active = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
for( int i = 0; i < particles.length; i++ ) {
|
||||||
|
particles[i].setLifetime(0);
|
||||||
|
particles[i] = null;
|
||||||
|
}
|
||||||
|
active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Particle getNextParticle() {
|
||||||
|
// TODO: improve by caching next particle
|
||||||
|
for( Particle p : particles ) {
|
||||||
|
if( p != null && !p.isActive() ) {
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void emitParticle() {
|
||||||
|
int ppf = particlesPerFrame;
|
||||||
|
Particle nextParticle = getNextParticle();
|
||||||
|
while( ppf > 0 && nextParticle != null ) {
|
||||||
|
int lifetime = (int) random(particleLifetime);
|
||||||
|
|
||||||
|
double rotation = (angle / 2.0) - (int) (Math.random() * angle);
|
||||||
|
Vector velocity = direction.copy().scale(strength).rotate(rotation);
|
||||||
|
velocity.scale(random());
|
||||||
|
|
||||||
|
nextParticle.spawn(lifetime, this.position, velocity);
|
||||||
|
nextParticle = getNextParticle();
|
||||||
|
ppf -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update( double delta ) {
|
||||||
|
emitParticle();
|
||||||
|
|
||||||
|
boolean _active = false;
|
||||||
|
for( Particle particle : particles ) {
|
||||||
|
if( particle != null ) {
|
||||||
|
if( particle.isActive() ) {
|
||||||
|
// if( vortex != null ) {
|
||||||
|
// vortex.attract(particle);
|
||||||
|
// }
|
||||||
|
particle.update(delta);
|
||||||
|
_active = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.active = _active;
|
||||||
|
}
|
||||||
|
|
||||||
|
private double random() {
|
||||||
|
return 1.0 - (Math.random() * randomness);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double random( double pZahl ) {
|
||||||
|
return pZahl * random();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw( Graphics2D graphics ) {
|
||||||
|
java.awt.Color current = graphics.getColor();
|
||||||
|
for( Particle particle : particles ) {
|
||||||
|
if( particle != null && particle.isVisible() ) {
|
||||||
|
particle.draw(graphics);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if( vortex != null ) {
|
||||||
|
// graphics.setColor(java.awt.Color.BLACK);
|
||||||
|
// double vscale = (4 * vortex.scale);
|
||||||
|
// graphics.fillOval((int) (vortex.position.x - vscale * .5), (int) (vortex.position.y - vscale * .5), (int) vscale, (int) vscale);
|
||||||
|
// }
|
||||||
|
graphics.setColor(current);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Vortex {
|
||||||
|
|
||||||
|
Vector position;
|
||||||
|
|
||||||
|
double speed = 1.0, scale = 1.0;
|
||||||
|
|
||||||
|
public Vortex( Vector pPosition, double pSpeed, double pScale ) {
|
||||||
|
this.position = pPosition.copy();
|
||||||
|
this.scale = pScale;
|
||||||
|
this.speed = pSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void attract( Particle pPartikel ) {
|
||||||
|
Vector diff = Vector.sub(pPartikel.position, this.position);
|
||||||
|
double dx = -diff.y * this.speed;
|
||||||
|
double dy = diff.x * this.speed;
|
||||||
|
double f = 1.0 / (1.0 + (dx * dx + dy * dy) / scale);
|
||||||
|
|
||||||
|
pPartikel.position.x += (diff.x - pPartikel.velocity.x) * f;
|
||||||
|
pPartikel.position.y += (diff.y - pPartikel.velocity.y) * f;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package schule.ngb.zm.particles;
|
||||||
|
|
||||||
|
public interface ParticleFactory {
|
||||||
|
|
||||||
|
Particle createParticle();
|
||||||
|
|
||||||
|
}
|
||||||
69
src/main/java/schule/ngb/zm/particles/PhysicsObject.java
Normal file
69
src/main/java/schule/ngb/zm/particles/PhysicsObject.java
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
package schule.ngb.zm.particles;
|
||||||
|
|
||||||
|
import schule.ngb.zm.Updatable;
|
||||||
|
import schule.ngb.zm.Vector;
|
||||||
|
|
||||||
|
|
||||||
|
public abstract class PhysicsObject implements Updatable {
|
||||||
|
|
||||||
|
protected Vector position, velocity, acceleration;
|
||||||
|
|
||||||
|
protected double mass = 1.0;
|
||||||
|
|
||||||
|
|
||||||
|
public PhysicsObject() {
|
||||||
|
position = new Vector();
|
||||||
|
velocity = new Vector();
|
||||||
|
acceleration = new Vector();
|
||||||
|
}
|
||||||
|
|
||||||
|
public PhysicsObject( Vector pPosition ) {
|
||||||
|
position = pPosition.copy();
|
||||||
|
velocity = new Vector();
|
||||||
|
acceleration = new Vector();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector getAcceleration() {
|
||||||
|
return acceleration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAcceleration( Vector pAcceleration ) {
|
||||||
|
this.acceleration = pAcceleration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getMass() {
|
||||||
|
return mass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMass( double pMass ) {
|
||||||
|
this.mass = pMass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector getPosition() {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPosition( Vector pPosition ) {
|
||||||
|
this.position = pPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector getVelocity() {
|
||||||
|
return velocity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVelocity( Vector pVelocity ) {
|
||||||
|
this.velocity = pVelocity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void accelerate( Vector pAcceleration ) {
|
||||||
|
acceleration.add(Vector.div(pAcceleration, mass));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update( double delta ) {
|
||||||
|
velocity.add(acceleration);
|
||||||
|
position.add(Vector.scale(velocity, delta));
|
||||||
|
acceleration.scale(0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
35
src/main/java/schule/ngb/zm/particles/StarParticle.java
Normal file
35
src/main/java/schule/ngb/zm/particles/StarParticle.java
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package schule.ngb.zm.particles;
|
||||||
|
|
||||||
|
import schule.ngb.zm.Color;
|
||||||
|
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
|
||||||
|
public class StarParticle extends BasicParticle {
|
||||||
|
|
||||||
|
public StarParticle() {
|
||||||
|
super();
|
||||||
|
this.startColor = Color.PURE_GREEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StarParticle( Color startColor ) {
|
||||||
|
this(startColor, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public StarParticle( Color startColor, Color finalColor ) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.color = startColor;
|
||||||
|
this.startColor = startColor;
|
||||||
|
this.finalColor = finalColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw( Graphics2D graphics ) {
|
||||||
|
if( isActive() && this.color != null ) {
|
||||||
|
graphics.setColor(this.color.getJavaColor());
|
||||||
|
graphics.drawLine((int) position.x - 3, (int) position.y - 3, (int) position.x + 3, (int) position.y + 3);
|
||||||
|
graphics.drawLine((int) position.x + 3, (int) position.y - 3, (int) position.x - 3, (int) position.y + 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
package schule.ngb.zm.shapes;
|
package schule.ngb.zm.shapes;
|
||||||
|
|
||||||
|
import schule.ngb.zm.Options;
|
||||||
|
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.geom.AffineTransform;
|
||||||
import java.awt.geom.CubicCurve2D;
|
import java.awt.geom.CubicCurve2D;
|
||||||
import java.awt.geom.Point2D;
|
import java.awt.geom.Point2D;
|
||||||
import java.awt.geom.QuadCurve2D;
|
import java.awt.geom.QuadCurve2D;
|
||||||
@@ -170,6 +174,30 @@ public class Curve extends Shape {
|
|||||||
move(dx, dy);
|
move(dx, dy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw( Graphics2D graphics, AffineTransform transform ) {
|
||||||
|
if( !visible ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AffineTransform orig = graphics.getTransform();
|
||||||
|
if( transform != null ) {
|
||||||
|
//graphics.transform(transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
graphics.translate(x, y);
|
||||||
|
graphics.rotate(Math.toRadians(rotation));
|
||||||
|
|
||||||
|
java.awt.Shape shape = getShape();
|
||||||
|
|
||||||
|
java.awt.Color currentColor = graphics.getColor();
|
||||||
|
fillShape(shape, graphics);
|
||||||
|
strokeShape(shape, graphics);
|
||||||
|
graphics.setColor(currentColor);
|
||||||
|
|
||||||
|
graphics.setTransform(orig);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals( Object o ) {
|
public boolean equals( Object o ) {
|
||||||
if( this == o ) return true;
|
if( this == o ) return true;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ public class CustomShape extends Shape {
|
|||||||
public CustomShape( double x, double y ) {
|
public CustomShape( double x, double y ) {
|
||||||
super(x, y);
|
super(x, y);
|
||||||
path = new Path2D.Double();
|
path = new Path2D.Double();
|
||||||
|
path.moveTo(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CustomShape( CustomShape custom ) {
|
public CustomShape( CustomShape custom ) {
|
||||||
@@ -36,7 +37,7 @@ public class CustomShape extends Shape {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void lineTo( double x, double y ) {
|
public void lineTo( double x, double y ) {
|
||||||
path.lineTo(x - x, y - y);
|
path.lineTo(x - this.x, y - this.y);
|
||||||
calculateBounds();
|
calculateBounds();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,13 +4,14 @@ import schule.ngb.zm.BasicDrawable;
|
|||||||
import schule.ngb.zm.Fillable;
|
import schule.ngb.zm.Fillable;
|
||||||
import schule.ngb.zm.Options;
|
import schule.ngb.zm.Options;
|
||||||
import schule.ngb.zm.Strokeable;
|
import schule.ngb.zm.Strokeable;
|
||||||
|
import schule.ngb.zm.util.Validator;
|
||||||
|
|
||||||
import java.awt.Graphics2D;
|
import java.awt.Graphics2D;
|
||||||
import java.awt.geom.AffineTransform;
|
import java.awt.geom.AffineTransform;
|
||||||
import java.awt.geom.Point2D;
|
import java.awt.geom.Point2D;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Basisklasse für alle Formen in der Zeichenmaschine.
|
* Dies ist die Basisklasse für alle Formen in der Zeichenmaschine.
|
||||||
* <p>
|
* <p>
|
||||||
* Alle Formen sind als Unterklassen von {@code Shape} implementiert.
|
* Alle Formen sind als Unterklassen von {@code Shape} implementiert.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -19,7 +20,6 @@ import java.awt.geom.Point2D;
|
|||||||
* Parametern initialisiert und einen, der die Werte einer anderen Form
|
* Parametern initialisiert und einen, der die Werte einer anderen Form
|
||||||
* desselben Typs übernimmt. In der Klasse {@link Circle} sind die Konstruktoren
|
* desselben Typs übernimmt. In der Klasse {@link Circle} sind die Konstruktoren
|
||||||
* beispielsweise so implementiert:
|
* beispielsweise so implementiert:
|
||||||
*
|
|
||||||
* <pre><code>
|
* <pre><code>
|
||||||
* public Circle( double x, double y, double radius ) {
|
* public Circle( double x, double y, double radius ) {
|
||||||
* super(x, y);
|
* super(x, y);
|
||||||
@@ -39,42 +39,42 @@ import java.awt.geom.Point2D;
|
|||||||
public abstract class Shape extends BasicDrawable {
|
public abstract class Shape extends BasicDrawable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* x-Koordinate der Form.
|
* Speichert die x-Koordinate der Form.
|
||||||
*/
|
*/
|
||||||
protected double x;
|
protected double x;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* y-Koordinate der Form.
|
* Speichert die y-Koordinate der Form.
|
||||||
*/
|
*/
|
||||||
protected double y;
|
protected double y;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rotation in Grad um den Punkt (x, y).
|
* Speichert die Rotation in Grad um den Punkt (x, y).
|
||||||
*/
|
*/
|
||||||
protected double rotation = 0.0;
|
protected double rotation = 0.0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Skalierungsfaktor.
|
* Speichert den Skalierungsfaktor.
|
||||||
*/
|
*/
|
||||||
protected double scale = 1.0;
|
protected double scale = 1.0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ankerpunkt der Form.
|
* Speichert den Ankerpunkt.
|
||||||
*/
|
*/
|
||||||
protected Options.Direction anchor = Options.Direction.CENTER;
|
protected Options.Direction anchor = Options.Direction.CENTER;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setzt die x- und y-Koordinate der Form auf 0.
|
* Erstellt eine neue Form mit den Koordinaten {@code (0,0)}.
|
||||||
*/
|
*/
|
||||||
public Shape() {
|
public Shape() {
|
||||||
this(0.0, 0.0);
|
this(0.0, 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setzt die x- und y-Koordinate der Form.
|
* Erstellt eine Form mit den angegebenen Koordinaten.
|
||||||
*
|
*
|
||||||
* @param x
|
* @param x Die x-Koordinate.
|
||||||
* @param y
|
* @param y Die y-Koordinate.
|
||||||
*/
|
*/
|
||||||
public Shape( double x, double y ) {
|
public Shape( double x, double y ) {
|
||||||
this.x = x;
|
this.x = x;
|
||||||
@@ -82,7 +82,7 @@ public abstract class Shape extends BasicDrawable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gibt die x-Koordinate der Form zurück.
|
* Liefert die aktuelle x-Koordinate der Form.
|
||||||
*
|
*
|
||||||
* @return Die x-Koordinate.
|
* @return Die x-Koordinate.
|
||||||
*/
|
*/
|
||||||
@@ -100,7 +100,7 @@ public abstract class Shape extends BasicDrawable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gibt die y-Koordinate der Form zurück.
|
* Liefert die aktuelle y-Koordinate der Form.
|
||||||
*
|
*
|
||||||
* @return Die y-Koordinate.
|
* @return Die y-Koordinate.
|
||||||
*/
|
*/
|
||||||
@@ -109,57 +109,44 @@ public abstract class Shape extends BasicDrawable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setzt die y-Koordinate der Form.
|
* Setzt die x-Koordinate der Form.
|
||||||
*
|
*
|
||||||
* @param y Die neue y-Koordinate.
|
* @param y Die neue y-Koordinate der Form.
|
||||||
*/
|
*/
|
||||||
public void setY( double y ) {
|
public void setY( double y ) {
|
||||||
this.y = y;
|
this.y = y;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gibt die Breite dieser Form zurück.
|
* Liefert die aktuelle Breite dieser Form.
|
||||||
* <p>
|
* <p>
|
||||||
* Die Breite einer Form ist immer die Breite ihrer Begrenzung,
|
* Die Breite einer Form ist immer die Breite ihrer Begrenzung,
|
||||||
* <strong>bevor</strong> Drehungen und andere Transformationen auf sie
|
* <strong>bevor</strong> Drehungen und andere Transformationen auf sie
|
||||||
* angewandt wurden.
|
* angewandt wurden.
|
||||||
* <p>
|
* <p>
|
||||||
* Die Begrenzungen der tatsächlich gezeichneten Form kann mit
|
* Die Begrenzungen der tatsächlich gezeichneten Form wird mit
|
||||||
* {@link #getBounds()} abgerufen werden.
|
* {@link #getBounds()} abgerufen.
|
||||||
*
|
*
|
||||||
* @return
|
* @return Die Breite der Form.
|
||||||
*/
|
*/
|
||||||
public abstract double getWidth();
|
public abstract double getWidth();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gibt die Höhe dieser Form zurück.
|
* Liefert die aktuelle Höhe dieser Form.
|
||||||
* <p>
|
* <p>
|
||||||
* Die Höhe einer Form ist immer die Höhe ihrer Begrenzung,
|
* Die Höhe einer Form ist immer die Höhe ihrer Begrenzung,
|
||||||
* <strong>bevor</strong> Drehungen und andere Transformationen auf sie
|
* <strong>bevor</strong> Drehungen und andere Transformationen auf sie
|
||||||
* angewandt wurden.
|
* angewandt wurden.
|
||||||
* <p>
|
* <p>
|
||||||
* Die Begrenzungen der tatsächlich gezeichneten Form kann mit
|
* Die Begrenzungen der tatsächlich gezeichneten Form wird mit
|
||||||
* {@link #getBounds()} abgerufen werden.
|
* {@link #getBounds()} abgerufen.
|
||||||
*
|
*
|
||||||
* @return
|
* @return Die Höhe der Form.
|
||||||
*/
|
*/
|
||||||
public abstract double getHeight();
|
public abstract double getHeight();
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setGradient( schule.ngb.zm.Color from, schule.ngb.zm.Color to, Options.Direction dir ) {
|
|
||||||
Point2D apDir = getAbsAnchorPoint(dir);
|
|
||||||
Point2D apInv = getAbsAnchorPoint(dir.inverse());
|
|
||||||
setGradient(apInv.getX(), apInv.getY(), from, apDir.getX(), apDir.getY(), to);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setGradient( schule.ngb.zm.Color from, schule.ngb.zm.Color to ) {
|
|
||||||
Point2D ap = getAbsAnchorPoint(CENTER);
|
|
||||||
setGradient(ap.getX(), ap.getY(), Math.min(ap.getX(), ap.getY()), from, to);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gibt die Rotation in Grad zurück.
|
* Liefert die Rotation in Grad.
|
||||||
*
|
*
|
||||||
* @return Rotation in Grad.
|
* @return Rotation in Grad.
|
||||||
*/
|
*/
|
||||||
@@ -176,14 +163,25 @@ public abstract class Shape extends BasicDrawable {
|
|||||||
this.rotation += angle % 360;
|
this.rotation += angle % 360;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void rotateTo( double angle ) {
|
/**
|
||||||
this.rotation = angle % 360;
|
* Dreht die Form um den angegebenen Winkel um das angegebene Drehzentrum.
|
||||||
}
|
*
|
||||||
|
* @param center Das Drehzentrum der Drehung.
|
||||||
|
* @param angle Der Drehwinkel.
|
||||||
|
*/
|
||||||
public void rotate( Point2D center, double angle ) {
|
public void rotate( Point2D center, double angle ) {
|
||||||
|
Validator.requireNotNull(center, "center");
|
||||||
rotate(center.getX(), center.getY(), angle);
|
rotate(center.getX(), center.getY(), angle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dreht die Form um den angegebenen Drehwinkel um die angegbenen
|
||||||
|
* Koordinaten als Drehzentrum.
|
||||||
|
*
|
||||||
|
* @param x x-Koordiante des Drehzentrums.
|
||||||
|
* @param y y-Koordiante des Drehzentrums.
|
||||||
|
* @param angle Drehwinkel in Grad.
|
||||||
|
*/
|
||||||
public void rotate( double x, double y, double angle ) {
|
public void rotate( double x, double y, double angle ) {
|
||||||
this.rotation += angle % 360;
|
this.rotation += angle % 360;
|
||||||
|
|
||||||
@@ -198,6 +196,15 @@ public abstract class Shape extends BasicDrawable {
|
|||||||
this.y = y2 + y;
|
this.y = y2 + y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt die Drehung der Form auf den angegebenen Winkel.
|
||||||
|
*
|
||||||
|
* @param angle Drehwinkel in Grad.
|
||||||
|
*/
|
||||||
|
public void rotateTo( double angle ) {
|
||||||
|
this.rotation = angle % 360;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gibt den aktuellen Skalierungsfaktor zurück.
|
* Gibt den aktuellen Skalierungsfaktor zurück.
|
||||||
*
|
*
|
||||||
@@ -207,34 +214,92 @@ public abstract class Shape extends BasicDrawable {
|
|||||||
return scale;
|
return scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt den Skalierungsfaktor auf den angegebenen Faktor.
|
||||||
|
* <p>
|
||||||
|
* Bei einem Faktor größer 0 wird die Form vergrößert, bei einem Faktor
|
||||||
|
* kleiner 0 verkleinert. Bei negativen Werten wird die Form entlang der x-
|
||||||
|
* bzw. y-Achse gespiegelt.
|
||||||
|
* <p>
|
||||||
|
* Das Seitenverhältnis wird immer beibehalten.
|
||||||
|
*
|
||||||
|
* @param factor Der Skalierungsfaktor.
|
||||||
|
*/
|
||||||
public void scale( double factor ) {
|
public void scale( double factor ) {
|
||||||
scale = factor;
|
scale = factor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skaliert die Form um den angegebenen Faktor.
|
||||||
|
* <p>
|
||||||
|
* Bei einem Faktor größer 0 wird die Form vergrößert, bei einem Faktor
|
||||||
|
* kleiner 0 verkleinert. Bei negativen Werten wird die Form entlang der x-
|
||||||
|
* bzw. y-Achse gespiegelt.
|
||||||
|
* <p>
|
||||||
|
* Die Skalierung wird zusätzlich zur aktuellen Skalierung angewandt. Wurde
|
||||||
|
* die Form zuvor um den Faktor 0.5 verkleinert und wird dann um 1.5
|
||||||
|
* vergrößert, dann ist die Form im Anschluss ein Drittel kleiner als zu
|
||||||
|
* Beginn ({@code 0.5 * 1.5 = 0.75}).
|
||||||
|
*
|
||||||
|
* @param factor Der Skalierungsfaktor.
|
||||||
|
*/
|
||||||
public void scaleBy( double factor ) {
|
public void scaleBy( double factor ) {
|
||||||
scale(scale * factor);
|
scale(scale * factor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Liefert den aktuellen Ankerpunkt der Form.
|
||||||
|
*
|
||||||
|
* @return Der Ankerpunkt.
|
||||||
|
*/
|
||||||
public Options.Direction getAnchor() {
|
public Options.Direction getAnchor() {
|
||||||
return anchor;
|
return anchor;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setzt den Ankerpunkt der Form basierend auf der angegebenen
|
* Setzt den Ankerpunkt der Form auf die angegebene Richtung.
|
||||||
* {@link Options.Direction Richtung}.
|
|
||||||
* <p>
|
* <p>
|
||||||
* Für das Setzen des Ankers muss das
|
* Jede Form hat einen Ankerpunkt, von dem aus sie gezeichnet wird. Jede
|
||||||
* {@link #getBounds() begrenzende Rechteck} berechnet werden. Unterklassen
|
* {@link schule.ngb.zm.Options.Direction Richtung} beschreibt einen der
|
||||||
* sollten die Methode überschreiben, wenn der Anker auch direkt gesetzt
|
* Neun Ankerpunkte:
|
||||||
* werden kann.
|
* <pre>
|
||||||
|
* NW────N────NE
|
||||||
|
* │ │
|
||||||
|
* │ │
|
||||||
|
* W C E
|
||||||
|
* │ │
|
||||||
|
* │ │
|
||||||
|
* SW────S────SE
|
||||||
|
* </pre>
|
||||||
|
* <p>
|
||||||
|
* Für den Ankerpunkt {@link #CENTER} wird die Form also ausgehend von den
|
||||||
|
* Koordinaten {@link #x} und {@link #y} um die Hälfte der Breite nach links
|
||||||
|
* und rechts, sowie um die Hälfte der Höhe nach oben und unten gezeichnet.
|
||||||
|
* Fpr den Ankerpunkt {@link #NORTHWEST} dagegen um die gesamte Breite nach
|
||||||
|
* rechts und die Höhe nach unten.
|
||||||
|
* <pre>
|
||||||
|
* setAnchor(CENTER) │ setAnchor(NORTHWEST)
|
||||||
|
* ┌───────────┐ │
|
||||||
|
* │ │ │
|
||||||
|
* │ │ │
|
||||||
|
* │ (x,y) │ │ (x,y)─────────┐
|
||||||
|
* │ │ │ │ │
|
||||||
|
* │ │ │ │ │
|
||||||
|
* └───────────┘ │ │ │
|
||||||
|
* │ │ │
|
||||||
|
* │ │ │
|
||||||
|
* │ └───────────┘
|
||||||
|
* </pre>
|
||||||
|
* <p>
|
||||||
|
* Der Ankerpunkt der Form bestimmt bei Transformationen auch die Position
|
||||||
|
* des Drehzentrums und anderer relativer Koordinaten bezüglich der Form.
|
||||||
*
|
*
|
||||||
* @param anchor
|
* @param anchor Der Ankerpunkt.
|
||||||
*/
|
*/
|
||||||
public void setAnchor( Options.Direction anchor ) {
|
public void setAnchor( Options.Direction anchor ) {
|
||||||
if( anchor != null ) {
|
Validator.requireNotNull(anchor, "anchor");
|
||||||
this.anchor = anchor;
|
this.anchor = anchor;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bestimmt die relativen Koordinaten des angegebenen Ankerpunkts basierend
|
* Bestimmt die relativen Koordinaten des angegebenen Ankerpunkts basierend
|
||||||
@@ -242,7 +307,19 @@ public abstract class Shape extends BasicDrawable {
|
|||||||
* <p>
|
* <p>
|
||||||
* Die Koordinaten des Ankerpunktes werden relativ zur oberen linken Ecke
|
* Die Koordinaten des Ankerpunktes werden relativ zur oberen linken Ecke
|
||||||
* des Rechtecks mit der Breite {@code width} und der Höhe {@code height}
|
* des Rechtecks mit der Breite {@code width} und der Höhe {@code height}
|
||||||
* bestimmt.
|
* bestimmt. Der Ankerpunkt {@link #NORTHWEST} hat daher immer das Ergebnis
|
||||||
|
* {@code (0,0)} und {@link #SOUTHEAST} {@code (width, height)}.
|
||||||
|
* <pre>
|
||||||
|
* (0,0)───(w/2,0)───(w,0)
|
||||||
|
* │ │
|
||||||
|
* │ │
|
||||||
|
* │ │
|
||||||
|
* (0,h/2) (w/2,h/2) (w,h/2)
|
||||||
|
* │ │
|
||||||
|
* │ │
|
||||||
|
* │ │
|
||||||
|
* (0,h)───(w/2,h)───(w,h)
|
||||||
|
* </pre>
|
||||||
*
|
*
|
||||||
* @param width Breite des umschließenden Rechtecks.
|
* @param width Breite des umschließenden Rechtecks.
|
||||||
* @param height Höhe des umschließenden Rechtecks.
|
* @param height Höhe des umschließenden Rechtecks.
|
||||||
@@ -259,11 +336,12 @@ public abstract class Shape extends BasicDrawable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bestimmt den Ankerpunkt der Form relativ zum gesetzten
|
* Bestimmt die Koordinaten des angegebenen Ankers der Form relativ zum
|
||||||
* {@link #setAnchor(Options.Direction) Ankerpunkt}.
|
* aktuellen {@link #setAnchor(Options.Direction) Ankerpunkt}.
|
||||||
*
|
*
|
||||||
* @param anchor Die Richtung des Ankerpunktes.
|
* @param anchor Die Richtung des Ankers.
|
||||||
* @return Der relative Ankerpunkt.
|
* @return Der relative Ankerpunkt.
|
||||||
|
* @see #getAnchorPoint(double, double, Options.Direction)
|
||||||
*/
|
*/
|
||||||
public Point2D.Double getAnchorPoint( Options.Direction anchor ) {
|
public Point2D.Double getAnchorPoint( Options.Direction anchor ) {
|
||||||
double wHalf = getWidth() * .5, hHalf = getHeight() * .5;
|
double wHalf = getWidth() * .5, hHalf = getHeight() * .5;
|
||||||
@@ -276,11 +354,20 @@ public abstract class Shape extends BasicDrawable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ermittelt die absoluten Koordinaten eines angegebenen
|
* Ermittelt die absoluten Koordinaten des angegebenen
|
||||||
* {@link #setAnchor(Options.Direction) Ankers}.
|
* {@link #setAnchor(Options.Direction) Ankers}.
|
||||||
|
* <p>
|
||||||
|
* Die absoluten Koordinaten werden bestimmt durch die Position der Form
|
||||||
|
* {@code (x,y)} plus die
|
||||||
|
* {@link #getAnchorPoint(Options.Direction) relativen Koordinaten} des
|
||||||
|
* Ankers.
|
||||||
*
|
*
|
||||||
* @param anchor Die Richtung des Ankerpunktes.
|
* <b>Wichtig:</b> Die Berechnung berücksichtigt derzeit keine Rotationen
|
||||||
|
* und Transformationen der Form.
|
||||||
|
*
|
||||||
|
* @param anchor Die Richtung des Ankers.
|
||||||
* @return Der absolute Ankerpunkt.
|
* @return Der absolute Ankerpunkt.
|
||||||
|
* @see #getAnchorPoint(double, double, Options.Direction)
|
||||||
*/
|
*/
|
||||||
public Point2D.Double getAbsAnchorPoint( Options.Direction anchor ) {
|
public Point2D.Double getAbsAnchorPoint( Options.Direction anchor ) {
|
||||||
// TODO: Die absoluten Anker müssten eigentlich die Rotation berücksichtigen.
|
// TODO: Die absoluten Anker müssten eigentlich die Rotation berücksichtigen.
|
||||||
@@ -293,20 +380,22 @@ public abstract class Shape extends BasicDrawable {
|
|||||||
/**
|
/**
|
||||||
* Kopiert die Eigenschaften der angegebenen Form in diese.
|
* Kopiert die Eigenschaften der angegebenen Form in diese.
|
||||||
* <p>
|
* <p>
|
||||||
* Unterklassen sollten diese Methode überschreiben, um weitere
|
* Unterklassen überschreiben diese Methode, um weitere Eigenschaften zu
|
||||||
* Eigenschaften zu kopieren (zum Beispiel den Radius eines Kreises).
|
* kopieren (zum Beispiel den Radius eines Kreises). Überschreibende
|
||||||
* Unterklassen sollten immer mit dem Aufruf {@code super.copyFrom(shape)}
|
* Methoden sollten immer mit dem Aufruf {@code super.copyFrom(shape)} die
|
||||||
* die Basiseigenschaften kopieren.
|
* Basiseigenschaften kopieren.
|
||||||
* <p>
|
* <p>
|
||||||
* Die Methode sollte so viele Eigenschaften wie möglich von der anderen
|
* Die Methode kopiert so viele Eigenschaften wie möglich von der
|
||||||
* Form in diese kopieren. Wenn die andere Form einen anderen Typ hat, dann
|
* angegebenen Form in diese. Wenn die andere Form einen anderen Typ hat,
|
||||||
* werden trotzdem die Basiseigenschaften (Konturlinie, Füllung, Position,
|
* dann werden trotzdem die Basiseigenschaften (Konturlinie, Füllung,
|
||||||
* Rotation, Skalierung, Sichtbarkeit und Ankerpunkt) in diese Form kopiert.
|
* Position, Rotation, Skalierung, Sichtbarkeit und Ankerpunkt) in diese
|
||||||
* Implementierende Unterklassen können soweit sinnvoll auch andere Werte
|
* Form kopiert. Soweit sinnvoll übernehmen implementierende Unterklassen
|
||||||
* übernehmen. Eine {@link Ellipse} kann beispielsweise auch die Breite und
|
* auch andere Werte. Eine {@link Ellipse} kopiert beispielsweise auch die
|
||||||
* Höhe eines {@link Rectangle} übernehmen.
|
* Breite und Höhe eines {@link Rectangle}.
|
||||||
|
* <p>
|
||||||
|
* Wird {@code null} übergeben, dann passiert nichts.
|
||||||
*
|
*
|
||||||
* @param shape Die Originalform, von der kopiert werden soll.
|
* @param shape Die Originalform, von der kopiert wird.
|
||||||
*/
|
*/
|
||||||
public void copyFrom( Shape shape ) {
|
public void copyFrom( Shape shape ) {
|
||||||
if( shape != null ) {
|
if( shape != null ) {
|
||||||
@@ -315,6 +404,7 @@ public abstract class Shape extends BasicDrawable {
|
|||||||
setStrokeColor(shape.getStrokeColor());
|
setStrokeColor(shape.getStrokeColor());
|
||||||
setStrokeWeight(shape.getStrokeWeight());
|
setStrokeWeight(shape.getStrokeWeight());
|
||||||
setStrokeType(shape.getStrokeType());
|
setStrokeType(shape.getStrokeType());
|
||||||
|
setStrokeJoin(shape.getStrokeJoin());
|
||||||
visible = shape.isVisible();
|
visible = shape.isVisible();
|
||||||
rotation = shape.getRotation();
|
rotation = shape.getRotation();
|
||||||
scale(shape.getScale());
|
scale(shape.getScale());
|
||||||
@@ -335,9 +425,9 @@ public abstract class Shape extends BasicDrawable {
|
|||||||
* </code></pre>
|
* </code></pre>
|
||||||
* <p>
|
* <p>
|
||||||
* Die Methode kann beliebig umgesetzt werden, um eine 1-zu-1-Kopie dieser
|
* Die Methode kann beliebig umgesetzt werden, um eine 1-zu-1-Kopie dieser
|
||||||
* Form zu erhalten. In der Regel sollte aber jede Form einen Konstruktor
|
* Form zu erhalten. In der Regel besitzt aber jede Form einen Konstruktor,
|
||||||
* besitzen, die alle Werte einer andern Form übernimmt. Die gezeigte
|
* der alle Werte einer andern Form übernimmt. Die gezeigte Implementierung
|
||||||
* Implementierung dürfte daher im Regelfall ausreichend sein.
|
* ist daher im Regelfall ausreichend.
|
||||||
*
|
*
|
||||||
* @return Eine genaue Kopie dieser Form.
|
* @return Eine genaue Kopie dieser Form.
|
||||||
*/
|
*/
|
||||||
@@ -348,8 +438,8 @@ public abstract class Shape extends BasicDrawable {
|
|||||||
* zurück. Intern werden die AWT Shapes benutzt, um sie auf den
|
* zurück. Intern werden die AWT Shapes benutzt, um sie auf den
|
||||||
* {@link Graphics2D Grafikkontext} zu zeichnen.
|
* {@link Graphics2D Grafikkontext} zu zeichnen.
|
||||||
* <p>
|
* <p>
|
||||||
* Wenn diese Form nicht durch eine AWT-Shape dargestellt wird, kann die
|
* Wenn diese Form nicht durch eine AWT-Shape dargestellt wird, liefert die
|
||||||
* Methode {@code null} zurückgeben.
|
* Methode {@code null}.
|
||||||
*
|
*
|
||||||
* @return Eine Java-AWT {@code Shape} die diese Form repräsentiert oder
|
* @return Eine Java-AWT {@code Shape} die diese Form repräsentiert oder
|
||||||
* {@code null}.
|
* {@code null}.
|
||||||
@@ -369,34 +459,85 @@ public abstract class Shape extends BasicDrawable {
|
|||||||
return new Bounds(this);
|
return new Bounds(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verschiebt die Form um die angegebenen Werte entlang der
|
||||||
|
* Koordinatenachsen.
|
||||||
|
*
|
||||||
|
* @param dx Verschiebung entlang der x-Achse.
|
||||||
|
* @param dy Verschiebung entlang der y-Achse.
|
||||||
|
*/
|
||||||
public void move( double dx, double dy ) {
|
public void move( double dx, double dy ) {
|
||||||
x += dx;
|
x += dx;
|
||||||
y += dy;
|
y += dy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bewegt die Form an die angegebenen Koordinaten.
|
||||||
|
*
|
||||||
|
* @param x Die neue x-Koordinate.
|
||||||
|
* @param y Die neue y-Koordinate.
|
||||||
|
*/
|
||||||
public void moveTo( double x, double y ) {
|
public void moveTo( double x, double y ) {
|
||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
this.y = y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bewegt die Form an dieselben Koordinaten wie die angegebene Form.
|
||||||
|
*
|
||||||
|
* @param shape Eine andere Form.
|
||||||
|
*/
|
||||||
public void moveTo( Shape shape ) {
|
public void moveTo( Shape shape ) {
|
||||||
moveTo(shape.getX(), shape.getY());
|
moveTo(shape.getX(), shape.getY());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bewegt die Form zum angegebenen Ankerpunkt der angegebenen Form.
|
||||||
|
*
|
||||||
|
* @param shape Die andere Form.
|
||||||
|
* @param dir Die Richtung des Ankerpunktes.
|
||||||
|
* @see #moveTo(Shape, Options.Direction, double)
|
||||||
|
*/
|
||||||
public void moveTo( Shape shape, Options.Direction dir ) {
|
public void moveTo( Shape shape, Options.Direction dir ) {
|
||||||
moveTo(shape, dir, 0.0);
|
moveTo(shape, dir, 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bewegt den Ankerpunkt dieser Form zu einem Ankerpunkt einer anderen Form.
|
* Bewegt den Ankerpunkt dieser Form zu einem Ankerpunkt einer anderen
|
||||||
* Mit {@code buff} kann ein zusätzlicher Abstand angegeben werden, um den
|
* Form.
|
||||||
* die Form entlang des Ankerpunktes {@code anchor} verschoben werden soll.
|
* <p>
|
||||||
|
* Mit {@code buff} wird ein zusätzlicher Abstand angegeben, um den die Form
|
||||||
|
* entlang des Ankerpunktes {@code anchor} verschoben wird.
|
||||||
|
* <p>
|
||||||
* Ist der Anker zum Beispiel {@code NORTH}, dann wird die Form um
|
* Ist der Anker zum Beispiel {@code NORTH}, dann wird die Form um
|
||||||
* {@code buff} nach oben verschoben.
|
* {@code buff} oberhalb der oberen Kante der zweiten Form verschoben.
|
||||||
|
* <p>
|
||||||
|
* Befinden sich die Formen zuvor in folgender Ausrichtung:
|
||||||
|
* <pre>
|
||||||
|
* ┌─────────┐
|
||||||
|
* │ │
|
||||||
|
* W B │
|
||||||
|
* ┌─────┐ │ │
|
||||||
|
* │ │ └─────────┘
|
||||||
|
* W A │
|
||||||
|
* │ │
|
||||||
|
* └─────┘
|
||||||
|
* </pre>
|
||||||
|
* <p>
|
||||||
|
* bringt sie der Aufruf {@code B.moveTo(A, DOWN, 0)} in diese Ausrichtung:
|
||||||
|
* <pre>
|
||||||
|
* B.moveTo(A, WEST, 0) │ B.moveTo(A, WEST, 10)
|
||||||
|
* │
|
||||||
|
* ┌─────┬───┐ │ ┌┬────┬────┐
|
||||||
|
* │ │ │ │ ││ │ │
|
||||||
|
* │ A B│ │ │ ││ A B │
|
||||||
|
* │ │ │ │ ││ │ │
|
||||||
|
* └─────┴───┘ │ └┴────┴────┘
|
||||||
|
* </pre>
|
||||||
*
|
*
|
||||||
* @param shape
|
* @param shape Die andere Form.
|
||||||
* @param dir
|
* @param dir Die Richtung des Ankerpunktes.
|
||||||
* @param buff
|
* @param buff Der Abstand zum angegebenen Ankerpunkt.
|
||||||
*/
|
*/
|
||||||
public void moveTo( Shape shape, Options.Direction dir, double buff ) {
|
public void moveTo( Shape shape, Options.Direction dir, double buff ) {
|
||||||
Point2D ap = shape.getAbsAnchorPoint(dir);
|
Point2D ap = shape.getAbsAnchorPoint(dir);
|
||||||
@@ -406,15 +547,22 @@ public abstract class Shape extends BasicDrawable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Richtet die Form entlang der angegebenen Richtung am Rand der
|
* Bewegt die Form an den Rand der Zeichenfläche in der angegebenen
|
||||||
* Zeichenfläche aus.
|
* Richtung.
|
||||||
*
|
*
|
||||||
* @param dir Die Richtung der Ausrichtung.
|
* @param dir Die Richtung.
|
||||||
*/
|
*/
|
||||||
public void alignTo( Options.Direction dir ) {
|
public void alignTo( Options.Direction dir ) {
|
||||||
alignTo(dir, 0.0);
|
alignTo(dir, 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bewegt die Form mit dem angegebenen Abstand an den Rand der Zeichenfläche
|
||||||
|
* in der angegebenen Richtung aus.
|
||||||
|
*
|
||||||
|
* @param dir Die Richtung.
|
||||||
|
* @param buff Der Abstand zum Rand.
|
||||||
|
*/
|
||||||
public void alignTo( Options.Direction dir, double buff ) {
|
public void alignTo( Options.Direction dir, double buff ) {
|
||||||
Point2D anchorShape = Shape.getAnchorPoint(canvasWidth, canvasHeight, dir);
|
Point2D anchorShape = Shape.getAnchorPoint(canvasWidth, canvasHeight, dir);
|
||||||
Point2D anchorThis = this.getAbsAnchorPoint(dir);
|
Point2D anchorThis = this.getAbsAnchorPoint(dir);
|
||||||
@@ -423,20 +571,58 @@ public abstract class Shape extends BasicDrawable {
|
|||||||
this.y += Math.abs(dir.y) * (anchorShape.getY() - anchorThis.getY()) + dir.y * buff;
|
this.y += Math.abs(dir.y) * (anchorShape.getY() - anchorThis.getY()) + dir.y * buff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bewegt den Ankerpunkt dieser Form in der angegebenen Richtung zum
|
||||||
|
* Gleichen Ankerpunkt der anderen Form.
|
||||||
|
*
|
||||||
|
* @param shape Die andere Form.
|
||||||
|
* @param dir Die Richtung des Ankerpunktes.
|
||||||
|
* @see #alignTo(Shape, Options.Direction, double)
|
||||||
|
*/
|
||||||
public void alignTo( Shape shape, Options.Direction dir ) {
|
public void alignTo( Shape shape, Options.Direction dir ) {
|
||||||
alignTo(shape, dir, 0.0);
|
alignTo(shape, dir, 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Richtet die Form entlang der angegebenen Richtung an einer anderen Form
|
* Richtet die Form entlang der angegebenen Richtung an einer anderen Form
|
||||||
* aus. Für {@code DOWN} wird beispielsweise die y-Koordinate der unteren
|
* aus.
|
||||||
* Kante dieser Form an der unteren Kante der angegebenen Form {@code shape}
|
* <p>
|
||||||
* ausgerichtet. Die x-Koordinate wird nicht verändert. {@code buff} gibt
|
* {@code buff} gibt einen Abstand ab, um den diese From versetzt
|
||||||
* einen Abstand ab, um den diese From versetzt ausgerichtet werden soll.
|
* ausgerichtet wird.
|
||||||
|
* <p>
|
||||||
|
* Für {@link #DOWN} wird beispielsweise die y-Koordinate der unteren Kante
|
||||||
|
* dieser Form an der unteren Kante von {@code shape} ausgerichtet. Die
|
||||||
|
* x-Koordinate wird in dem Fall nicht verändert.
|
||||||
|
* <p>
|
||||||
|
* Befinden sich die Formen beispielsweise in folgender Position:
|
||||||
|
* <pre>
|
||||||
|
* ┌─────┐
|
||||||
|
* │ │
|
||||||
|
* │ B │
|
||||||
|
* ┌─────┐ │ │
|
||||||
|
* │ │ └──D──┘
|
||||||
|
* │ A │
|
||||||
|
* │ │
|
||||||
|
* └──D──┘
|
||||||
|
* </pre>
|
||||||
|
* <p>
|
||||||
|
* <p>
|
||||||
|
* werden sie durch {@code alignTo} so positioniert:
|
||||||
|
* <pre>
|
||||||
|
* B.alignTo(A, EAST, 0) │ B.alignTo(A, EAST, 10)
|
||||||
|
* │
|
||||||
|
* ┌─────┐ ┌─────┐ │ ┌─────┐
|
||||||
|
* │ │ │ │ │ │ │ ┌─────┐
|
||||||
|
* │ A │ │ B │ │ │ A │ │ │
|
||||||
|
* │ │ │ │ │ │ │ │ B │
|
||||||
|
* └──D──┘ └──D──┘ │ └──D──┘ │ │
|
||||||
|
* │ └──D──┘
|
||||||
|
* │
|
||||||
|
* </pre>
|
||||||
*
|
*
|
||||||
* @param shape
|
* @param shape Die andere Form.
|
||||||
* @param dir
|
* @param dir Die Richtung.
|
||||||
* @param buff
|
* @param buff Der Abstand.
|
||||||
*/
|
*/
|
||||||
public void alignTo( Shape shape, Options.Direction dir, double buff ) {
|
public void alignTo( Shape shape, Options.Direction dir, double buff ) {
|
||||||
Point2D anchorShape = shape.getAbsAnchorPoint(dir);
|
Point2D anchorShape = shape.getAbsAnchorPoint(dir);
|
||||||
@@ -446,16 +632,46 @@ public abstract class Shape extends BasicDrawable {
|
|||||||
this.y += Math.abs(dir.y) * (anchorShape.getY() - anchorThis.getY()) + dir.y * buff;
|
this.y += Math.abs(dir.y) * (anchorShape.getY() - anchorThis.getY()) + dir.y * buff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param shape
|
||||||
|
* @param dir
|
||||||
|
* @see #nextTo(Shape, Options.Direction, double)
|
||||||
|
*/
|
||||||
public void nextTo( Shape shape, Options.Direction dir ) {
|
public void nextTo( Shape shape, Options.Direction dir ) {
|
||||||
nextTo(shape, dir, DEFAULT_BUFFER);
|
nextTo(shape, dir, DEFAULT_BUFFER);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bewegt die Form neben eine andere in Richtung des angegebenen
|
* Bewegt die Form neben eine andere in Richtung des angegebenen
|
||||||
* Ankerpunktes. Im Gegensatz zu
|
* Ankerpunktes.
|
||||||
* {@link #moveTo(Shape, Options.Direction, double)} wird die Breite bzw.
|
* <p>
|
||||||
* Höhe der Formen berücksichtigt und die Formen so platziert, dass keine
|
* Im Gegensatz zu {@link #moveTo(Shape, Options.Direction, double)} wird
|
||||||
* Überlappungen vorhanden sind.
|
* die Breite bzw. Höhe der Formen berücksichtigt und die Formen so
|
||||||
|
* platziert, dass keine Überlappungen vorhanden sind.
|
||||||
|
* <p>
|
||||||
|
* Befinden sich die Formen zuvor in folgender Ausrichtung:
|
||||||
|
* <pre>
|
||||||
|
* ┌─────┐
|
||||||
|
* │ │
|
||||||
|
* W B │
|
||||||
|
* ┌──────┐ │ │
|
||||||
|
* │ │ └─────┘
|
||||||
|
* │ A E
|
||||||
|
* │ │
|
||||||
|
* └──────┘
|
||||||
|
* </pre>
|
||||||
|
* <p>
|
||||||
|
* bringt sie der Aufruf {@code B.nextTo(A, EAST, 0)} in diese Ausrichtung:
|
||||||
|
* <pre>
|
||||||
|
* B.nextTo(A, EAST, 0) │ B.nextTo(A, EAST, 10)
|
||||||
|
* │
|
||||||
|
* ┌─────┬─────┐ │ ┌─────┐ ┌─────┐
|
||||||
|
* │ │ │ │ │ │ │ │
|
||||||
|
* │ A │ B │ │ │ A │ │ B │
|
||||||
|
* │ │ │ │ │ │ │ │
|
||||||
|
* └─────┴─────┘ │ └─────┘ └─────┘
|
||||||
|
* │
|
||||||
|
* </pre>
|
||||||
*
|
*
|
||||||
* @param shape
|
* @param shape
|
||||||
* @param dir
|
* @param dir
|
||||||
@@ -469,6 +685,19 @@ public abstract class Shape extends BasicDrawable {
|
|||||||
this.y += (anchorShape.getY() - anchorThis.getY()) + dir.y * buff;
|
this.y += (anchorShape.getY() - anchorThis.getY()) + dir.y * buff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setGradient( schule.ngb.zm.Color from, schule.ngb.zm.Color to, Options.Direction dir ) {
|
||||||
|
Point2D apDir = getAbsAnchorPoint(dir);
|
||||||
|
Point2D apInv = getAbsAnchorPoint(dir.inverse());
|
||||||
|
setGradient(apInv.getX(), apInv.getY(), from, apDir.getX(), apDir.getY(), to);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setGradient( schule.ngb.zm.Color from, schule.ngb.zm.Color to ) {
|
||||||
|
Point2D ap = getAbsAnchorPoint(CENTER);
|
||||||
|
setGradient(ap.getX(), ap.getY(), Math.min(ap.getX(), ap.getY()), from, to);
|
||||||
|
}
|
||||||
|
|
||||||
/*public void shear( double dx, double dy ) {
|
/*public void shear( double dx, double dy ) {
|
||||||
verzerrung.shear(dx, dy);
|
verzerrung.shear(dx, dy);
|
||||||
}*/
|
}*/
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
/**
|
||||||
|
* Diese Paket enthält Formen, die Diagramme darstellen.
|
||||||
|
*/
|
||||||
|
package schule.ngb.zm.shapes.charts;
|
||||||
13
src/main/java/schule/ngb/zm/shapes/package-info.java
Normal file
13
src/main/java/schule/ngb/zm/shapes/package-info.java
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* Dieses Paket enthält Implementationen der abstrakten
|
||||||
|
* {@link schule.ngb.zm.shapes.Shape} Klasse.
|
||||||
|
*
|
||||||
|
* Jede Unterklasse von {@code Shape} stellt eine konkrete Form wie ein
|
||||||
|
* {@link schule.ngb.zm.shapes.Rectangle Rechteck}, ein
|
||||||
|
* {@link schule.ngb.zm.shapes.Circle Kreis} oder ein
|
||||||
|
* {@link schule.ngb.zm.shapes.Picture Bild} dar.
|
||||||
|
*
|
||||||
|
* Mit {@link schule.ngb.zm.shapes.ShapeGroup} können Formen gruppiert
|
||||||
|
* und gemeinsam transformiert werden.
|
||||||
|
*/
|
||||||
|
package schule.ngb.zm.shapes;
|
||||||
204
src/main/java/schule/ngb/zm/util/Cache.java
Normal file
204
src/main/java/schule/ngb/zm/util/Cache.java
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
package schule.ngb.zm.util;
|
||||||
|
|
||||||
|
import java.lang.ref.Reference;
|
||||||
|
import java.lang.ref.SoftReference;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ein Cache ist ein {@link Map} Implementation, die Inhaltsobjekte in einer
|
||||||
|
* {@link Reference} speichert und als Zwischenspeicher für Objekte dienen kann,
|
||||||
|
* deren Erstellung aufwendig ist.
|
||||||
|
* <p>
|
||||||
|
* Für einen Cache ist nicht garantiert, dass ein eingefügtes Objekt beim
|
||||||
|
* nächsten Aufruf noch vorhanden ist, da die Referenz inzwischen vom Garbage
|
||||||
|
* Collector gelöscht worden sein kann.
|
||||||
|
* <p>
|
||||||
|
* Als interne Map wird einen {@link ConcurrentHashMap} verwendet.
|
||||||
|
* <p>
|
||||||
|
* Ein passender Cache wird mittels der Fabrikmethoden {@link #newSoftCache()}
|
||||||
|
* und {@link #newWeakCache()} erstellt.
|
||||||
|
* <pre><code>
|
||||||
|
* Cache<String, Image> imageCache = Cache.newSoftCache();
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* @param <K> Der Typ der Schlüssel.
|
||||||
|
* @param <V> Der Typ der Objekte.
|
||||||
|
*/
|
||||||
|
public final class Cache<K, V> implements Map<K, V> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt einen Cache mit {@link SoftReference} Referenzen.
|
||||||
|
*
|
||||||
|
* @param <K> Der Typ der Schlüssel.
|
||||||
|
* @param <V> Der Typ der Objekte.
|
||||||
|
* @return Ein Cache.
|
||||||
|
*/
|
||||||
|
public static <K, V> Cache<K, V> newSoftCache() {
|
||||||
|
return new Cache<>(SoftReference::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt einen Cache mit {@link WeakReference} Referenzen.
|
||||||
|
*
|
||||||
|
* @param <K> Der Typ der Schlüssel.
|
||||||
|
* @param <V> Der Typ der Objekte.
|
||||||
|
* @return Ein Cache.
|
||||||
|
*/
|
||||||
|
public static <K, V> Cache<K, V> newWeakCache() {
|
||||||
|
return new Cache<>(WeakReference::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private final Map<K, Reference<V>> cache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private final Reference<V> NOCACHE;
|
||||||
|
|
||||||
|
private final Function<V, Reference<V>> refSupplier;
|
||||||
|
|
||||||
|
private Cache( Function<V, Reference<V>> refSupplier ) {
|
||||||
|
this.refSupplier = refSupplier;
|
||||||
|
NOCACHE = refSupplier.apply(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return cache.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return cache.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsKey( Object key ) {
|
||||||
|
return cache.containsKey(key) && cache.get(key).get() != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsValue( Object value ) {
|
||||||
|
return cache.values().stream()
|
||||||
|
.anyMatch(( ref ) -> ref.get() != null && ref.get() == value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V get( Object key ) {
|
||||||
|
if( cache.containsKey(key) ) {
|
||||||
|
return cache.get(key).get();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deaktiviert das Caching für den angegebenen Schlüssel.
|
||||||
|
* <p>
|
||||||
|
* Folgende Aufrufe von {@link #put(Object, Object)} mit demselben Schlüssel
|
||||||
|
* haben keinen Effekt. Um das Caching wieder zu aktivieren, muss
|
||||||
|
* {@link #remove(Object)} mit dem Schlüssel aufgerufen werden,
|
||||||
|
*
|
||||||
|
* @param key Der Schlüssel.
|
||||||
|
*/
|
||||||
|
public void disableCache( K key ) {
|
||||||
|
cache.put(key, NOCACHE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prüft, ob der für den angegebenen Schlüssel zuvor
|
||||||
|
* {@link #disableCache(Object)} aufgerufen wurde.
|
||||||
|
*
|
||||||
|
* @param key Der Schlüssel.
|
||||||
|
* @return {@code true}, wenn der Schlüssel nicht gespeichert wird.
|
||||||
|
*/
|
||||||
|
public boolean isCachingDisabled( K key ) {
|
||||||
|
return cache.get(key) == NOCACHE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V put( K key, V value ) {
|
||||||
|
if( !isCachingDisabled(key) ) {
|
||||||
|
V prev = remove(key);
|
||||||
|
cache.put(key, refSupplier.apply(value));
|
||||||
|
return prev;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V remove( Object key ) {
|
||||||
|
Reference<V> ref = cache.get(key);
|
||||||
|
cache.remove(key);
|
||||||
|
|
||||||
|
V prev = null;
|
||||||
|
if( ref != null ) {
|
||||||
|
prev = ref.get();
|
||||||
|
ref.clear();
|
||||||
|
}
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void putAll( Map<? extends K, ? extends V> m ) {
|
||||||
|
for( Entry<? extends K, ? extends V> e : m.entrySet() ) {
|
||||||
|
put(e.getKey(), e.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
cache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<K> keySet() {
|
||||||
|
return cache.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<V> values() {
|
||||||
|
return cache.values().stream()
|
||||||
|
.filter(( ref ) -> ref.get() != null)
|
||||||
|
.map(( ref ) -> ref.get())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Entry<K, V>> entrySet() {
|
||||||
|
return cache.entrySet().stream()
|
||||||
|
.filter(( e ) -> e.getValue() != null && e.getValue().get() != null)
|
||||||
|
.map(( e ) -> new SoftCacheEntryView(e.getKey()))
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class SoftCacheEntryView implements Map.Entry<K, V> {
|
||||||
|
|
||||||
|
private K key;
|
||||||
|
|
||||||
|
public SoftCacheEntryView( K key ) {
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public K getKey() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V getValue() {
|
||||||
|
return Cache.this.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V setValue( V value ) {
|
||||||
|
return Cache.this.put(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ import java.util.List;
|
|||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Eine Helferklasse, um Dinge zu zählen.
|
* Eine Hilfsklasse, um Dinge zu zählen.
|
||||||
* <p>
|
* <p>
|
||||||
* Im einfachsten Fall kann der Zähler als geteilte Zählvariable genutzt werden,
|
* Im einfachsten Fall kann der Zähler als geteilte Zählvariable genutzt werden,
|
||||||
* die mit {@link #inc()} und {@link #dec()} aus verschiedenen Objekten oder
|
* die mit {@link #inc()} und {@link #dec()} aus verschiedenen Objekten oder
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import java.time.LocalDateTime;
|
|||||||
import java.time.ZoneOffset;
|
import java.time.ZoneOffset;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Function;
|
import java.util.function.*;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@@ -28,21 +28,23 @@ import static java.lang.Math.log;
|
|||||||
import static schule.ngb.zm.Constants.random;
|
import static schule.ngb.zm.Constants.random;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hilfsklasse, um zufällige Beispieldaten zu erzeugen.
|
* Eine Hilfsklasse, um zufällige Beispieldaten zu erzeugen.
|
||||||
* <p>
|
* <p>
|
||||||
* Die Klasse kann verschiedene Arten realistischer Beispieldaten erzeugen,
|
* Die Klasse kann verschiedene Arten realistischer Beispieldaten erzeugen.
|
||||||
* unter anderem Namen, E-Mail-Adressen, Passwörter oder Platzhalter-Bilder.
|
* Unter anderem Namen, E-Mail-Adressen, Passwörter oder Platzhalter-Bilder.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings( "unused" )
|
@SuppressWarnings( "unused" )
|
||||||
public final class Faker {
|
public final class Faker {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* URL, von der extern generierte Fake-Bilder geladen werden können.
|
||||||
|
* <p>
|
||||||
|
* Die URL wird als Format-String definiert mit zwei {@code %d}
|
||||||
|
* Platzhaltern, die durch die Breite und Höhe des gewünschten Bildes
|
||||||
|
* ersetzt werden.
|
||||||
|
*/
|
||||||
public static final String FAKE_IMG_URL = "https://loremflickr.com/%d/%d";
|
public static final String FAKE_IMG_URL = "https://loremflickr.com/%d/%d";
|
||||||
|
|
||||||
public static void main( String[] args ) {
|
|
||||||
String text = Faker.fakeText(2000, 8);
|
|
||||||
System.out.println(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger Benutzerdaten.
|
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger Benutzerdaten.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -76,8 +78,8 @@ public final class Faker {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger Namen (Vor- und
|
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger Namen im Format
|
||||||
* Nachname).
|
* "Vorname Nachname".
|
||||||
*
|
*
|
||||||
* @param count Anzahl der Beispieldaten.
|
* @param count Anzahl der Beispieldaten.
|
||||||
* @return Ein Array mit den Beispieldaten.
|
* @return Ein Array mit den Beispieldaten.
|
||||||
@@ -133,7 +135,7 @@ public final class Faker {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger
|
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger
|
||||||
* {@code Date}-Objekte.
|
* {@code LocalDate}-Objekte, die ein Datum ohne Uhrzeit beschreiben.
|
||||||
*
|
*
|
||||||
* @param count Anzahl der Beispieldaten.
|
* @param count Anzahl der Beispieldaten.
|
||||||
* @return Ein Array mit den Beispieldaten.
|
* @return Ein Array mit den Beispieldaten.
|
||||||
@@ -151,6 +153,14 @@ public final class Faker {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger
|
||||||
|
* {@code LocalDateTime}-Objekte, die einen Zeitpunkt mit Dateum und Uhrzeit
|
||||||
|
* beschreiben,
|
||||||
|
*
|
||||||
|
* @param count Anzahl der Beispieldaten.
|
||||||
|
* @return Ein Array mit den Beispieldaten.
|
||||||
|
*/
|
||||||
public static LocalDateTime[] fakeDatetimes( int count ) {
|
public static LocalDateTime[] fakeDatetimes( int count ) {
|
||||||
long nowEpoch = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC);
|
long nowEpoch = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC);
|
||||||
long from = LocalDateTime.ofEpochSecond(nowEpoch - 18 * 365, 0, ZoneOffset.UTC).toEpochSecond(ZoneOffset.UTC);
|
long from = LocalDateTime.ofEpochSecond(nowEpoch - 18 * 365, 0, ZoneOffset.UTC).toEpochSecond(ZoneOffset.UTC);
|
||||||
@@ -164,6 +174,17 @@ public final class Faker {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erzeugt einen Blindtext mit der angegebenen Anzahl Worten, aufgeteilt in
|
||||||
|
* die angegebene Anzahl Absätze.
|
||||||
|
* <p>
|
||||||
|
* Abssätze werden duch einen doppelten Zeilenumbruch {@code \n\n}
|
||||||
|
* getrennt.
|
||||||
|
*
|
||||||
|
* @param words Anzahl Wörter im Text insgesamt.
|
||||||
|
* @param paragraphs Anzahl Absätze.
|
||||||
|
* @return Ein zufälliger Blindtext.
|
||||||
|
*/
|
||||||
public static String fakeText( int words, int paragraphs ) {
|
public static String fakeText( int words, int paragraphs ) {
|
||||||
String basetext = "";
|
String basetext = "";
|
||||||
try(
|
try(
|
||||||
@@ -197,6 +218,16 @@ public final class Faker {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erzeugt ein Array mit der angegebenen Anzahl zufällig erzeugter Integer
|
||||||
|
* im angegebenen Bereich.
|
||||||
|
*
|
||||||
|
* @param count Anzahl der Zahlen im Array.
|
||||||
|
* @param min Untere Grenze der Zufallszahlen.
|
||||||
|
* @param max Obere Grenze der Zufallszahlen.
|
||||||
|
* @return Ein Array mit Zufallszahlen.
|
||||||
|
* @see Constants#random(int, int)
|
||||||
|
*/
|
||||||
public static int[] fakeIntArray( int count, int min, int max ) {
|
public static int[] fakeIntArray( int count, int min, int max ) {
|
||||||
int[] arr = new int[count];
|
int[] arr = new int[count];
|
||||||
for( int i = 0; i < count; i++ ) {
|
for( int i = 0; i < count; i++ ) {
|
||||||
@@ -205,14 +236,56 @@ public final class Faker {
|
|||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erzeugt eine Liste mit der angegebenen Anzahl zufällig erzeugter Integer
|
||||||
|
* im angegebenen Bereich.
|
||||||
|
* <p>
|
||||||
|
* Ist {@code list} ein Listenobjekt, werden dei Zahlen an diese angehängt.
|
||||||
|
* Wird {@code null} übergeben, wird eine neue {@link ArrayList} erzeugt.
|
||||||
|
*
|
||||||
|
* @param count Anzahl der erzeugten Zahlen.
|
||||||
|
* @param min Untere Grenze der Zufallszahlen.
|
||||||
|
* @param max Obere Grenze der Zufallszahlen.
|
||||||
|
* @param list Eine Liste, die befüllt werden soll, oder {@code null}.
|
||||||
|
* @return Ein Array mit Zufallszahlen.
|
||||||
|
* @see Constants#random(int, int)
|
||||||
|
*/
|
||||||
public static List<Integer> fakeIntegerList( int count, int min, int max, List<Integer> list ) {
|
public static List<Integer> fakeIntegerList( int count, int min, int max, List<Integer> list ) {
|
||||||
if( list == null ) {
|
List<Integer> result = (list == null) ? new ArrayList<>(count) : list;
|
||||||
list = new ArrayList<>(count);
|
fakeIntegers(count, min, max, result::add);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erzeugt die angegebene Anzahl Zufallszahlen im angegebenen Bereich und
|
||||||
|
* übergibt sie an den angegebenen {@code Consumer}.
|
||||||
|
*
|
||||||
|
* Ein typischer Aufruf, um eine {@code #LinkedList} mit 100 Zufallszahlen
|
||||||
|
* zu erzeugen könnte so aussehen:
|
||||||
|
* <pre><code>
|
||||||
|
* List<Integer> l = new LinkedList<>();
|
||||||
|
* Faker.fakeIntegers(100, 0, 100, l::add);
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* @param count Anzahl der erzeugten Zahlen.
|
||||||
|
* @param min Untere Grenze der Zufallszahlen.
|
||||||
|
* @param max Obere Grenze der Zufallszahlen.
|
||||||
|
* @param con {@code Consumer} für die Zahlen.
|
||||||
|
*/
|
||||||
|
public static void fakeIntegers( int count, int min, int max, Consumer<Integer> con ) {
|
||||||
for( int i = 0; i < count; i++ ) {
|
for( int i = 0; i < count; i++ ) {
|
||||||
list.add(random(min, max));
|
con.accept(random(min, max));
|
||||||
}
|
}
|
||||||
return list;
|
}
|
||||||
|
|
||||||
|
public static <L> L fakeIntegers( int count, int min, int max, Supplier<L> sup, BiConsumer<L, Integer> con ) {
|
||||||
|
L result = sup.get();
|
||||||
|
|
||||||
|
for( int i = 0; i < count; i++ ) {
|
||||||
|
con.accept(result, random(min, max));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings( "unchecked" )
|
@SuppressWarnings( "unchecked" )
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ public final class Log {
|
|||||||
* mindestens herabgesenkt werden sollen.
|
* mindestens herabgesenkt werden sollen.
|
||||||
*/
|
*/
|
||||||
public static void enableGlobalLevel( Level level ) {
|
public static void enableGlobalLevel( Level level ) {
|
||||||
int lvl = Validator.requireNotNull(level).intValue();
|
int lvl = Validator.requireNotNull(level, "level").intValue();
|
||||||
ensureRootLoggerInitialized();
|
ensureRootLoggerInitialized();
|
||||||
|
|
||||||
// Decrease level of root level ConsoleHandlers for output
|
// Decrease level of root level ConsoleHandlers for output
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package schule.ngb.zm.util;
|
|||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helferklasse zur Zeitmessung im Nanosekundenbereich.
|
* Hilfsklasse zur Zeitmessung im Nanosekundenbereich.
|
||||||
* <p>
|
* <p>
|
||||||
* Mit einem {@code Timer} kann zum Beispiel die Laufzeit eines Algorithmus
|
* Mit einem {@code Timer} kann zum Beispiel die Laufzeit eines Algorithmus
|
||||||
* gemessen werden. Wie eine echte Stoppuhr läuft der {@code Timer} weiter, wenn
|
* gemessen werden. Wie eine echte Stoppuhr läuft der {@code Timer} weiter, wenn
|
||||||
|
|||||||
@@ -6,35 +6,55 @@ import java.util.function.Supplier;
|
|||||||
/**
|
/**
|
||||||
* Statische Methoden, um Methodenparameter auf Gültigkeit zu prüfen.
|
* Statische Methoden, um Methodenparameter auf Gültigkeit zu prüfen.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings( "UnusedReturnValue" )
|
@SuppressWarnings( {"unused", "UnusedReturnValue"} )
|
||||||
public class Validator {
|
public class Validator {
|
||||||
|
|
||||||
public static final <T> T requireNotNull( T obj ) {
|
public static final <T> T requireNotNull( T obj, CharSequence paramName ) {
|
||||||
return Objects.requireNonNull(obj);
|
return requireNotNull(obj, () -> "Parameter <%s> may not be null.".format(paramName.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final <T> T requireNotNull( T obj, CharSequence msg ) {
|
public static final <T> T requireNotNull( T obj, CharSequence paramName, CharSequence msg ) {
|
||||||
return Objects.requireNonNull(obj, msg::toString);
|
return requireNotNull(obj, () -> msg.toString().format(paramName.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final <T> T requireNotNull( T obj, Supplier<String> msg ) {
|
public static final <T> T requireNotNull( T obj, Supplier<String> msg ) {
|
||||||
return Objects.requireNonNull(obj, msg);
|
if( obj == null ) {
|
||||||
|
throw new NullPointerException(msg == null ? "Parameter may not be null." : msg.get());
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final String requireNotEmpty( String str ) {
|
public static final String requireNotEmpty( String str, CharSequence paramName ) {
|
||||||
return requireNotEmpty(str, (Supplier<String>) null);
|
return requireNotEmpty(str, () -> String.format("Parameter <%s> may not be empty string (<%s> provided)", paramName, str));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final String requireNotEmpty( String str, CharSequence msg ) {
|
public static final String requireNotEmpty( String str, CharSequence paramName, CharSequence msg ) {
|
||||||
return requireNotEmpty(str, msg::toString);
|
return requireNotEmpty(str, () -> msg.toString().format(paramName.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final String requireNotEmpty( String str, Supplier<String> msg ) {
|
public static final String requireNotEmpty( String str, Supplier<String> msg ) {
|
||||||
if( str.isEmpty() )
|
if( str.isEmpty() ) {
|
||||||
throw new IllegalArgumentException(msg == null ? String.format("Parameter may not be empty string (<%s> provided)", str) : msg.get());
|
throw new IllegalArgumentException(msg == null ? String.format("Parameter may not be empty string (<%s> provided)", str) : msg.get());
|
||||||
|
}
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final <T> T[] requireNotEmpty( T[] arr, CharSequence paramName ) {
|
||||||
|
return requireNotEmpty(arr, () -> String.format("Parameter <%s> may not be empty", paramName));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final <T> T[] requireNotEmpty( T[] arr, CharSequence paramName, CharSequence msg ) {
|
||||||
|
return requireNotEmpty(arr, () -> msg.toString().format(paramName.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final <T> T[] requireNotEmpty( T[] arr, Supplier<String> msg ) {
|
||||||
|
if( arr.length == 0 )
|
||||||
|
throw new IllegalArgumentException(msg == null ? "Parameter array may not be empty" : msg.get());
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static final int requirePositive( int i ) {
|
public static final int requirePositive( int i ) {
|
||||||
return requirePositive(i, (Supplier<String>) null);
|
return requirePositive(i, (Supplier<String>) null);
|
||||||
}
|
}
|
||||||
@@ -139,21 +159,6 @@ public class Validator {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
public static final <T> T[] requireNotEmpty( T[] arr ) {
|
|
||||||
return requireNotEmpty(arr, (Supplier<String>) null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final <T> T[] requireNotEmpty( T[] arr, CharSequence msg ) {
|
|
||||||
return requireNotEmpty(arr, msg::toString);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final <T> T[] requireNotEmpty( T[] arr, Supplier<String> msg ) {
|
|
||||||
if( arr.length == 0 )
|
|
||||||
throw new IllegalArgumentException(msg == null ? "Parameter array may not be empty" : msg.get());
|
|
||||||
return arr;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final <T> T[] requireSize( T[] arr, int size ) {
|
public static final <T> T[] requireSize( T[] arr, int size ) {
|
||||||
return requireSize(arr, size, (Supplier<String>) null);
|
return requireSize(arr, size, (Supplier<String>) null);
|
||||||
}
|
}
|
||||||
@@ -168,20 +173,6 @@ public class Validator {
|
|||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final <T> T[] requireValid( T[] arr ) {
|
|
||||||
return requireValid(arr, (Supplier<String>) null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final <T> T[] requireValid( T[] arr, CharSequence msg ) {
|
|
||||||
return requireValid(arr, msg::toString);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final <T> T[] requireValid( T[] arr, Supplier<String> msg ) {
|
|
||||||
if( arr == null || arr.length > 0 )
|
|
||||||
throw new IllegalArgumentException(msg == null ? "Parameter array may not be null or empty" : msg.get());
|
|
||||||
return arr;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Validator() {
|
private Validator() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,9 @@ import java.util.function.BiConsumer;
|
|||||||
* dispatcher.dispatchEvent("stop", new MyEvent());
|
* dispatcher.dispatchEvent("stop", new MyEvent());
|
||||||
* }
|
* }
|
||||||
* </code></pre>
|
* </code></pre>
|
||||||
|
* <p>
|
||||||
|
* Siehe {@link schule.ngb.zm.media.AudioListener} und
|
||||||
|
* {@link schule.ngb.zm.media.Music} für ein Beispiel der Verwendung.
|
||||||
*
|
*
|
||||||
* @param <E> Typ der Event-Objekte.
|
* @param <E> Typ der Event-Objekte.
|
||||||
* @param <L> Typ der verwendeten Listener-Schnittstelle.
|
* @param <L> Typ der verwendeten Listener-Schnittstelle.
|
||||||
@@ -73,8 +76,8 @@ public class EventDispatcher<E, L extends Listener<E>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void registerEventType( String eventKey, BiConsumer<E, L> dispatcher ) {
|
public void registerEventType( String eventKey, BiConsumer<E, L> dispatcher ) {
|
||||||
Validator.requireNotNull(eventKey);
|
Validator.requireNotNull(eventKey, "eventKey");
|
||||||
Validator.requireNotNull(dispatcher);
|
Validator.requireNotNull(dispatcher, "dispatcher");
|
||||||
|
|
||||||
if( !eventRegistered(eventKey) ) {
|
if( !eventRegistered(eventKey) ) {
|
||||||
eventRegistry.put(eventKey, dispatcher);
|
eventRegistry.put(eventKey, dispatcher);
|
||||||
@@ -99,8 +102,8 @@ public class EventDispatcher<E, L extends Listener<E>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void dispatchEvent( String eventKey, final E event ) {
|
public void dispatchEvent( String eventKey, final E event ) {
|
||||||
Validator.requireNotNull(eventKey);
|
Validator.requireNotNull(eventKey, "eventKey");
|
||||||
Validator.requireNotNull(event);
|
Validator.requireNotNull(event, "event");
|
||||||
|
|
||||||
if( eventRegistered(eventKey) ) {
|
if( eventRegistered(eventKey) ) {
|
||||||
final BiConsumer<E, L> dispatcher = eventRegistry.get(eventKey);
|
final BiConsumer<E, L> dispatcher = eventRegistry.get(eventKey);
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
/**
|
||||||
|
* Dieses Paket enthält Hilfsklassen, die das Listener-Entwurfsmuster
|
||||||
|
* umsetzen.
|
||||||
|
*/
|
||||||
|
package schule.ngb.zm.util.events;
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
package schule.ngb.zm.util.io;
|
package schule.ngb.zm.util.io;
|
||||||
|
|
||||||
import schule.ngb.zm.util.Log;
|
import schule.ngb.zm.util.Log;
|
||||||
|
import schule.ngb.zm.util.Validator;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -11,9 +13,10 @@ import java.util.Arrays;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helferklasse, um Textdateien in verschiedenen Formaten einzulesen.
|
* Hilfsklasse, um Textdateien in verschiedenen Formaten einzulesen.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings( "unused" )
|
@SuppressWarnings( "unused" )
|
||||||
public final class FileLoader {
|
public final class FileLoader {
|
||||||
@@ -63,6 +66,9 @@ public final class FileLoader {
|
|||||||
* @return Eine Liste mit den Zeilen der Textdatei.
|
* @return Eine Liste mit den Zeilen der Textdatei.
|
||||||
*/
|
*/
|
||||||
public static List<String> loadLines( String source, Charset charset ) {
|
public static List<String> loadLines( String source, Charset charset ) {
|
||||||
|
Validator.requireNotNull(source, "source");
|
||||||
|
Validator.requireNotNull(charset, "charset");
|
||||||
|
|
||||||
try( BufferedReader reader = ResourceStreamProvider.getReader(source, charset) ) {
|
try( BufferedReader reader = ResourceStreamProvider.getReader(source, charset) ) {
|
||||||
List<String> result = new ArrayList<>();
|
List<String> result = new ArrayList<>();
|
||||||
|
|
||||||
@@ -72,8 +78,11 @@ public final class FileLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
} catch( MalformedURLException muex ) {
|
||||||
|
LOG.warn("Could not find resource for <%s>", source);
|
||||||
|
return Collections.emptyList();
|
||||||
} catch( IOException ex ) {
|
} catch( IOException ex ) {
|
||||||
LOG.error(ex, "Error while loading lines from source <%s>", source);
|
LOG.warn(ex, "Error while loading lines from source <%s>", source);
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -101,6 +110,9 @@ public final class FileLoader {
|
|||||||
* @return Der Inhalt der Textdatei.
|
* @return Der Inhalt der Textdatei.
|
||||||
*/
|
*/
|
||||||
public static String loadText( String source, Charset charset ) {
|
public static String loadText( String source, Charset charset ) {
|
||||||
|
Validator.requireNotNull(source, "source");
|
||||||
|
Validator.requireNotNull(charset, "charset");
|
||||||
|
|
||||||
try( BufferedReader reader = ResourceStreamProvider.getReader(source, charset) ) {
|
try( BufferedReader reader = ResourceStreamProvider.getReader(source, charset) ) {
|
||||||
StringBuilder result = new StringBuilder();
|
StringBuilder result = new StringBuilder();
|
||||||
|
|
||||||
@@ -110,8 +122,11 @@ public final class FileLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return result.toString();
|
return result.toString();
|
||||||
|
} catch( MalformedURLException muex ) {
|
||||||
|
LOG.warn("Could not find resource for <%s>", source);
|
||||||
|
return "";
|
||||||
} catch( IOException ex ) {
|
} catch( IOException ex ) {
|
||||||
LOG.error(ex, "Error while loading string from source <%s>", source);
|
LOG.warn(ex, "Error while loading string from source <%s>", source);
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -166,34 +181,39 @@ public final class FileLoader {
|
|||||||
).toArray(String[][]::new);
|
).toArray(String[][]::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static double[][] loadValues( String source, char separator, boolean skipFirst ) {
|
public static double[][] loadValues( String source, String separator, boolean skipFirst ) {
|
||||||
return loadValues(source, separator, skipFirst, UTF8);
|
return loadValues(source, separator, skipFirst, UTF8);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lädt Double-Werte aus einer CSV Datei in ein zweidimensionales Array.
|
* Lädt Double-Werte aus einer Text-Datei in ein zweidimensionales Array.
|
||||||
* <p>
|
* <p>
|
||||||
* Die gelesenen Strings werden mit {@link Double#parseDouble(String)} in
|
* Die Zeilen der Eingabedatei werden anhand der Zeichenkette {@code separator}
|
||||||
* {@code double} umgeformt. Es leigt in der Verantwortung des Nutzers
|
* in einzelne Teile aufgetrennt. {@code separator} wird als regulärer Ausdruck
|
||||||
* sicherzustellen, dass die CSV-Datei auch nur Zahlen enthält, die korrekt
|
* interpretiert (siehe {@link String#split(String)}).
|
||||||
* in {@code double} umgewandelt werden können. Zellen für die die
|
* <p>
|
||||||
* Umwandlung fehlschlägt werden mit 0.0 befüllt.
|
* Jeder Teilstring wird mit {@link Double#parseDouble(String)} in
|
||||||
|
* {@code double} umgeformt. Es liegt in der Verantwortung des Nutzers,
|
||||||
|
* sicherzustellen, dass die Eingabedatei nur Zahlen enthält, die korrekt
|
||||||
|
* in {@code double} umgewandelt werden können. Zellen, für die die
|
||||||
|
* Umwandlung fehlschlägt, werden mit 0.0 befüllt.
|
||||||
* <p>
|
* <p>
|
||||||
* Die Methode unterliegt denselben Einschränkungen wie
|
* Die Methode unterliegt denselben Einschränkungen wie
|
||||||
* {@link #loadCsv(String, char, boolean, Charset)}.
|
* {@link #loadCsv(String, char, boolean, Charset)}.
|
||||||
*
|
*
|
||||||
* @param source Die Quelle der CSV-Daten.
|
* @param source Die Quelle der CSV-Daten.
|
||||||
* @param separator Das verwendete Trennzeichen.
|
* @param separator Ein Trennzeichen oder ein regulärer Ausdruck.
|
||||||
* @param skipFirst Ob die erste Zeile übersprungen werden soll.
|
* @param skipFirst Ob die erste Zeile übersprungen werden soll.
|
||||||
* @param charset Die zu verwendende Zeichenkodierung.
|
* @param charset Die zu verwendende Zeichenkodierung.
|
||||||
* @return Ein Array mit den Daten als {@code String}s.
|
* @return Ein Array mit den Daten als {@code String}s.
|
||||||
*/
|
*/
|
||||||
public static double[][] loadValues( String source, char separator, boolean skipFirst, Charset charset ) {
|
public static double[][] loadValues( String source, String separator, boolean skipFirst, Charset charset ) {
|
||||||
int n = skipFirst ? 1 : 0;
|
int n = skipFirst ? 1 : 0;
|
||||||
List<String> lines = loadLines(source, charset);
|
List<String> lines = loadLines(source, charset);
|
||||||
return lines.stream().skip(n).map(
|
return lines.stream().skip(n).map(
|
||||||
( line ) -> Arrays
|
( line ) -> Arrays
|
||||||
.stream(line.split(Character.toString(separator)))
|
//.stream(line.split(Character.toString(separator)))
|
||||||
|
.stream(line.split(separator))
|
||||||
.mapToDouble(
|
.mapToDouble(
|
||||||
( value ) -> {
|
( value ) -> {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,32 +1,42 @@
|
|||||||
package schule.ngb.zm.util.io;
|
package schule.ngb.zm.util.io;
|
||||||
|
|
||||||
|
import schule.ngb.zm.util.Cache;
|
||||||
import schule.ngb.zm.util.Log;
|
import schule.ngb.zm.util.Log;
|
||||||
import schule.ngb.zm.util.Validator;
|
import schule.ngb.zm.util.Validator;
|
||||||
|
|
||||||
import java.awt.Font;
|
import java.awt.Font;
|
||||||
import java.awt.FontFormatException;
|
import java.awt.FontFormatException;
|
||||||
|
import java.awt.Image;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Eine Hilfsklasse mit Klassenmethoden, um Schriftarten zu laden.
|
||||||
|
* <p>
|
||||||
|
* Schriftarten können von verschiedenen Quellen geladen werden. Schriftarten,
|
||||||
|
* die aus Dateien geladen wurden, werden in einem internen Cache gespeichert
|
||||||
|
* und nachfolgende Versuche, dieselbe Schriftart zu laden, werden aus dem Cache
|
||||||
|
* bedient.
|
||||||
|
*/
|
||||||
public class FontLoader {
|
public class FontLoader {
|
||||||
|
|
||||||
private static final Map<String, Font> fontCache = new ConcurrentHashMap<>();
|
private static final Cache<String, Font> fontCache = Cache.newSoftCache();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lädt eine Schrift aus einer Datei.
|
* Lädt eine Schrift aus einer Datei.
|
||||||
* <p>
|
* <p>
|
||||||
* Die Methode kann eine Liste von Schriften bekommen und probiert diese
|
* Die Methode kann eine Liste von Schriften bekommen und probiert diese
|
||||||
* nacheinander zu laden. Die erste Schrift, die Fehlerfrei geladen werden
|
* nacheinander zu laden. Die erste Schrift, die Fehlerfrei geladen werden
|
||||||
* kann, wird zurückgegeben. Kann keine der Schriften geladen werden, ist das
|
* kann, wird zurückgegeben. Kann keine der Schriften geladen werden, ist
|
||||||
* Ergebnis {@code null}.
|
* das Ergebnis {@code null}.
|
||||||
* <p>
|
* <p>
|
||||||
* Die gefundene Schrift wird unter ihrem Dateinamen in den Schriftenspeicher
|
* Die gefundene Schrift wird unter ihrem Dateinamen in den
|
||||||
* geladen und kann danach in der Zeichenmaschine benutzt werden.
|
* Schriftenspeicher geladen und kann danach in der Zeichenmaschine benutzt
|
||||||
|
* werden.
|
||||||
* <p>
|
* <p>
|
||||||
* Eine Datei mit dem Namen "fonts/Font-Name.ttf" würde mit dem Namen
|
* Eine Datei mit dem Namen "fonts/Font-Name.ttf" würde mit dem Namen
|
||||||
* "Font-Name" geladen und kann danach zum Beispiel in einem
|
* "Font-Name" geladen und kann danach zum Beispiel in einem
|
||||||
@@ -53,8 +63,8 @@ public class FontLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static Font loadFont( String name, String source ) {
|
public static Font loadFont( String name, String source ) {
|
||||||
Validator.requireNotNull(source,"Font source may not be null");
|
Validator.requireNotNull(source, "source");
|
||||||
Validator.requireNotEmpty(source, "Font source may not be empty.");
|
Validator.requireNotEmpty(source, "source");
|
||||||
|
|
||||||
if( fontCache.containsKey(name) ) {
|
if( fontCache.containsKey(name) ) {
|
||||||
LOG.trace("Retrieved font <%s> from font cache.", name);
|
LOG.trace("Retrieved font <%s> from font cache.", name);
|
||||||
@@ -82,10 +92,12 @@ public class FontLoader {
|
|||||||
//ge.registerFont(font);
|
//ge.registerFont(font);
|
||||||
}
|
}
|
||||||
LOG.debug("Loaded custom font from source <%s>.", source);
|
LOG.debug("Loaded custom font from source <%s>.", source);
|
||||||
|
} catch( MalformedURLException muex ) {
|
||||||
|
LOG.warn("Could not find font resource for <%s>", source);
|
||||||
} catch( IOException ioex ) {
|
} catch( IOException ioex ) {
|
||||||
LOG.error(ioex, "Error loading custom font file from source <%s>.", source);
|
LOG.warn(ioex, "Error loading custom font file from source <%s>.", source);
|
||||||
} catch( FontFormatException ffex ) {
|
} catch( FontFormatException ffex ) {
|
||||||
LOG.error(ffex, "Error creating custom font from source <%s>.", source);
|
LOG.warn(ffex, "Error creating custom font from source <%s>.", source);
|
||||||
}
|
}
|
||||||
|
|
||||||
return font;
|
return font;
|
||||||
@@ -96,11 +108,12 @@ public class FontLoader {
|
|||||||
* <p>
|
* <p>
|
||||||
* Die Methode kann eine Liste von Schriften bekommen und probiert diese
|
* Die Methode kann eine Liste von Schriften bekommen und probiert diese
|
||||||
* nacheinander zu laden. Die erste Schrift, die Fehlerfrei geladen werden
|
* nacheinander zu laden. Die erste Schrift, die Fehlerfrei geladen werden
|
||||||
* kann, wird zurückgegeben. Kann keine der Schriften geladen werden, ist das
|
* kann, wird zurückgegeben. Kann keine der Schriften geladen werden, ist
|
||||||
* Ergebnis {@code null}.
|
* das Ergebnis {@code null}.
|
||||||
* <p>
|
* <p>
|
||||||
* Die gefundene Schrift wird unter ihrem Dateinamen in den Schriftenspeicher
|
* Die gefundene Schrift wird unter ihrem Dateinamen in den
|
||||||
* geladen und kann danach in der Zeichenmaschine benutzt werden.
|
* Schriftenspeicher geladen und kann danach in der Zeichenmaschine benutzt
|
||||||
|
* werden.
|
||||||
* <p>
|
* <p>
|
||||||
* Eine Datei mit dem Namen "fonts/Font-Name.ttf" würde mit dem Namen
|
* Eine Datei mit dem Namen "fonts/Font-Name.ttf" würde mit dem Namen
|
||||||
* "Font-Name" geladen und kann danach zum Beispiel in einem
|
* "Font-Name" geladen und kann danach zum Beispiel in einem
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package schule.ngb.zm.util.io;
|
package schule.ngb.zm.util.io;
|
||||||
|
|
||||||
import schule.ngb.zm.util.Log;
|
import schule.ngb.zm.util.Log;
|
||||||
|
import schule.ngb.zm.util.Cache;
|
||||||
import schule.ngb.zm.util.Validator;
|
import schule.ngb.zm.util.Validator;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
@@ -15,16 +16,20 @@ import java.io.File;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.lang.ref.SoftReference;
|
import java.lang.ref.SoftReference;
|
||||||
import java.util.Map;
|
import java.net.MalformedURLException;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Eine Hilfsklasse mit Klassenmethoden, um Bilder zu laden.
|
||||||
|
* <p>
|
||||||
|
* Bilder können von verschiedenen Quellen als {@link Image} geladen werden. Die
|
||||||
|
* Objekte werden in einem internen Cache gespeichert und nachfolgende Versuche,
|
||||||
|
* dieselbe Quelle zu laden, werden aus dem Cache bedient.
|
||||||
|
*/
|
||||||
public final class ImageLoader {
|
public final class ImageLoader {
|
||||||
|
|
||||||
public static boolean caching = true;
|
public static boolean caching = true;
|
||||||
|
|
||||||
private static final Map<String, SoftReference<BufferedImage>> imageCache = new ConcurrentHashMap<>();
|
private static final Cache<String, BufferedImage> imageCache = Cache.newSoftCache();
|
||||||
|
|
||||||
private static final SoftReference<BufferedImage> NOCACHE = new SoftReference<>(null);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lädt ein Bild von der angegebenen Quelle {@code source}.
|
* Lädt ein Bild von der angegebenen Quelle {@code source}.
|
||||||
@@ -63,11 +68,11 @@ public final class ImageLoader {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public static BufferedImage loadImage( String source, boolean caching ) {
|
public static BufferedImage loadImage( String source, boolean caching ) {
|
||||||
Validator.requireNotNull(source, "Image source may not be null");
|
Validator.requireNotNull(source, "source");
|
||||||
Validator.requireNotEmpty(source, "Image source may not be empty.");
|
Validator.requireNotEmpty(source, "source");
|
||||||
|
|
||||||
if( caching && isCached(source) ) {
|
if( caching && imageCache.containsKey(source) ) {
|
||||||
return getCache(source);
|
return imageCache.get(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
BufferedImage img = null;
|
BufferedImage img = null;
|
||||||
@@ -78,10 +83,12 @@ public final class ImageLoader {
|
|||||||
img = ImageIO.read(in);
|
img = ImageIO.read(in);
|
||||||
|
|
||||||
if( caching && img != null ) {
|
if( caching && img != null ) {
|
||||||
putCache(source, img);
|
imageCache.put(source, img);
|
||||||
}
|
}
|
||||||
|
} catch( MalformedURLException muex ) {
|
||||||
|
LOG.warn("Could not find image resource for <%s>", source);
|
||||||
} catch( IOException ioex ) {
|
} catch( IOException ioex ) {
|
||||||
LOG.error(ioex, "Error loading image file from source <%s>.", source);
|
LOG.warn(ioex, "Error loading image file from source <%s>.", source);
|
||||||
}
|
}
|
||||||
return img;
|
return img;
|
||||||
}
|
}
|
||||||
@@ -106,7 +113,7 @@ public final class ImageLoader {
|
|||||||
public static boolean preloadImage( String name, String source ) {
|
public static boolean preloadImage( String name, String source ) {
|
||||||
BufferedImage img = loadImage(source, true);
|
BufferedImage img = loadImage(source, true);
|
||||||
if( img != null ) {
|
if( img != null ) {
|
||||||
putCache(name, img);
|
imageCache.put(name, img);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -127,7 +134,7 @@ public final class ImageLoader {
|
|||||||
* @param img ZU speicherndes Bild.
|
* @param img ZU speicherndes Bild.
|
||||||
*/
|
*/
|
||||||
public static void preloadImage( String name, BufferedImage img ) {
|
public static void preloadImage( String name, BufferedImage img ) {
|
||||||
putCache(name, img);
|
imageCache.put(name, img);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -138,8 +145,7 @@ public final class ImageLoader {
|
|||||||
* {@code false}.
|
* {@code false}.
|
||||||
*/
|
*/
|
||||||
public static boolean isCached( String name ) {
|
public static boolean isCached( String name ) {
|
||||||
SoftReference<BufferedImage> imgRef = imageCache.get(name);
|
return imageCache.containsKey(name);
|
||||||
return imgRef != null && imgRef != NOCACHE && imgRef.get() != null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -152,30 +158,6 @@ public final class ImageLoader {
|
|||||||
imageCache.remove(name);
|
imageCache.remove(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Speichert ein Bild als {@link SoftReference} im Cache.
|
|
||||||
*
|
|
||||||
* @param name Name des Bildes im Zwischenspeicher.
|
|
||||||
* @param img Das zu speichernde Bild.
|
|
||||||
*/
|
|
||||||
private static void putCache( final String name, final BufferedImage img ) {
|
|
||||||
imageCache.put(name, new SoftReference<>(img));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Holt ein Bild aus dem Cache.
|
|
||||||
* <p>
|
|
||||||
* Prüft nicht, ob ein Bild vorhanden ist. Dies sollte vom Aufrufenden
|
|
||||||
* übernommen werden, da sonst eine {@link NullPointerException} erzeugt
|
|
||||||
* werden kann.
|
|
||||||
*
|
|
||||||
* @param name
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private static BufferedImage getCache( final String name ) {
|
|
||||||
return imageCache.get(name).get();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deaktiviert den Cache für die angegebene Quelle.
|
* Deaktiviert den Cache für die angegebene Quelle.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -185,14 +167,14 @@ public final class ImageLoader {
|
|||||||
*
|
*
|
||||||
* @param name Die Quelle des Bildes.
|
* @param name Die Quelle des Bildes.
|
||||||
*/
|
*/
|
||||||
public static void preventCache( final String name ) {
|
public static void disableCache( final String name ) {
|
||||||
imageCache.put(name, NOCACHE);
|
imageCache.disableCache(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Leer den Cache und löschte alle bisher gespeicherten Bilder.
|
* Leer den Cache und löschte alle bisher gespeicherten Bilder.
|
||||||
* <p>
|
* <p>
|
||||||
* Auch vorher mit {@link #preventCache(String)} verhinderte Caches werden
|
* Auch vorher mit {@link #disableCache(String)} verhinderte Caches werden
|
||||||
* gelöscht und müssen neu gesetzt werden.
|
* gelöscht und müssen neu gesetzt werden.
|
||||||
*/
|
*/
|
||||||
public static void clearCache() {
|
public static void clearCache() {
|
||||||
@@ -339,12 +321,8 @@ public final class ImageLoader {
|
|||||||
* @throws IOException Falls es einen Fehler beim Speichern gab.
|
* @throws IOException Falls es einen Fehler beim Speichern gab.
|
||||||
*/
|
*/
|
||||||
public static void saveImage( BufferedImage image, File file, boolean overwriteIfExists ) throws IOException {
|
public static void saveImage( BufferedImage image, File file, boolean overwriteIfExists ) throws IOException {
|
||||||
if( image == null ) {
|
Validator.requireNotNull(image, "image");
|
||||||
throw new NullPointerException("Image may not be <null>.");
|
Validator.requireNotNull(file, "file");
|
||||||
}
|
|
||||||
if( file == null ) {
|
|
||||||
throw new NullPointerException("File may not be <null>.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if( file.isFile() ) {
|
if( file.isFile() ) {
|
||||||
// Datei existiert schon
|
// Datei existiert schon
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import java.nio.file.Files;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helferklasse, um {@link InputStream}s für Ressourcen zu erhalten.
|
* Hilfsklasse, um {@link InputStream}s für Ressourcen zu erhalten.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class ResourceStreamProvider {
|
public class ResourceStreamProvider {
|
||||||
@@ -47,8 +47,8 @@ public class ResourceStreamProvider {
|
|||||||
* einer bestehenden Ressource.
|
* einer bestehenden Ressource.
|
||||||
*/
|
*/
|
||||||
public static URL getResourceURL( String source ) throws NullPointerException, IllegalArgumentException, IOException {
|
public static URL getResourceURL( String source ) throws NullPointerException, IllegalArgumentException, IOException {
|
||||||
Validator.requireNotNull(source, "Resource source may not be null");
|
Validator.requireNotNull(source, "source");
|
||||||
Validator.requireNotEmpty(source, "Resource source may not be empty.");
|
Validator.requireNotEmpty(source, "source");
|
||||||
|
|
||||||
// Ist source ein valider Dateipfad?
|
// Ist source ein valider Dateipfad?
|
||||||
File file = new File(source);
|
File file = new File(source);
|
||||||
|
|||||||
5
src/main/java/schule/ngb/zm/util/io/package-info.java
Normal file
5
src/main/java/schule/ngb/zm/util/io/package-info.java
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
/**
|
||||||
|
* Dieses Paket enthält Hilfsklassen, um Ressourcen aus verschiedenen Quellen
|
||||||
|
* zu laden.
|
||||||
|
*/
|
||||||
|
package schule.ngb.zm.util.io;
|
||||||
4
src/main/java/schule/ngb/zm/util/package-info.java
Normal file
4
src/main/java/schule/ngb/zm/util/package-info.java
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
/**
|
||||||
|
* Dieses Paket enthält Hilfsklassen für verschiedene Einsatzzwecke.
|
||||||
|
*/
|
||||||
|
package schule.ngb.zm.util;
|
||||||
5
src/main/java/schule/ngb/zm/util/tasks/package-info.java
Normal file
5
src/main/java/schule/ngb/zm/util/tasks/package-info.java
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
/**
|
||||||
|
* Dieses Paket enthält Hilfsklassen zur Ausführung paralleler Aufgaben
|
||||||
|
* innerhalb der Zeichenmaschine.
|
||||||
|
*/
|
||||||
|
package schule.ngb.zm.util.tasks;
|
||||||
9
src/main/resources/overview.html
Normal file
9
src/main/resources/overview.html
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<HTML>
|
||||||
|
<BODY>
|
||||||
|
Die Zeichenmaschine
|
||||||
|
<p>
|
||||||
|
<h1>Die Zeichenmaschine</h1>
|
||||||
|
|
||||||
|
@author J. Neugebauer
|
||||||
|
</BODY>
|
||||||
|
</HTML>
|
||||||
@@ -252,4 +252,18 @@ class ColorTest {
|
|||||||
void darker() {
|
void darker() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void compare() {
|
||||||
|
assertEquals(1.0, Color.RED.compare(Color.RED), 0.0001);
|
||||||
|
assertEquals(1.0, Color.BLUE.compare(Color.BLUE), 0.0001);
|
||||||
|
assertEquals(1.0, Color.WHITE.compare(Color.WHITE), 0.0001);
|
||||||
|
assertEquals(1.0, Color.BLACK.compare(Color.BLACK), 0.0001);
|
||||||
|
|
||||||
|
|
||||||
|
assertEquals(0.0, Color.BLACK.compare(Color.WHITE), 0.0001);
|
||||||
|
assertEquals(0.0, Color.WHITE.compare(Color.BLACK), 0.0001);
|
||||||
|
|
||||||
|
assertEquals(0.5, Color.GRAY.compare(Color.BLACK), 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
51
src/test/java/schule/ngb/zm/Testmaschine.java
Normal file
51
src/test/java/schule/ngb/zm/Testmaschine.java
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package schule.ngb.zm;
|
||||||
|
|
||||||
|
|
||||||
|
import schule.ngb.zm.layers.DrawingLayer;
|
||||||
|
import schule.ngb.zm.util.Log;
|
||||||
|
|
||||||
|
public class Testmaschine extends Zeichenmaschine {
|
||||||
|
|
||||||
|
static {
|
||||||
|
Log.enableGlobalDebugging();
|
||||||
|
}
|
||||||
|
|
||||||
|
private DrawingLayer gridLayer;
|
||||||
|
|
||||||
|
public Testmaschine() {
|
||||||
|
this(400, 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Testmaschine( int width, int height ) {
|
||||||
|
super(width, height, "Testmaschine", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void settings() {
|
||||||
|
gridLayer = new DrawingLayer(getWidth(), getHeight());
|
||||||
|
this.getCanvas().addLayer(1, gridLayer);
|
||||||
|
setGrid(50, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGrid( int majorGrid, int minorGrid ) {
|
||||||
|
gridLayer.clear();
|
||||||
|
|
||||||
|
gridLayer.clear(LIGHTGRAY);
|
||||||
|
gridLayer.setStrokeColor(LIGHTGRAY.darker(20));
|
||||||
|
for( int i = 0; i < getWidth(); i += minorGrid ) {
|
||||||
|
gridLayer.line(i, 0, i, gridLayer.getHeight());
|
||||||
|
}
|
||||||
|
for( int i = 0; i < getHeight(); i += minorGrid ) {
|
||||||
|
gridLayer.line(0, i, gridLayer.getWidth(), i);
|
||||||
|
}
|
||||||
|
|
||||||
|
gridLayer.setStrokeColor(LIGHTGRAY.darker(50));
|
||||||
|
for( int i = 0; i < getWidth(); i += majorGrid ) {
|
||||||
|
gridLayer.line(i, 0, i, gridLayer.getHeight());
|
||||||
|
}
|
||||||
|
for( int i = 0; i < getHeight(); i += majorGrid ) {
|
||||||
|
gridLayer.line(0, i, gridLayer.getWidth(), i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
71
src/test/java/schule/ngb/zm/TestmaschineTest.java
Normal file
71
src/test/java/schule/ngb/zm/TestmaschineTest.java
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package schule.ngb.zm;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import schule.ngb.zm.layers.DrawingLayer;
|
||||||
|
import schule.ngb.zm.util.io.ImageLoader;
|
||||||
|
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static schule.ngb.zm.util.test.ImageAssertions.assertEquals;
|
||||||
|
import static schule.ngb.zm.util.test.ImageAssertions.setSaveDiffImageOnFail;
|
||||||
|
|
||||||
|
public class TestmaschineTest {
|
||||||
|
|
||||||
|
private static Testmaschine tm;
|
||||||
|
|
||||||
|
private static DrawingLayer drawing;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void beforeAll() {
|
||||||
|
setSaveDiffImageOnFail(true);
|
||||||
|
|
||||||
|
tm = new Testmaschine();
|
||||||
|
drawing = tm.getDrawingLayer();
|
||||||
|
assertNotNull(drawing);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
static void afterAll() {
|
||||||
|
tm.exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
drawing.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSaveDiffImage() {
|
||||||
|
drawing.noStroke();
|
||||||
|
drawing.setAnchor(Constants.NORTHWEST);
|
||||||
|
drawing.setFillColor(Constants.BLUE);
|
||||||
|
drawing.rect(0, 0, 400, 400);
|
||||||
|
drawing.setFillColor(Constants.RED);
|
||||||
|
drawing.rect(100, 100, 200, 200);
|
||||||
|
|
||||||
|
BufferedImage img1 = ImageLoader.createImage(400, 400);
|
||||||
|
Graphics2D graphics = img1.createGraphics();
|
||||||
|
|
||||||
|
graphics.setColor(Constants.BLUE.getJavaColor());
|
||||||
|
graphics.fillRect(0, 0, 400, 400);
|
||||||
|
graphics.setColor(Constants.RED.getJavaColor());
|
||||||
|
graphics.fillRect(100, 100, 200, 200);
|
||||||
|
|
||||||
|
assertEquals(drawing.buffer, drawing.buffer);
|
||||||
|
assertEquals(ImageLoader.copyImage(drawing.buffer), drawing.buffer);
|
||||||
|
assertEquals(img1, drawing.buffer);
|
||||||
|
assertEquals(img1, tm.getImage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGrid() {
|
||||||
|
// tm.setGrid(50, 10);
|
||||||
|
tm.delay(2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
96
src/test/java/schule/ngb/zm/anim/AnimationGroupsTest.java
Normal file
96
src/test/java/schule/ngb/zm/anim/AnimationGroupsTest.java
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
package schule.ngb.zm.anim;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import schule.ngb.zm.Color;
|
||||||
|
import schule.ngb.zm.Testmaschine;
|
||||||
|
import schule.ngb.zm.Zeichenmaschine;
|
||||||
|
import schule.ngb.zm.layers.ShapesLayer;
|
||||||
|
import schule.ngb.zm.shapes.Circle;
|
||||||
|
import schule.ngb.zm.shapes.Shape;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
|
||||||
|
class AnimationGroupsTest {
|
||||||
|
|
||||||
|
private static Testmaschine zm;
|
||||||
|
|
||||||
|
private static ShapesLayer shapes;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void beforeAll() {
|
||||||
|
zm = new Testmaschine();
|
||||||
|
shapes = zm.getShapesLayer();
|
||||||
|
assertNotNull(shapes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
static void afterAll() {
|
||||||
|
zm.exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
shapes.removeAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void animationGroup() {
|
||||||
|
Shape s = new Circle(0, 0, 10);
|
||||||
|
shapes.add(s);
|
||||||
|
|
||||||
|
Animation<Shape> anims = new AnimationGroup<>(
|
||||||
|
500,
|
||||||
|
Arrays.asList(
|
||||||
|
new MoveAnimation(s, 200, 200, 2000, Easing.DEFAULT_EASING),
|
||||||
|
new FillAnimation(s, Color.GREEN, 1000, Easing.sineIn())
|
||||||
|
)
|
||||||
|
);
|
||||||
|
Animations.playAndWait(anims);
|
||||||
|
assertEquals(200, s.getX());
|
||||||
|
assertEquals(200, s.getY());
|
||||||
|
assertEquals(Color.GREEN, s.getFillColor());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void animationSequence() {
|
||||||
|
Shape s = new Circle(0, 0, 10);
|
||||||
|
shapes.add(s);
|
||||||
|
|
||||||
|
Animation<Shape> anims = new AnimationSequence<>(
|
||||||
|
Arrays.asList(
|
||||||
|
new CircleAnimation(s, 200, 0, 90, false, 1000, Easing::rushIn),
|
||||||
|
new CircleAnimation(s, 200, 400, 90, 1000, Easing::rushOut),
|
||||||
|
new CircleAnimation(s, 200, 400, 90, false, 1000, Easing::rushIn),
|
||||||
|
new CircleAnimation(s, 200, 0, 90, 1000, Easing::rushOut)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
Animations.playAndWait(anims);
|
||||||
|
assertEquals(0, s.getX());
|
||||||
|
assertEquals(0, s.getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void animationSequenceContinous() {
|
||||||
|
Shape s = new Circle(0, 0, 10);
|
||||||
|
shapes.add(s);
|
||||||
|
|
||||||
|
Animation<Shape> anims = new ContinousAnimation<>(new AnimationSequence<>(
|
||||||
|
Arrays.asList(
|
||||||
|
new CircleAnimation(s, 200, 0, 90, false, 1000, Easing::rushIn),
|
||||||
|
new CircleAnimation(s, 200, 400, 90, 1000, Easing::rushOut),
|
||||||
|
new CircleAnimation(s, 200, 400, 90, false, 1000, Easing::rushIn),
|
||||||
|
new CircleAnimation(s, 200, 0, 90, 1000, Easing::rushOut)
|
||||||
|
)
|
||||||
|
), false);
|
||||||
|
Animations.playAndWait(anims);
|
||||||
|
zm.delay(8000);
|
||||||
|
anims.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
75
src/test/java/schule/ngb/zm/anim/AnimationTest.java
Normal file
75
src/test/java/schule/ngb/zm/anim/AnimationTest.java
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
package schule.ngb.zm.anim;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import schule.ngb.zm.Testmaschine;
|
||||||
|
import schule.ngb.zm.layers.ShapesLayer;
|
||||||
|
import schule.ngb.zm.shapes.Circle;
|
||||||
|
import schule.ngb.zm.shapes.Shape;
|
||||||
|
import schule.ngb.zm.util.test.TestEnv;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
public class AnimationTest {
|
||||||
|
|
||||||
|
private static Testmaschine zm;
|
||||||
|
|
||||||
|
private static ShapesLayer shapes;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void beforeAll() {
|
||||||
|
zm = new Testmaschine();
|
||||||
|
shapes = zm.getShapesLayer();
|
||||||
|
assertNotNull(shapes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
static void afterAll() {
|
||||||
|
zm.exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
shapes.removeAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void circleAnimation() {
|
||||||
|
Shape s = new Circle(zm.getWidth()/4.0, zm.getHeight()/2.0, 10);
|
||||||
|
shapes.add(s);
|
||||||
|
|
||||||
|
CircleAnimation anim = new CircleAnimation(s, zm.getWidth()/2.0, zm.getHeight()/2.0, 360, true, 3000, Easing::linear);
|
||||||
|
Animations.playAndWait(anim);
|
||||||
|
assertEquals(zm.getWidth()/4.0, s.getX());
|
||||||
|
assertEquals(zm.getHeight()/2.0, s.getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void fadeAnimation() {
|
||||||
|
Shape s = new Circle(zm.getWidth()/4.0, zm.getHeight()/2.0, 10);
|
||||||
|
s.setFillColor(s.getFillColor(), 0);
|
||||||
|
s.setStrokeColor(s.getStrokeColor(), 0);
|
||||||
|
shapes.add(s);
|
||||||
|
|
||||||
|
Animation<Shape> anim = new FadeAnimation(s, 255, 1000);
|
||||||
|
Animations.playAndWait(anim);
|
||||||
|
assertEquals(s.getFillColor().getAlpha(), 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void continousAnimation() {
|
||||||
|
Shape s = new Circle(zm.getWidth()/4, zm.getHeight()/2, 10);
|
||||||
|
shapes.add(s);
|
||||||
|
|
||||||
|
ContinousAnimation anim = new ContinousAnimation(
|
||||||
|
new CircleAnimation(s, zm.getWidth()/2, zm.getHeight()/2, 360, true, 1000, Easing::linear)
|
||||||
|
);
|
||||||
|
Animations.play(anim);
|
||||||
|
zm.delay(3000);
|
||||||
|
anim.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
30
src/test/java/schule/ngb/zm/layers/DrawingLayerTest.java
Normal file
30
src/test/java/schule/ngb/zm/layers/DrawingLayerTest.java
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package schule.ngb.zm.layers;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import schule.ngb.zm.Constants;
|
||||||
|
import schule.ngb.zm.Zeichenmaschine;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class DrawingLayerTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void imageRotateAndScale() {
|
||||||
|
Zeichenmaschine zm = new Zeichenmaschine();
|
||||||
|
zm.getDrawingLayer().imageRotateAndScale(
|
||||||
|
"WitchCraftIcons_122_t.PNG",
|
||||||
|
50, 100,
|
||||||
|
90,
|
||||||
|
300, 200,
|
||||||
|
Constants.NORTHWEST
|
||||||
|
);
|
||||||
|
zm.redraw();
|
||||||
|
|
||||||
|
try {
|
||||||
|
Thread.sleep(4000);
|
||||||
|
} catch( InterruptedException e ) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -10,6 +10,11 @@ import java.awt.Shape;
|
|||||||
import java.awt.Stroke;
|
import java.awt.Stroke;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Eine Ebene, die {@link java.awt.Shape} Objekte zeichnet.
|
||||||
|
*
|
||||||
|
* Die Ebene ist für Tests der Kompatibilität mit Java-AWT gedacht.
|
||||||
|
*/
|
||||||
@SuppressWarnings( "unused" )
|
@SuppressWarnings( "unused" )
|
||||||
public final class Shape2DLayer extends Layer {
|
public final class Shape2DLayer extends Layer {
|
||||||
|
|
||||||
158
src/test/java/schule/ngb/zm/particles/ParticleExample.java
Normal file
158
src/test/java/schule/ngb/zm/particles/ParticleExample.java
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
package schule.ngb.zm.particles;
|
||||||
|
|
||||||
|
import schule.ngb.zm.*;
|
||||||
|
import schule.ngb.zm.layers.DrawableLayer;
|
||||||
|
import schule.ngb.zm.layers.DrawingLayer;
|
||||||
|
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
|
||||||
|
public class ParticleExample extends Testmaschine {
|
||||||
|
|
||||||
|
public static void main( String[] args ) {
|
||||||
|
new ParticleExample();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParticleExample() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
ParticleEmitter pe1, pe2, pe3;
|
||||||
|
|
||||||
|
Rocket r;
|
||||||
|
|
||||||
|
public void setup() {
|
||||||
|
getLayer(DrawingLayer.class).hide();
|
||||||
|
background.setColor(0);
|
||||||
|
drawing.noStroke();
|
||||||
|
drawing.setFillColor(WHITE);
|
||||||
|
for( int i = 0; i < 1000; i++ ) {
|
||||||
|
drawing.point(random(0, canvasWidth), random(0, canvasHeight));
|
||||||
|
}
|
||||||
|
|
||||||
|
pe1 = new ParticleEmitter(
|
||||||
|
100, 100, 50, 5,
|
||||||
|
// new BasicParticleFactory(PINK, BLUE)
|
||||||
|
new GenericParticleFactory<Particle>(() -> {
|
||||||
|
return new Particle() {
|
||||||
|
@Override
|
||||||
|
public void draw( Graphics2D graphics ) {
|
||||||
|
graphics.setColor(Color.MAGENTA.getJavaColor());
|
||||||
|
graphics.rotate(Constants.radians(45), (int) position.x, (int) position.y);
|
||||||
|
graphics.drawRect((int) position.x - 3, (int) position.y - 3, 6, 6);
|
||||||
|
graphics.rotate(Constants.radians(-45), (int) position.x, (int) position.y);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
pe1.randomness = .2;
|
||||||
|
pe1.angle = 45;
|
||||||
|
pe1.strength = 200;
|
||||||
|
|
||||||
|
|
||||||
|
pe2 = new ParticleEmitter(
|
||||||
|
300, 300, 50, 10,
|
||||||
|
new GenericParticleFactory(() -> new StarParticle(RED, new Color(BLUE, 55)))
|
||||||
|
//new GenericParticleFactory(StarParticle.class, RED, new Color(BLUE, 55))
|
||||||
|
);
|
||||||
|
pe2.direction = NORTH.asVector().scale(100);
|
||||||
|
pe2.randomness = .8;
|
||||||
|
pe2.angle = 90;
|
||||||
|
pe2.strength = 200;
|
||||||
|
|
||||||
|
|
||||||
|
pe3 = new ParticleEmitter(
|
||||||
|
100, 400, 20, 8,
|
||||||
|
new BasicParticleFactory(YELLOW, RED)
|
||||||
|
);
|
||||||
|
pe3.direction = SOUTH.asVector();
|
||||||
|
pe3.randomness = .33;
|
||||||
|
pe3.angle = 30;
|
||||||
|
|
||||||
|
DrawableLayer drawables = new DrawableLayer();
|
||||||
|
addLayer(drawables);
|
||||||
|
drawables.add(pe1, pe2, pe3);
|
||||||
|
|
||||||
|
pe1.start();
|
||||||
|
pe2.start();
|
||||||
|
pe3.start();
|
||||||
|
|
||||||
|
r = new Rocket(200, 400);
|
||||||
|
drawables.add(r);
|
||||||
|
r.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update( double delta ) {
|
||||||
|
pe1.update(delta);
|
||||||
|
pe2.update(delta);
|
||||||
|
pe3.update(delta);
|
||||||
|
pe3.position.add(NORTH.asVector().scale(Constants.map(runtime, 0, 1000, 0, 10) * delta));
|
||||||
|
|
||||||
|
if( r.isActive() ) {
|
||||||
|
r.update(delta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Rocket extends PhysicsObject implements Drawable {
|
||||||
|
|
||||||
|
ParticleEmitter trail;
|
||||||
|
|
||||||
|
private boolean starting = false;
|
||||||
|
|
||||||
|
private double acc = 4;
|
||||||
|
|
||||||
|
public Rocket( double x, double y ) {
|
||||||
|
super(new Vector(x, y));
|
||||||
|
trail = new ParticleEmitter(
|
||||||
|
x, y, 30, 6,
|
||||||
|
new BasicParticleFactory(YELLOW, RED)
|
||||||
|
);
|
||||||
|
trail.direction = SOUTH.asVector();
|
||||||
|
trail.randomness = .33;
|
||||||
|
trail.angle = 30;
|
||||||
|
trail.position = this.position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start() {
|
||||||
|
starting = true;
|
||||||
|
trail.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isActive() {
|
||||||
|
return starting;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isVisible() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update( double delta ) {
|
||||||
|
super.update(delta);
|
||||||
|
if( this.acceleration.lengthSq() < acc * acc ) {
|
||||||
|
this.accelerate(NORTHWEST.asVector().scale(acc));
|
||||||
|
}
|
||||||
|
|
||||||
|
trail.update(delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw( Graphics2D graphics ) {
|
||||||
|
graphics.rotate(-Constants.radians(velocity.angle() + 180), position.getIntX(), position.getIntY());
|
||||||
|
trail.draw(graphics);
|
||||||
|
|
||||||
|
graphics.setColor(WHITE.getJavaColor());
|
||||||
|
graphics.fillRect(position.getIntX() - 6, position.getIntY() - 32, 12, 32);
|
||||||
|
graphics.fillPolygon(
|
||||||
|
new int[]{position.getIntX() - 6, position.getIntX(), position.getIntX() + 6},
|
||||||
|
new int[]{position.getIntY() - 32, position.getIntY() - 40, position.getIntY() - 32},
|
||||||
|
3
|
||||||
|
);
|
||||||
|
graphics.rotate(Constants.radians(velocity.angle() + 180), position.getIntX(), position.getIntY());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
31
src/test/java/schule/ngb/zm/util/FakerTest.java
Normal file
31
src/test/java/schule/ngb/zm/util/FakerTest.java
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package schule.ngb.zm.util;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
|
||||||
|
import schule.ngb.zm.util.abi.List;
|
||||||
|
import schule.ngb.zm.util.abi.Queue;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class FakerTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void fakeIntegers() {
|
||||||
|
LinkedList<Integer> l = new LinkedList<>();
|
||||||
|
Faker.fakeIntegers(100, 0, 100, l::add);
|
||||||
|
assertEquals(100, l.size());
|
||||||
|
|
||||||
|
LinkedList<Integer> intList = Faker.fakeIntegers(100, 0, 100, LinkedList::new, (list, i) -> list.add(i));
|
||||||
|
assertEquals(100, intList.size());
|
||||||
|
|
||||||
|
List<Integer> abiList = Faker.fakeIntegers(100, 0, 100, List::new, (list, i) -> list.append(i));
|
||||||
|
assertFalse(abiList.isEmpty());
|
||||||
|
|
||||||
|
Queue<Integer> abiQueue = new Queue<>();
|
||||||
|
Faker.fakeIntegers(100, 0, 100, abiQueue::enqueue);
|
||||||
|
assertFalse(abiQueue.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -72,10 +72,10 @@ class FileLoaderTest {
|
|||||||
{2.1,2.2,2.3},
|
{2.1,2.2,2.3},
|
||||||
{3.1,3.2,3.3}
|
{3.1,3.2,3.3}
|
||||||
};
|
};
|
||||||
csv = FileLoader.loadValues("data_comma.csv", ',', true);
|
csv = FileLoader.loadValues("data_comma.csv", ",", true);
|
||||||
assertArrayEquals(data, csv);
|
assertArrayEquals(data, csv);
|
||||||
|
|
||||||
csv = FileLoader.loadValues("data_semicolon_latin.csv", ';', true, FileLoader.ISO_8859_1);
|
csv = FileLoader.loadValues("data_semicolon_latin.csv", ";", true, FileLoader.ISO_8859_1);
|
||||||
assertArrayEquals(data, csv);
|
assertArrayEquals(data, csv);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,14 +11,14 @@ class ValidatorTest {
|
|||||||
StringBuilder sb = new StringBuilder("Message");
|
StringBuilder sb = new StringBuilder("Message");
|
||||||
|
|
||||||
Object o1 = new Object();
|
Object o1 = new Object();
|
||||||
assertEquals(o1, Validator.requireNotNull(o1));
|
assertEquals(o1, Validator.requireNotNull(o1, "content"));
|
||||||
assertEquals(o1, Validator.requireNotNull(o1, "Message"));
|
assertEquals(o1, Validator.requireNotNull(o1, "content", "Message"));
|
||||||
assertEquals(o1, Validator.requireNotNull(o1, sb));
|
assertEquals(o1, Validator.requireNotNull(o1, "content", sb));
|
||||||
assertEquals(o1, Validator.requireNotNull(o1, ()->"Message"));
|
assertEquals(o1, Validator.requireNotNull(o1, ()->"Message"));
|
||||||
|
|
||||||
String o2 = null;
|
String o2 = null;
|
||||||
assertThrowsExactly(NullPointerException.class, () -> Validator.requireNotNull(o2));
|
assertThrowsExactly(NullPointerException.class, () -> Validator.requireNotNull(o2, "content"));
|
||||||
assertThrowsExactly(NullPointerException.class, () -> Validator.requireNotNull(o2, "Message"));
|
assertThrowsExactly(NullPointerException.class, () -> Validator.requireNotNull(o2, "content", "Message"));
|
||||||
assertThrowsExactly(NullPointerException.class, () -> Validator.requireNotNull(o2, ()->"Message"));
|
assertThrowsExactly(NullPointerException.class, () -> Validator.requireNotNull(o2, ()->"Message"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,14 +27,14 @@ class ValidatorTest {
|
|||||||
StringBuilder sb = new StringBuilder("Message");
|
StringBuilder sb = new StringBuilder("Message");
|
||||||
|
|
||||||
String s1 = "Content";
|
String s1 = "Content";
|
||||||
assertEquals(s1, Validator.requireNotEmpty(s1));
|
assertEquals(s1, Validator.requireNotEmpty(s1, "content"));
|
||||||
assertEquals(s1, Validator.requireNotEmpty(s1, "Message"));
|
assertEquals(s1, Validator.requireNotEmpty(s1, "content", "Message"));
|
||||||
assertEquals(s1, Validator.requireNotEmpty(s1, sb));
|
assertEquals(s1, Validator.requireNotEmpty(s1, "content", sb));
|
||||||
assertEquals(s1, Validator.requireNotEmpty(s1, ()->"Message"));
|
assertEquals(s1, Validator.requireNotEmpty(s1, ()->"Message"));
|
||||||
|
|
||||||
String s2 = "";
|
String s2 = "";
|
||||||
assertThrowsExactly(IllegalArgumentException.class, () -> Validator.requireNotEmpty(s2));
|
assertThrowsExactly(IllegalArgumentException.class, () -> Validator.requireNotEmpty(s2, "content"));
|
||||||
assertThrowsExactly(IllegalArgumentException.class, () -> Validator.requireNotEmpty(s2, "Message"));
|
assertThrowsExactly(IllegalArgumentException.class, () -> Validator.requireNotEmpty(s2, "content", "Message"));
|
||||||
assertThrowsExactly(IllegalArgumentException.class, () -> Validator.requireNotEmpty(s2, ()->"Message"));
|
assertThrowsExactly(IllegalArgumentException.class, () -> Validator.requireNotEmpty(s2, ()->"Message"));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
262
src/test/java/schule/ngb/zm/util/abi/BinarySearchTree.java
Executable file
262
src/test/java/schule/ngb/zm/util/abi/BinarySearchTree.java
Executable file
@@ -0,0 +1,262 @@
|
|||||||
|
package schule.ngb.zm.util.abi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Materialien zu den zentralen NRW-Abiturpruefungen im Fach Informatik ab 2018
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Generische Klasse BinarySearchTree<ContentType>
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Mithilfe der generischen Klasse BinarySearchTree koennen beliebig viele
|
||||||
|
* Objekte in einem Binaerbaum (binaerer Suchbaum) entsprechend einer
|
||||||
|
* Ordnungsrelation verwaltet werden. <br />
|
||||||
|
* Ein Objekt der Klasse stellt entweder einen leeren binaeren Suchbaum dar oder
|
||||||
|
* verwaltet ein Inhaltsobjekt sowie einen linken und einen rechten Teilbaum,
|
||||||
|
* die ebenfalls Objekte der Klasse BinarySearchTree sind.<br />
|
||||||
|
* Die Klasse der Objekte, die in dem Suchbaum verwaltet werden sollen, muss
|
||||||
|
* das generische Interface ComparableContent implementieren. Dabei muss durch
|
||||||
|
* Ueberschreiben der drei Vergleichsmethoden isLess, isEqual, isGreater (s.
|
||||||
|
* Dokumentation des Interfaces) eine eindeutige Ordnungsrelation festgelegt
|
||||||
|
* sein. <br />
|
||||||
|
* Alle Objekte im linken Teilbaum sind kleiner als das Inhaltsobjekt des
|
||||||
|
* binaeren Suchbaums. Alle Objekte im rechten Teilbaum sind groesser als das
|
||||||
|
* Inhaltsobjekt des binaeren Suchbaums. Diese Bedingung gilt (rekursiv) auch in
|
||||||
|
* beiden Teilbaeumen. <br />
|
||||||
|
* Hinweis: In dieser Version wird die Klasse BinaryTree nicht benutzt.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author Qualitaets- und UnterstuetzungsAgentur - Landesinstitut fuer Schule
|
||||||
|
* @version Generisch_03 2017-11-28
|
||||||
|
*/
|
||||||
|
public class BinarySearchTree<ContentType extends ComparableContent<ContentType>> {
|
||||||
|
|
||||||
|
/* --------- Anfang der privaten inneren Klasse -------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Durch diese innere Klasse kann man dafuer sorgen, dass ein leerer Baum
|
||||||
|
* null ist, ein nicht-leerer Baum jedoch immer eine nicht-null-Wurzel sowie
|
||||||
|
* nicht-null-Teilbaeume hat.
|
||||||
|
*/
|
||||||
|
private class BSTNode<CT extends ComparableContent<CT>> {
|
||||||
|
|
||||||
|
private CT content;
|
||||||
|
private BinarySearchTree<CT> left, right;
|
||||||
|
|
||||||
|
public BSTNode(CT pContent) {
|
||||||
|
// Der Knoten hat einen linken und rechten Teilbaum, die
|
||||||
|
// beide von null verschieden sind. Also hat ein Blatt immer zwei
|
||||||
|
// leere Teilbaeume unter sich.
|
||||||
|
this.content = pContent;
|
||||||
|
left = new BinarySearchTree<CT>();
|
||||||
|
right = new BinarySearchTree<CT>();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------- Ende der privaten inneren Klasse -------------- */
|
||||||
|
|
||||||
|
private BSTNode<ContentType> node;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der Konstruktor erzeugt einen leeren Suchbaum.
|
||||||
|
*/
|
||||||
|
public BinarySearchTree() {
|
||||||
|
this.node = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Diese Anfrage liefert den Wahrheitswert true, wenn der Suchbaum leer ist,
|
||||||
|
* sonst liefert sie den Wert false.
|
||||||
|
*
|
||||||
|
* @return true, wenn der binaere Suchbaum leer ist, sonst false
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return this.node == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Falls der Parameter null ist, geschieht nichts.<br />
|
||||||
|
* Falls ein bezueglich der verwendeten Vergleichsmethode isEqual mit
|
||||||
|
* pContent uebereinstimmendes Objekt im geordneten binaeren Suchbau
|
||||||
|
* enthalten ist, passiert nichts. <br />
|
||||||
|
* Achtung: hier wird davon ausgegangen, dass isEqual genau dann true
|
||||||
|
* liefert, wenn isLess und isGreater false liefern. <br />
|
||||||
|
* Andernfalls (isLess oder isGreater) wird das Objekt pContent entsprechend
|
||||||
|
* der vorgegebenen Ordnungsrelation in den BinarySearchTree eingeordnet.
|
||||||
|
*
|
||||||
|
* @param pContent
|
||||||
|
* einzufuegendes Objekt vom Typ ContentType
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public void insert(ContentType pContent) {
|
||||||
|
if (pContent != null) {
|
||||||
|
if (isEmpty()) {
|
||||||
|
this.node = new BSTNode<ContentType>(pContent);
|
||||||
|
} else if (pContent.isLess(this.node.content)) {
|
||||||
|
this.node.left.insert(pContent);
|
||||||
|
} else if(pContent.isGreater(this.node.content)) {
|
||||||
|
this.node.right.insert(pContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Diese Anfrage liefert den linken Teilbaum des binaeren Suchbaumes. <br />
|
||||||
|
* Wenn er leer ist, wird null zurueckgegeben.
|
||||||
|
*
|
||||||
|
* @return den linken Teilbaum (Objekt vom Typ BinarySearchTree<ContentType>)
|
||||||
|
* bzw. null, wenn der Suchbaum leer ist
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public BinarySearchTree<ContentType> getLeftTree() {
|
||||||
|
if (this.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return this.node.left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Diese Anfrage liefert das Inhaltsobjekt des Suchbaumes. Wenn der Suchbaum
|
||||||
|
* leer ist, wird null zurueckgegeben.
|
||||||
|
*
|
||||||
|
* @return das Inhaltsobjekt vom Typ ContentType bzw. null, wenn der aktuelle
|
||||||
|
* Suchbaum leer ist
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public ContentType getContent() {
|
||||||
|
if (this.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return this.node.content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Diese Anfrage liefert den rechten Teilbaum des binaeren Suchbaumes. <br />
|
||||||
|
* Wenn er leer ist, wird null zurueckgegeben.
|
||||||
|
*
|
||||||
|
* @return den rechten Teilbaum (Objekt vom Typ BinarySearchTree<ContentType>)
|
||||||
|
* bzw. null, wenn der aktuelle Suchbaum leer ist
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public BinarySearchTree<ContentType> getRightTree() {
|
||||||
|
if (this.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return this.node.right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Falls ein bezueglich der verwendeten Vergleichsmethode mit
|
||||||
|
* pContent uebereinstimmendes Objekt im binaeren Suchbaum enthalten
|
||||||
|
* ist, wird dieses entfernt. Falls der Parameter null ist, aendert sich
|
||||||
|
* nichts.
|
||||||
|
*
|
||||||
|
* @param pContent
|
||||||
|
* zu entfernendes Objekt vom Typ ContentType
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public void remove(ContentType pContent) {
|
||||||
|
if (isEmpty() || pContent == null ) {
|
||||||
|
// Abbrechen, da kein Element zum entfernen vorhanden ist.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pContent.isLess(node.content)) {
|
||||||
|
// Element ist im linken Teilbaum zu loeschen.
|
||||||
|
node.left.remove(pContent);
|
||||||
|
} else if (pContent.isGreater(node.content)) {
|
||||||
|
// Element ist im rechten Teilbaum zu loeschen.
|
||||||
|
node.right.remove(pContent);
|
||||||
|
} else {
|
||||||
|
// Element ist gefunden.
|
||||||
|
if (node.left.isEmpty()) {
|
||||||
|
if (node.right.isEmpty()) {
|
||||||
|
// Es gibt keinen Nachfolger.
|
||||||
|
node = null;
|
||||||
|
} else {
|
||||||
|
// Es gibt nur rechts einen Nachfolger.
|
||||||
|
node = getNodeOfRightSuccessor();
|
||||||
|
}
|
||||||
|
} else if (node.right.isEmpty()) {
|
||||||
|
// Es gibt nur links einen Nachfolger.
|
||||||
|
node = getNodeOfLeftSuccessor();
|
||||||
|
} else {
|
||||||
|
// Es gibt links und rechts einen Nachfolger.
|
||||||
|
if (getNodeOfRightSuccessor().left.isEmpty()) {
|
||||||
|
// Der rechte Nachfolger hat keinen linken Nachfolger.
|
||||||
|
node.content = getNodeOfRightSuccessor().content;
|
||||||
|
node.right = getNodeOfRightSuccessor().right;
|
||||||
|
} else {
|
||||||
|
BinarySearchTree<ContentType> previous = node.right
|
||||||
|
.ancestorOfSmallRight();
|
||||||
|
BinarySearchTree<ContentType> smallest = previous.node.left;
|
||||||
|
this.node.content = smallest.node.content;
|
||||||
|
previous.remove(smallest.node.content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Falls ein bezueglich der verwendeten Vergleichsmethode isEqual mit
|
||||||
|
* pContent uebereinstimmendes Objekt im binaeren Suchbaum enthalten ist,
|
||||||
|
* liefert die Anfrage dieses, ansonsten wird null zurueckgegeben. <br />
|
||||||
|
* Falls der Parameter null ist, wird null zurueckgegeben.
|
||||||
|
*
|
||||||
|
* @param pContent
|
||||||
|
* zu suchendes Objekt vom Typ ContentType
|
||||||
|
* @return das gefundene Objekt vom Typ ContentType, bei erfolgloser Suche null
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public ContentType search(ContentType pContent) {
|
||||||
|
if (this.isEmpty() || pContent == null) {
|
||||||
|
// Abbrechen, da es kein Element zu suchen gibt.
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
ContentType content = this.getContent();
|
||||||
|
if (pContent.isLess(content)) {
|
||||||
|
// Element wird im linken Teilbaum gesucht.
|
||||||
|
return this.getLeftTree().search(pContent);
|
||||||
|
} else if (pContent.isGreater(content)) {
|
||||||
|
// Element wird im rechten Teilbaum gesucht.
|
||||||
|
return this.getRightTree().search(pContent);
|
||||||
|
} else if (pContent.isEqual(content)) {
|
||||||
|
// Element wurde gefunden.
|
||||||
|
return content;
|
||||||
|
} else {
|
||||||
|
// Dieser Fall sollte nicht auftreten.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------- Weitere private Methoden -------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Methode liefert denjenigen Baum, dessen linker Nachfolger keinen linken
|
||||||
|
* Nachfolger mehr hat. Es ist also spaeter moeglich, in einem Baum im
|
||||||
|
* rechten Nachfolger den Vorgaenger des linkesten Nachfolgers zu finden.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private BinarySearchTree<ContentType> ancestorOfSmallRight() {
|
||||||
|
if (getNodeOfLeftSuccessor().left.isEmpty()) {
|
||||||
|
return this;
|
||||||
|
} else {
|
||||||
|
return node.left.ancestorOfSmallRight();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BSTNode<ContentType> getNodeOfLeftSuccessor() {
|
||||||
|
return node.left.node;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BSTNode<ContentType> getNodeOfRightSuccessor() {
|
||||||
|
return node.right.node;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
212
src/test/java/schule/ngb/zm/util/abi/BinaryTree.java
Executable file
212
src/test/java/schule/ngb/zm/util/abi/BinaryTree.java
Executable file
@@ -0,0 +1,212 @@
|
|||||||
|
package schule.ngb.zm.util.abi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Materialien zu den zentralen NRW-Abiturpruefungen im Fach Informatik ab 2018
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Generische Klasse BinaryTree<ContentType>
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Mithilfe der generischen Klasse BinaryTree koennen beliebig viele
|
||||||
|
* Inhaltsobjekte vom Typ ContentType in einem Binaerbaum verwaltet werden. Ein
|
||||||
|
* Objekt der Klasse stellt entweder einen leeren Baum dar oder verwaltet ein
|
||||||
|
* Inhaltsobjekt sowie einen linken und einen rechten Teilbaum, die ebenfalls
|
||||||
|
* Objekte der generischen Klasse BinaryTree sind.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author Qualitaets- und UnterstuetzungsAgentur - Landesinstitut fuer Schule
|
||||||
|
* @version Generisch_03 2014-03-01
|
||||||
|
*/
|
||||||
|
public class BinaryTree<ContentType> {
|
||||||
|
|
||||||
|
/* --------- Anfang der privaten inneren Klasse -------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Durch diese innere Klasse kann man dafuer sorgen, dass ein leerer Baum
|
||||||
|
* null ist, ein nicht-leerer Baum jedoch immer eine nicht-null-Wurzel sowie
|
||||||
|
* nicht-null-Teilbaeume, ggf. leere Teilbaeume hat.
|
||||||
|
*/
|
||||||
|
private class BTNode<CT> {
|
||||||
|
|
||||||
|
private CT content;
|
||||||
|
private BinaryTree<CT> left, right;
|
||||||
|
|
||||||
|
public BTNode(CT pContent) {
|
||||||
|
// Der Knoten hat einen linken und einen rechten Teilbaum, die
|
||||||
|
// beide von null verschieden sind. Also hat ein Blatt immer zwei
|
||||||
|
// leere Teilbaeume unter sich.
|
||||||
|
this.content = pContent;
|
||||||
|
left = new BinaryTree<CT>();
|
||||||
|
right = new BinaryTree<CT>();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------- Ende der privaten inneren Klasse -------------- */
|
||||||
|
|
||||||
|
private BTNode<ContentType> node;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nach dem Aufruf des Konstruktors existiert ein leerer Binaerbaum.
|
||||||
|
*/
|
||||||
|
public BinaryTree() {
|
||||||
|
this.node = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wenn der Parameter pContent ungleich null ist, existiert nach dem Aufruf
|
||||||
|
* des Konstruktors der Binaerbaum und hat pContent als Inhaltsobjekt und
|
||||||
|
* zwei leere Teilbaeume. Falls der Parameter null ist, wird ein leerer
|
||||||
|
* Binaerbaum erzeugt.
|
||||||
|
*
|
||||||
|
* @param pContent
|
||||||
|
* das Inhaltsobjekt des Wurzelknotens vom Typ ContentType
|
||||||
|
*/
|
||||||
|
public BinaryTree(ContentType pContent) {
|
||||||
|
if (pContent != null) {
|
||||||
|
this.node = new BTNode<ContentType>(pContent);
|
||||||
|
} else {
|
||||||
|
this.node = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wenn der Parameter pContent ungleich null ist, wird ein Binaerbaum mit
|
||||||
|
* pContent als Inhalt und den beiden Teilbaeume pLeftTree und pRightTree
|
||||||
|
* erzeugt. Sind pLeftTree oder pRightTree gleich null, wird der
|
||||||
|
* entsprechende Teilbaum als leerer Binaerbaum eingefuegt. So kann es also
|
||||||
|
* nie passieren, dass linke oder rechte Teilbaeume null sind. Wenn der
|
||||||
|
* Parameter pContent gleich null ist, wird ein leerer Binaerbaum erzeugt.
|
||||||
|
*
|
||||||
|
* @param pContent
|
||||||
|
* das Inhaltsobjekt des Wurzelknotens vom Typ ContentType
|
||||||
|
* @param pLeftTree
|
||||||
|
* der linke Teilbaum vom Typ BinaryTree<ContentType>
|
||||||
|
* @param pRightTree
|
||||||
|
* der rechte Teilbaum vom Typ BinaryTree<ContentType>
|
||||||
|
*/
|
||||||
|
public BinaryTree(ContentType pContent, BinaryTree<ContentType> pLeftTree, BinaryTree<ContentType> pRightTree) {
|
||||||
|
if (pContent != null) {
|
||||||
|
this.node = new BTNode<ContentType>(pContent);
|
||||||
|
if (pLeftTree != null) {
|
||||||
|
this.node.left = pLeftTree;
|
||||||
|
} else {
|
||||||
|
this.node.left = new BinaryTree<ContentType>();
|
||||||
|
}
|
||||||
|
if (pRightTree != null) {
|
||||||
|
this.node.right = pRightTree;
|
||||||
|
} else {
|
||||||
|
this.node.right = new BinaryTree<ContentType>();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Da der Inhalt null ist, wird ein leerer BinarySearchTree erzeugt.
|
||||||
|
this.node = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Diese Anfrage liefert den Wahrheitswert true, wenn der Binaerbaum leer
|
||||||
|
* ist, sonst liefert sie den Wert false.
|
||||||
|
*
|
||||||
|
* @return true, wenn der Binaerbaum leer ist, sonst false
|
||||||
|
*/
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return this.node == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wenn pContent null ist, geschieht nichts. <br />
|
||||||
|
* Ansonsten: Wenn der Binaerbaum leer ist, wird der Parameter pContent als
|
||||||
|
* Inhaltsobjekt sowie ein leerer linker und rechter Teilbaum eingefuegt.
|
||||||
|
* Ist der Binaerbaum nicht leer, wird das Inhaltsobjekt durch pContent
|
||||||
|
* ersetzt. Die Teilbaeume werden nicht geaendert.
|
||||||
|
*
|
||||||
|
* @param pContent
|
||||||
|
* neues Inhaltsobjekt vom Typ ContentType
|
||||||
|
*/
|
||||||
|
public void setContent(ContentType pContent) {
|
||||||
|
if (pContent != null) {
|
||||||
|
if (this.isEmpty()) {
|
||||||
|
node = new BTNode<ContentType>(pContent);
|
||||||
|
this.node.left = new BinaryTree<ContentType>();
|
||||||
|
this.node.right = new BinaryTree<ContentType>();
|
||||||
|
}
|
||||||
|
this.node.content = pContent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Diese Anfrage liefert das Inhaltsobjekt des Binaerbaums. Wenn der
|
||||||
|
* Binaerbaum leer ist, wird null zurueckgegeben.
|
||||||
|
*
|
||||||
|
* @return das Inhaltsobjekt der Wurzel vom Typ ContentType bzw. null, wenn
|
||||||
|
* der Binaerbaum leer ist
|
||||||
|
*/
|
||||||
|
public ContentType getContent() {
|
||||||
|
if (this.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return this.node.content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Falls der Parameter null ist, geschieht nichts. Wenn der Binaerbaum leer
|
||||||
|
* ist, wird pTree nicht angehaengt. Andernfalls erhaelt der Binaerbaum den
|
||||||
|
* uebergebenen BinaryTree als linken Teilbaum.
|
||||||
|
*
|
||||||
|
* @param pTree
|
||||||
|
* neuer linker Teilbaum vom Typ BinaryTree<ContentType>
|
||||||
|
*/
|
||||||
|
public void setLeftTree(BinaryTree<ContentType> pTree) {
|
||||||
|
if (!this.isEmpty() && pTree != null) {
|
||||||
|
this.node.left = pTree;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Falls der Parameter null ist, geschieht nichts. Wenn der Binaerbaum leer
|
||||||
|
* ist, wird pTree nicht angehaengt. Andernfalls erhaelt der Binaerbaum den
|
||||||
|
* uebergebenen BinaryTree als rechten Teilbaum.
|
||||||
|
*
|
||||||
|
* @param pTree
|
||||||
|
* neuer linker Teilbaum vom Typ BinaryTree<ContentType>
|
||||||
|
*/
|
||||||
|
public void setRightTree(BinaryTree<ContentType> pTree) {
|
||||||
|
if (!this.isEmpty() && pTree != null) {
|
||||||
|
this.node.right = pTree;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Diese Anfrage liefert den linken Teilbaum des Binaerbaumes. Wenn der
|
||||||
|
* Binaerbaum leer ist, wird null zurueckgegeben.
|
||||||
|
*
|
||||||
|
* @return linker Teilbaum vom Typ BinaryTree<ContentType> oder null, wenn
|
||||||
|
* der aktuelle Binaerbaum leer ist
|
||||||
|
*/
|
||||||
|
public BinaryTree<ContentType> getLeftTree() {
|
||||||
|
if (!this.isEmpty()) {
|
||||||
|
return this.node.left;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Diese Anfrage liefert den rechten Teilbaum des Binaerbaumes. Wenn der
|
||||||
|
* Binaerbaum (this) leer ist, wird null zurueckgegeben.
|
||||||
|
*
|
||||||
|
* @return rechter Teilbaum vom Typ BinaryTree<ContentType> oder null, wenn
|
||||||
|
* der aktuelle Binaerbaum (this) leer ist
|
||||||
|
*/
|
||||||
|
public BinaryTree<ContentType> getRightTree() {
|
||||||
|
if (!this.isEmpty()) {
|
||||||
|
return this.node.right;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
163
src/test/java/schule/ngb/zm/util/abi/Client.java
Executable file
163
src/test/java/schule/ngb/zm/util/abi/Client.java
Executable file
@@ -0,0 +1,163 @@
|
|||||||
|
package schule.ngb.zm.util.abi;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.net.Socket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Materialien zu den zentralen NRW-Abiturpruefungen im Fach Informatik ab 2018
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Klasse Client
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Objekte von Unterklassen der abstrakten Klasse Client ermoeglichen
|
||||||
|
* Netzwerkverbindungen zu einem Server mittels TCP/IP-Protokoll. Nach
|
||||||
|
* Verbindungsaufbau koennen Zeichenketten (Strings) zum Server gesendet und von
|
||||||
|
* diesem empfangen werden, wobei der Nachrichtenempfang nebenlaeufig geschieht.
|
||||||
|
* Zur Vereinfachung finden Nachrichtenversand und -empfang zeilenweise statt,
|
||||||
|
* d. h., beim Senden einer Zeichenkette wird ein Zeilentrenner ergaenzt und beim
|
||||||
|
* Empfang wird dieser entfernt. Jede empfangene Nachricht wird einer
|
||||||
|
* Ereignisbehandlungsmethode uebergeben, die in Unterklassen implementiert werden
|
||||||
|
* muss. Es findet nur eine rudimentaere Fehlerbehandlung statt, so dass z.B.
|
||||||
|
* Verbindungsabbrueche nicht zu einem Programmabbruch fuehren. Eine einmal
|
||||||
|
* unterbrochene oder getrennte Verbindung kann nicht reaktiviert werden.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author Qualitaets- und UnterstuetzungsAgentur - Landesinstitut fuer Schule
|
||||||
|
* @version 30.08.2016
|
||||||
|
*/
|
||||||
|
|
||||||
|
public abstract class Client
|
||||||
|
{
|
||||||
|
private MessageHandler messageHandler;
|
||||||
|
|
||||||
|
private class MessageHandler extends Thread
|
||||||
|
{
|
||||||
|
private SocketWrapper socketWrapper;
|
||||||
|
private boolean active;
|
||||||
|
|
||||||
|
private class SocketWrapper
|
||||||
|
{
|
||||||
|
private Socket socket;
|
||||||
|
private BufferedReader fromServer;
|
||||||
|
private PrintWriter toServer;
|
||||||
|
|
||||||
|
public SocketWrapper(String pServerIP, int pServerPort)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
socket = new Socket(pServerIP, pServerPort);
|
||||||
|
toServer = new PrintWriter(socket.getOutputStream(), true);
|
||||||
|
fromServer = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
socket = null;
|
||||||
|
toServer = null;
|
||||||
|
fromServer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String receive()
|
||||||
|
{
|
||||||
|
if(fromServer != null)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return fromServer.readLine();
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
return(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void send(String pMessage)
|
||||||
|
{
|
||||||
|
if(toServer != null)
|
||||||
|
{
|
||||||
|
toServer.println(pMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close()
|
||||||
|
{
|
||||||
|
if(socket != null)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
socket.close();
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Falls eine Verbindung getrennt werden soll, deren Endpunkt
|
||||||
|
* nicht mehr existiert bzw. ihrerseits bereits beendet worden ist,
|
||||||
|
* geschieht nichts.
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private MessageHandler(String pServerIP, int pServerPort)
|
||||||
|
{
|
||||||
|
socketWrapper = new SocketWrapper(pServerIP, pServerPort);
|
||||||
|
start();
|
||||||
|
if(socketWrapper.socket != null)
|
||||||
|
active = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
String message = null;
|
||||||
|
while (active)
|
||||||
|
{
|
||||||
|
message = socketWrapper.receive();
|
||||||
|
if (message != null)
|
||||||
|
processMessage(message);
|
||||||
|
else
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void send(String pMessage)
|
||||||
|
{
|
||||||
|
if(active)
|
||||||
|
socketWrapper.send(pMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void close()
|
||||||
|
{
|
||||||
|
if(active)
|
||||||
|
{
|
||||||
|
active = false;
|
||||||
|
socketWrapper.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Client(String pServerIP, int pServerPort)
|
||||||
|
{
|
||||||
|
messageHandler = new MessageHandler(pServerIP, pServerPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isConnected()
|
||||||
|
{
|
||||||
|
return(messageHandler.active);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void send(String pMessage)
|
||||||
|
{
|
||||||
|
messageHandler.send(pMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close()
|
||||||
|
{
|
||||||
|
messageHandler.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void processMessage(String pMessage);
|
||||||
|
|
||||||
|
}
|
||||||
62
src/test/java/schule/ngb/zm/util/abi/ComparableContent.java
Executable file
62
src/test/java/schule/ngb/zm/util/abi/ComparableContent.java
Executable file
@@ -0,0 +1,62 @@
|
|||||||
|
package schule.ngb.zm.util.abi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Materialien zu den zentralen NRW-Abiturpruefungen im Fach Informatik ab 2018
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Generisches Interface (Schnittstelle) ComparableContent<ContentType>
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* <p>Das generische Interface ComparableContent<ContentType> legt die Methoden
|
||||||
|
* fest, ueber die Objekte verfuegen muessen, die in einen binaeren Suchbaum
|
||||||
|
* (BinarySearchTree) eingefuegt werden sollen. Die Ordnungsrelation wird in
|
||||||
|
* Klassen, die ComparableContent implementieren durch Ueberschreiben der drei
|
||||||
|
* implizit abstrakten Methoden isGreater, isEqual und isLess festgelegt.
|
||||||
|
* </p>
|
||||||
|
* </p>
|
||||||
|
* @author Qualitaets- und UnterstuetzungsAgentur - Landesinstitut fuer Schule
|
||||||
|
* @version Generisch_02 2014-03-01
|
||||||
|
*/
|
||||||
|
public interface ComparableContent<ContentType> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wenn festgestellt wird, dass das Objekt, von dem die Methode aufgerufen
|
||||||
|
* wird, bzgl. der gewuenschten Ordnungsrelation groesser als das Objekt
|
||||||
|
* pContent ist, wird true geliefert. Sonst wird false geliefert.
|
||||||
|
*
|
||||||
|
* @param pContent
|
||||||
|
* das mit dem aufrufenden Objekt zu vergleichende Objekt vom
|
||||||
|
* Typ ContentType
|
||||||
|
* @return true, wenn das aufrufende Objekt groesser ist als das Objekt
|
||||||
|
* pContent, sonst false
|
||||||
|
*/
|
||||||
|
public boolean isGreater(ContentType pContent);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wenn festgestellt wird, dass das Objekt, von dem die Methode aufgerufen
|
||||||
|
* wird, bzgl. der gewuenschten Ordnungsrelation gleich gross wie das Objekt
|
||||||
|
* pContent ist, wird true geliefert. Sonst wird false geliefert.
|
||||||
|
*
|
||||||
|
* @param pContent
|
||||||
|
* das mit dem aufrufenden Objekt zu vergleichende Objekt vom
|
||||||
|
* Typ ContentType
|
||||||
|
* @return true, wenn das aufrufende Objekt gleich gross ist wie das Objekt
|
||||||
|
* pContent, sonst false
|
||||||
|
*/
|
||||||
|
public boolean isEqual(ContentType pContent);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wenn festgestellt wird, dass das Objekt, von dem die Methode aufgerufen
|
||||||
|
* wird, bzgl. der gewuenschten Ordnungsrelation kleiner als das Objekt
|
||||||
|
* pContent ist, wird true geliefert. Sonst wird false geliefert.
|
||||||
|
*
|
||||||
|
* @param pContent
|
||||||
|
* das mit dem aufrufenden Objekt zu vergleichende Objekt vom
|
||||||
|
* Typ ContentType
|
||||||
|
* @return true, wenn das aufrufende Objekt kleiner ist als das Objekt
|
||||||
|
* pContent, sonst false
|
||||||
|
*/
|
||||||
|
public boolean isLess(ContentType pContent);
|
||||||
|
|
||||||
|
}
|
||||||
90
src/test/java/schule/ngb/zm/util/abi/Connection.java
Executable file
90
src/test/java/schule/ngb/zm/util/abi/Connection.java
Executable file
@@ -0,0 +1,90 @@
|
|||||||
|
package schule.ngb.zm.util.abi; /**
|
||||||
|
* <p>
|
||||||
|
* Materialien zu den zentralen NRW-Abiturpruefungen im Fach Informatik ab 2018
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Klasse Connection
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Objekte der Klasse Connection ermoeglichen eine Netzwerkverbindung zu einem
|
||||||
|
* Server mittels TCP/IP-Protokoll. Nach Verbindungsaufbau koennen Zeichenketten
|
||||||
|
* (Strings) zum Server gesendet und von diesem empfangen werden. Zur
|
||||||
|
* Vereinfachung geschieht dies zeilenweise, d. h., beim Senden einer
|
||||||
|
* Zeichenkette wird ein Zeilentrenner ergaenzt und beim Empfang wird dieser
|
||||||
|
* entfernt. Es findet nur eine rudimentaere Fehlerbehandlung statt, so dass z.B.
|
||||||
|
* der Zugriff auf unterbrochene oder bereits getrennte Verbindungen nicht zu
|
||||||
|
* einem Programmabbruch fuehrt. Eine einmal getrennte Verbindung kann nicht
|
||||||
|
* reaktiviert werden.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author Qualitaets- und UnterstuetzungsAgentur - Landesinstitut fuer Schule
|
||||||
|
* @version 30.08.2016
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.net.*;
|
||||||
|
import java.io.*;
|
||||||
|
|
||||||
|
public class Connection
|
||||||
|
{
|
||||||
|
private Socket socket;
|
||||||
|
private BufferedReader fromServer;
|
||||||
|
private PrintWriter toServer;
|
||||||
|
|
||||||
|
public Connection(String pServerIP, int pServerPort)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
socket = new Socket(pServerIP, pServerPort);
|
||||||
|
toServer = new PrintWriter(socket.getOutputStream(), true);
|
||||||
|
fromServer = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
//Erstelle eine nicht-verbundene Instanz von Socket, wenn die avisierte
|
||||||
|
//Verbindung nicht hergestellt werden kann
|
||||||
|
socket = null;
|
||||||
|
toServer = null;
|
||||||
|
fromServer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String receive()
|
||||||
|
{
|
||||||
|
if(fromServer != null)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return fromServer.readLine();
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
return(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void send(String pMessage)
|
||||||
|
{
|
||||||
|
if(toServer != null)
|
||||||
|
{
|
||||||
|
toServer.println(pMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close()
|
||||||
|
{
|
||||||
|
|
||||||
|
if(socket != null && !socket.isClosed())
|
||||||
|
try
|
||||||
|
{
|
||||||
|
socket.close();
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Falls eine Verbindung geschlossen werden soll, deren Endpunkt nicht
|
||||||
|
* mehr existiert bzw. seinerseits bereits geschlossen worden ist oder
|
||||||
|
* die nicht korrekt instanziiert werden konnte (socket == null), geschieht
|
||||||
|
* nichts.
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
151
src/test/java/schule/ngb/zm/util/abi/DatabaseConnector.java
Executable file
151
src/test/java/schule/ngb/zm/util/abi/DatabaseConnector.java
Executable file
@@ -0,0 +1,151 @@
|
|||||||
|
package schule.ngb.zm.util.abi;
|
||||||
|
|
||||||
|
import java.sql.*;
|
||||||
|
import java.sql.Connection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Materialien zu den zentralen NRW-Abiturpruefungen im Fach Informatik ab 2018
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Klasse DatabaseConnector
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Ein Objekt der Klasse DatabaseConnector ermoeglicht die Abfrage und Manipulation
|
||||||
|
* einer SQLite-Datenbank.
|
||||||
|
* Beim Erzeugen des Objekts wird eine Datenbankverbindung aufgebaut, so dass
|
||||||
|
* anschließend SQL-Anweisungen an diese Datenbank gerichtet werden koennen.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author Qualitaets- und UnterstuetzungsAgentur - Landesinstitut fuer Schule
|
||||||
|
* @version 2016-01-24
|
||||||
|
*/
|
||||||
|
public class DatabaseConnector{
|
||||||
|
private Connection connection;
|
||||||
|
private QueryResult currentQueryResult = null;
|
||||||
|
private String message = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ein Objekt vom Typ DatabaseConnector wird erstellt, und eine Verbindung zur Datenbank
|
||||||
|
* wird aufgebaut. Mit den Parametern pIP und pPort werden die IP-Adresse und die
|
||||||
|
* Port-Nummer uebergeben, unter denen die Datenbank mit Namen pDatabase zu erreichen ist.
|
||||||
|
* Mit den Parametern pUsername und pPassword werden Benutzername und Passwort fuer die
|
||||||
|
* Datenbank uebergeben.
|
||||||
|
*/
|
||||||
|
public DatabaseConnector(String pIP, int pPort, String pDatabase, String pUsername, String pPassword){
|
||||||
|
//Eine Impementierung dieser Schnittstelle fuer SQLite ignoriert pID und pPort, da die Datenbank immer lokal ist.
|
||||||
|
//Auch pUsername und pPassword werden nicht verwendet, da SQLite sie nicht unterstuetzt.
|
||||||
|
try {
|
||||||
|
//Laden der Treiberklasse
|
||||||
|
Class.forName("org.sqlite.JDBC");
|
||||||
|
|
||||||
|
//Verbindung herstellen
|
||||||
|
connection = DriverManager.getConnection("jdbc:sqlite:"+pDatabase);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
message = e.getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der Auftrag schickt den im Parameter pSQLStatement enthaltenen SQL-Befehl an die
|
||||||
|
* Datenbank ab.
|
||||||
|
* Handelt es sich bei pSQLStatement um einen SQL-Befehl, der eine Ergebnismenge
|
||||||
|
* liefert, so kann dieses Ergebnis anschließend mit der Methode getCurrentQueryResult
|
||||||
|
* abgerufen werden.
|
||||||
|
*/
|
||||||
|
public void executeStatement(String pSQLStatement){
|
||||||
|
//Altes Ergebnis loeschen
|
||||||
|
currentQueryResult = null;
|
||||||
|
message = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
//Neues Statement erstellen
|
||||||
|
Statement statement = connection.createStatement();
|
||||||
|
|
||||||
|
//SQL Anweisung an die DB schicken.
|
||||||
|
if (statement.execute(pSQLStatement)) { //Fall 1: Es gibt ein Ergebnis
|
||||||
|
|
||||||
|
//Resultset auslesen
|
||||||
|
ResultSet resultset = statement.getResultSet();
|
||||||
|
|
||||||
|
//Spaltenanzahl ermitteln
|
||||||
|
int columnCount = resultset.getMetaData().getColumnCount();
|
||||||
|
|
||||||
|
//Spaltennamen und Spaltentypen in Felder uebertragen
|
||||||
|
String[] resultColumnNames = new String[columnCount];
|
||||||
|
String[] resultColumnTypes = new String[columnCount];
|
||||||
|
for (int i = 0; i < columnCount; i++){
|
||||||
|
resultColumnNames[i] = resultset.getMetaData().getColumnLabel(i+1);
|
||||||
|
resultColumnTypes[i] = resultset.getMetaData().getColumnTypeName(i+1);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Queue fuer die Zeilen der Ergebnistabelle erstellen
|
||||||
|
Queue<String[]> rows = new Queue<String[]>();
|
||||||
|
|
||||||
|
//Daten in Queue uebertragen und Zeilen zaehlen
|
||||||
|
int rowCount = 0;
|
||||||
|
while (resultset.next()){
|
||||||
|
String[] resultrow = new String[columnCount];
|
||||||
|
for (int s = 0; s < columnCount; s++){
|
||||||
|
resultrow[s] = resultset.getString(s+1);
|
||||||
|
}
|
||||||
|
rows.enqueue(resultrow);
|
||||||
|
rowCount = rowCount + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Ergebnisfeld erstellen und Zeilen aus Queue uebertragen
|
||||||
|
String[][] resultData = new String[rowCount][columnCount];
|
||||||
|
int j = 0;
|
||||||
|
while (!rows.isEmpty()){
|
||||||
|
resultData[j] = rows.front();
|
||||||
|
rows.dequeue();
|
||||||
|
j = j + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Statement schließen und Ergebnisobjekt erstellen
|
||||||
|
statement.close();
|
||||||
|
currentQueryResult = new QueryResult(resultData, resultColumnNames, resultColumnTypes);
|
||||||
|
|
||||||
|
} else { //Fall 2: Es gibt kein Ergebnis.
|
||||||
|
//Statement ohne Ergebnisobjekt schliessen
|
||||||
|
statement.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
//Fehlermeldung speichern
|
||||||
|
message = e.getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert das Ergebnis des letzten mit der Methode executeStatement an
|
||||||
|
* die Datenbank geschickten SQL-Befehls als Ob-jekt vom Typ QueryResult zurueck.
|
||||||
|
* Wurde bisher kein SQL-Befehl abgeschickt oder ergab der letzte Aufruf von
|
||||||
|
* executeStatement keine Ergebnismenge (z.B. bei einem INSERT-Befehl oder einem
|
||||||
|
* Syntaxfehler), so wird null geliefert.
|
||||||
|
*/
|
||||||
|
public QueryResult getCurrentQueryResult(){
|
||||||
|
return currentQueryResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert null oder eine Fehlermeldung, die sich jeweils auf die letzte zuvor ausgefuehrte
|
||||||
|
* Datenbankoperation bezieht.
|
||||||
|
*/
|
||||||
|
public String getErrorMessage(){
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Datenbankverbindung wird geschlossen.
|
||||||
|
*/
|
||||||
|
public void close(){
|
||||||
|
try{
|
||||||
|
connection.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
message = e.getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
77
src/test/java/schule/ngb/zm/util/abi/Edge.java
Executable file
77
src/test/java/schule/ngb/zm/util/abi/Edge.java
Executable file
@@ -0,0 +1,77 @@
|
|||||||
|
package schule.ngb.zm.util.abi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Materialien zu den zentralen NRW-Abiturpruefungen im Fach Informatik ab 2018
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Klasse Edge
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Die Klasse Edge stellt eine einzelne, ungerichtete Kante eines Graphen dar.
|
||||||
|
* Beim Erstellen werden die beiden durch sie zu verbindenden Knotenobjekte und eine
|
||||||
|
* Gewichtung als double uebergeben. Beide Knotenobjekte koennen abgefragt werden.
|
||||||
|
* Des Weiteren koennen die Gewichtung und eine Markierung gesetzt und abgefragt werden.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author Qualitaets- und UnterstuetzungsAgentur - Landesinstitut fuer Schule
|
||||||
|
* @version Oktober 2015
|
||||||
|
*/
|
||||||
|
public class Edge{
|
||||||
|
private Vertex[] vertices;
|
||||||
|
private double weight;
|
||||||
|
private boolean mark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ein neues Objekt vom Typ Edge wird erstellt. Die von diesem Objekt
|
||||||
|
* repraesentierte Kante verbindet die Knoten pVertex und pAnotherVertex mit der
|
||||||
|
* Gewichtung pWeight. Ihre Markierung hat den Wert false.
|
||||||
|
*/
|
||||||
|
public Edge( Vertex pVertex, Vertex pAnotherVertex, double pWeight){
|
||||||
|
vertices = new Vertex[2];
|
||||||
|
vertices[0] = pVertex;
|
||||||
|
vertices[1] = pAnotherVertex;
|
||||||
|
weight = pWeight;
|
||||||
|
mark = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage gibt die beiden Knoten, die durch die Kante verbunden werden, als neues Feld vom Typ Vertex zurueck. Das Feld hat
|
||||||
|
* genau zwei Eintraege mit den Indexwerten 0 und 1.
|
||||||
|
*/
|
||||||
|
public Vertex[] getVertices(){
|
||||||
|
Vertex[] result = new Vertex[2];
|
||||||
|
result[0] = vertices[0];
|
||||||
|
result[1] = vertices[1];
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der Auftrag setzt das Gewicht der Kante auf pWeight.
|
||||||
|
*/
|
||||||
|
public void setWeight(double pWeight){
|
||||||
|
weight = pWeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert das Gewicht der Kante als double.
|
||||||
|
*/
|
||||||
|
public double getWeight(){
|
||||||
|
return weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der Auftrag setzt die Markierung der Kante auf den Wert pMark.
|
||||||
|
*/
|
||||||
|
public void setMark(boolean pMark){
|
||||||
|
mark = pMark;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert true, wenn die Markierung der Kante den Wert true hat, ansonsten false.
|
||||||
|
*/
|
||||||
|
public boolean isMarked(){
|
||||||
|
return mark;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
314
src/test/java/schule/ngb/zm/util/abi/Graph.java
Executable file
314
src/test/java/schule/ngb/zm/util/abi/Graph.java
Executable file
@@ -0,0 +1,314 @@
|
|||||||
|
package schule.ngb.zm.util.abi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Materialien zu den zentralen NRW-Abiturpruefungen im Fach Informatik ab 2018
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Klasse Graph
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Die Klasse Graph stellt einen ungerichteten, kantengewichteten Graphen dar. Es koennen
|
||||||
|
* Knoten- und Kantenobjekte hinzugefuegt und entfernt, flache Kopien der Knoten- und Kantenlisten
|
||||||
|
* des Graphen angefragt und Markierungen von Knoten und Kanten gesetzt und ueberprueft werden.
|
||||||
|
* Des Weiteren kann eine Liste der Nachbarn eines bestimmten Knoten, eine Liste der inzidenten
|
||||||
|
* Kanten eines bestimmten Knoten und die Kante von einem bestimmten Knoten zu einem
|
||||||
|
* anderen bestimmten Knoten angefragt werden. Abgesehen davon kann abgefragt werden, welches
|
||||||
|
* Knotenobjekt zu einer bestimmten ID gehoert und ob der Graph leer ist.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author Qualitaets- und UnterstuetzungsAgentur - Landesinstitut fuer Schule
|
||||||
|
* @version Oktober 2015
|
||||||
|
*/
|
||||||
|
public class Graph{
|
||||||
|
private List<Vertex> vertices;
|
||||||
|
private List<Edge> edges;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ein Objekt vom Typ Graph wird erstellt. Der von diesem Objekt
|
||||||
|
* repraesentierte Graph ist leer.
|
||||||
|
*/
|
||||||
|
public Graph(){
|
||||||
|
//Leere Listen fuer Knoten und Kanten erstellen.
|
||||||
|
vertices = new List<Vertex>();
|
||||||
|
edges = new List<Edge>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert eine neue Liste aller Knotenobjekte vom Typ List<Vertex>.
|
||||||
|
*/
|
||||||
|
public List<Vertex> getVertices(){
|
||||||
|
//Eine neue Liste mit allen Vertex-Objekten erstellen.
|
||||||
|
List<Vertex> result = new List<Vertex>();
|
||||||
|
vertices.toFirst();
|
||||||
|
while (vertices.hasAccess()){
|
||||||
|
result.append(vertices.getContent());
|
||||||
|
vertices.next();
|
||||||
|
}
|
||||||
|
//Aktuelles Element zum Anfang bewegen.
|
||||||
|
result.toFirst();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert eine neue Liste aller Kantenobjekte vom Typ List<Edge>.
|
||||||
|
*/
|
||||||
|
public List<Edge> getEdges(){
|
||||||
|
//Eine neue Liste mit allen Edge-Objekten erstellen.
|
||||||
|
List<Edge> result = new List<Edge>();
|
||||||
|
edges.toFirst();
|
||||||
|
while (edges.hasAccess()){
|
||||||
|
result.append(edges.getContent());
|
||||||
|
edges.next();
|
||||||
|
}
|
||||||
|
//Aktuelles Element zum Anfang bewegen.
|
||||||
|
result.toFirst();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert das Knotenobjekt mit pID als ID. Ist ein solchen Knotenobjekt nicht im Graphen enthalten,
|
||||||
|
* wird null zurueckgeliefert.
|
||||||
|
*/
|
||||||
|
public Vertex getVertex( String pID){
|
||||||
|
//Vertex-Objekt mit pID als ID suchen.
|
||||||
|
Vertex result = null;
|
||||||
|
vertices.toFirst();
|
||||||
|
while (vertices.hasAccess() && result == null){
|
||||||
|
if (vertices.getContent().getID().equals(pID)){
|
||||||
|
result = vertices.getContent();
|
||||||
|
}
|
||||||
|
vertices.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
//Objekt zurueckliefern.
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der Auftrag fuegt den Knoten pVertex in den Graphen ein, sofern es noch keinen
|
||||||
|
* Knoten mit demselben ID-Eintrag wie pVertex im Graphen gibt und pVertex eine ID ungleich null hat.
|
||||||
|
* Ansonsten passiert nichts.
|
||||||
|
*/
|
||||||
|
public void addVertex( Vertex pVertex){
|
||||||
|
//Pruefen, ob der Vertex existiert und eine ID hat.
|
||||||
|
if (pVertex != null && pVertex.getID() != null) {
|
||||||
|
//Pruefen, ob nicht schon ein Vertex mit gleicher ID enthalten ist.
|
||||||
|
boolean freeID = true;
|
||||||
|
vertices.toFirst();
|
||||||
|
while (vertices.hasAccess() && freeID){
|
||||||
|
if (vertices.getContent().getID().equals(pVertex.getID())){
|
||||||
|
freeID = false;
|
||||||
|
}
|
||||||
|
vertices.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
//Wenn die ID noch frei ist, den Vertex einfuegen, sonst nichts tun.
|
||||||
|
if (freeID) {
|
||||||
|
vertices.append(pVertex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der Auftrag fuegt die Kante pEdge in den Graphen ein, sofern beide durch die Kante verbundenen Knoten
|
||||||
|
* im Graphen enthalten sind, nicht identisch sind und noch keine Kante zwischen den Knoten existiert. Ansonsten passiert nichts.
|
||||||
|
*/
|
||||||
|
public void addEdge(Edge pEdge){
|
||||||
|
//Pruefen, ob pEdge exisitert.
|
||||||
|
if (pEdge != null){
|
||||||
|
Vertex[] vertexPair = pEdge.getVertices();
|
||||||
|
|
||||||
|
//Einfuegekriterien pruefen.
|
||||||
|
if (vertexPair[0] != null && vertexPair[1] != null &&
|
||||||
|
this.getVertex(vertexPair[0].getID()) == vertexPair[0] &&
|
||||||
|
this.getVertex(vertexPair[1].getID()) == vertexPair[1] &&
|
||||||
|
this.getEdge(vertexPair[0], vertexPair[1]) == null &&
|
||||||
|
vertexPair[0] != vertexPair[1]){
|
||||||
|
//Kante einfuegen.
|
||||||
|
edges.append(pEdge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der Auftrag entfernt den Knoten pVertex aus dem Graphen und loescht alle Kanten, die mit ihm inzident sind.
|
||||||
|
* Ist der Knoten pVertex nicht im Graphen enthalten, passiert nichts.
|
||||||
|
*/
|
||||||
|
public void removeVertex( Vertex pVertex){
|
||||||
|
//Inzidente Kanten entfernen.
|
||||||
|
edges.toFirst();
|
||||||
|
while (edges.hasAccess()){
|
||||||
|
Vertex[] akt = edges.getContent().getVertices();
|
||||||
|
if (akt[0] == pVertex || akt[1] == pVertex){
|
||||||
|
edges.remove();
|
||||||
|
} else {
|
||||||
|
edges.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Knoten entfernen
|
||||||
|
vertices.toFirst();
|
||||||
|
while (vertices.hasAccess() && vertices.getContent()!= pVertex){
|
||||||
|
vertices.next();
|
||||||
|
}
|
||||||
|
if (vertices.hasAccess()){
|
||||||
|
vertices.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der Auftrag entfernt die Kante pEdge aus dem Graphen. Ist die Kante pEdge nicht
|
||||||
|
* im Graphen enthalten, passiert nichts.
|
||||||
|
*/
|
||||||
|
public void removeEdge(Edge pEdge){
|
||||||
|
//Kante aus Kantenliste des Graphen entfernen.
|
||||||
|
edges.toFirst();
|
||||||
|
while (edges.hasAccess()){
|
||||||
|
if (edges.getContent() == pEdge){
|
||||||
|
edges.remove();
|
||||||
|
} else {
|
||||||
|
edges.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der Auftrag setzt die Markierungen aller Knoten des Graphen auf pMark.
|
||||||
|
*/
|
||||||
|
public void setAllVertexMarks(boolean pMark){
|
||||||
|
vertices.toFirst();
|
||||||
|
while (vertices.hasAccess()){
|
||||||
|
vertices.getContent().setMark(pMark);
|
||||||
|
vertices.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der Auftrag setzt die Markierungen aller Kanten des Graphen auf pMark.
|
||||||
|
*/
|
||||||
|
public void setAllEdgeMarks(boolean pMark){
|
||||||
|
edges.toFirst();
|
||||||
|
while (edges.hasAccess()){
|
||||||
|
edges.getContent().setMark(pMark);
|
||||||
|
edges.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert true, wenn alle Knoten des Graphen mit true markiert sind, ansonsten false.
|
||||||
|
*/
|
||||||
|
public boolean allVerticesMarked(){
|
||||||
|
boolean result = true;
|
||||||
|
vertices.toFirst();
|
||||||
|
while (vertices.hasAccess()){
|
||||||
|
if (!vertices.getContent().isMarked()){
|
||||||
|
result = false;
|
||||||
|
}
|
||||||
|
vertices.next();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert true, wenn alle Kanten des Graphen mit true markiert sind, ansonsten false.
|
||||||
|
*/
|
||||||
|
public boolean allEdgesMarked(){
|
||||||
|
boolean result = true;
|
||||||
|
edges.toFirst();
|
||||||
|
while (edges.hasAccess()){
|
||||||
|
if (!edges.getContent().isMarked()){
|
||||||
|
result = false;
|
||||||
|
}
|
||||||
|
edges.next();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert alle Nachbarn des Knotens pVertex als neue Liste vom Typ List<Vertex>. Hat der Knoten
|
||||||
|
* pVertex keine Nachbarn in diesem Graphen oder ist gar nicht in diesem Graphen enthalten, so
|
||||||
|
* wird eine leere Liste zurueckgeliefert.
|
||||||
|
*/
|
||||||
|
public List<Vertex> getNeighbours( Vertex pVertex){
|
||||||
|
List<Vertex> result = new List<Vertex>();
|
||||||
|
|
||||||
|
//Alle Kanten durchlaufen.
|
||||||
|
edges.toFirst();
|
||||||
|
while (edges.hasAccess()){
|
||||||
|
|
||||||
|
//Wenn ein Knoten der Kante pVertex ist, den anderen als Nachbarn in die Ergebnisliste einfuegen.
|
||||||
|
Vertex[] vertexPair = edges.getContent().getVertices();
|
||||||
|
if (vertexPair[0] == pVertex) {
|
||||||
|
result.append(vertexPair[1]);
|
||||||
|
} else {
|
||||||
|
if (vertexPair[1] == pVertex){
|
||||||
|
result.append(vertexPair[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
edges.next();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert eine neue Liste alle inzidenten Kanten zum Knoten pVertex. Hat der Knoten
|
||||||
|
* pVertex keine inzidenten Kanten in diesem Graphen oder ist gar nicht in diesem Graphen enthalten, so
|
||||||
|
* wird eine leere Liste zurueckgeliefert.
|
||||||
|
*/
|
||||||
|
public List<Edge> getEdges( Vertex pVertex){
|
||||||
|
List<Edge> result = new List<Edge>();
|
||||||
|
|
||||||
|
//Alle Kanten durchlaufen.
|
||||||
|
edges.toFirst();
|
||||||
|
while (edges.hasAccess()){
|
||||||
|
|
||||||
|
//Wenn ein Knoten der Kante pVertex ist, dann Kante als inzidente Kante in die Ergebnisliste einfuegen.
|
||||||
|
Vertex[] vertexPair = edges.getContent().getVertices();
|
||||||
|
if (vertexPair[0] == pVertex) {
|
||||||
|
result.append(edges.getContent());
|
||||||
|
} else{
|
||||||
|
if (vertexPair[1] == pVertex){
|
||||||
|
result.append(edges.getContent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
edges.next();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert die Kante, welche die Knoten pVertex und pAnotherVertex verbindet,
|
||||||
|
* als Objekt vom Typ Edge. Ist der Knoten pVertex oder der Knoten pAnotherVertex nicht
|
||||||
|
* im Graphen enthalten oder gibt es keine Kante, die beide Knoten verbindet, so wird null
|
||||||
|
* zurueckgeliefert.
|
||||||
|
*/
|
||||||
|
public Edge getEdge( Vertex pVertex, Vertex pAnotherVertex){
|
||||||
|
Edge result = null;
|
||||||
|
|
||||||
|
//Kanten durchsuchen, solange keine passende gefunden wurde.
|
||||||
|
edges.toFirst();
|
||||||
|
while (edges.hasAccess() && result == null){
|
||||||
|
|
||||||
|
//Pruefen, ob die Kante pVertex und pAnotherVertex verbindet.
|
||||||
|
Vertex[] vertexPair = edges.getContent().getVertices();
|
||||||
|
if ((vertexPair[0] == pVertex && vertexPair[1] == pAnotherVertex) ||
|
||||||
|
(vertexPair[0] == pAnotherVertex && vertexPair[1] == pVertex)) {
|
||||||
|
//Kante als Ergebnis merken.
|
||||||
|
result = edges.getContent();
|
||||||
|
}
|
||||||
|
edges.next();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert true, wenn der Graph keine Knoten enthaelt, ansonsten false.
|
||||||
|
*/
|
||||||
|
public boolean isEmpty(){
|
||||||
|
return vertices.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
347
src/test/java/schule/ngb/zm/util/abi/List.java
Executable file
347
src/test/java/schule/ngb/zm/util/abi/List.java
Executable file
@@ -0,0 +1,347 @@
|
|||||||
|
package schule.ngb.zm.util.abi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Materialien zu den zentralen NRW-Abiturpruefungen im Fach Informatik ab 2018
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Generische Klasse List<ContentType>
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Objekt der generischen Klasse List verwalten beliebig viele linear
|
||||||
|
* angeordnete Objekte vom Typ ContentType. Auf hoechstens ein Listenobjekt,
|
||||||
|
* aktuellesObjekt genannt, kann jeweils zugegriffen werden.<br />
|
||||||
|
* Wenn eine Liste leer ist, vollstaendig durchlaufen wurde oder das aktuelle
|
||||||
|
* Objekt am Ende der Liste geloescht wurde, gibt es kein aktuelles Objekt.<br />
|
||||||
|
* Das erste oder das letzte Objekt einer Liste koennen durch einen Auftrag zum
|
||||||
|
* aktuellen Objekt gemacht werden. Ausserdem kann das dem aktuellen Objekt
|
||||||
|
* folgende Listenobjekt zum neuen aktuellen Objekt werden. <br />
|
||||||
|
* Das aktuelle Objekt kann gelesen, veraendert oder geloescht werden. Ausserdem
|
||||||
|
* kann vor dem aktuellen Objekt ein Listenobjekt eingefuegt werden.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author Qualitaets- und UnterstuetzungsAgentur - Landesinstitut fuer Schule
|
||||||
|
* @version Generisch_06 2015-10-25
|
||||||
|
*/
|
||||||
|
public class List<ContentType> {
|
||||||
|
|
||||||
|
/* --------- Anfang der privaten inneren Klasse -------------- */
|
||||||
|
|
||||||
|
private class ListNode {
|
||||||
|
|
||||||
|
private ContentType contentObject;
|
||||||
|
private ListNode next;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ein neues Objekt wird erschaffen. Der Verweis ist leer.
|
||||||
|
*
|
||||||
|
* @param pContent das Inhaltsobjekt vom Typ ContentType
|
||||||
|
*/
|
||||||
|
private ListNode(ContentType pContent) {
|
||||||
|
contentObject = pContent;
|
||||||
|
next = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der Inhalt des Knotens wird zurueckgeliefert.
|
||||||
|
*
|
||||||
|
* @return das Inhaltsobjekt des Knotens
|
||||||
|
*/
|
||||||
|
public ContentType getContentObject() {
|
||||||
|
return contentObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der Inhalt dieses Kontens wird gesetzt.
|
||||||
|
*
|
||||||
|
* @param pContent das Inhaltsobjekt vom Typ ContentType
|
||||||
|
*/
|
||||||
|
public void setContentObject(ContentType pContent) {
|
||||||
|
contentObject = pContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der Nachfolgeknoten wird zurueckgeliefert.
|
||||||
|
*
|
||||||
|
* @return das Objekt, auf das der aktuelle Verweis zeigt
|
||||||
|
*/
|
||||||
|
public ListNode getNextNode() {
|
||||||
|
return this.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der Verweis wird auf das Objekt, das als Parameter uebergeben
|
||||||
|
* wird, gesetzt.
|
||||||
|
*
|
||||||
|
* @param pNext der Nachfolger des Knotens
|
||||||
|
*/
|
||||||
|
public void setNextNode(ListNode pNext) {
|
||||||
|
this.next = pNext;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------- Ende der privaten inneren Klasse -------------- */
|
||||||
|
|
||||||
|
// erstes Element der Liste
|
||||||
|
ListNode first;
|
||||||
|
|
||||||
|
// letztes Element der Liste
|
||||||
|
ListNode last;
|
||||||
|
|
||||||
|
// aktuelles Element der Liste
|
||||||
|
ListNode current;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Eine leere Liste wird erzeugt.
|
||||||
|
*/
|
||||||
|
public List() {
|
||||||
|
first = null;
|
||||||
|
last = null;
|
||||||
|
current = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert den Wert true, wenn die Liste keine Objekte enthaelt,
|
||||||
|
* sonst liefert sie den Wert false.
|
||||||
|
*
|
||||||
|
* @return true, wenn die Liste leer ist, sonst false
|
||||||
|
*/
|
||||||
|
public boolean isEmpty() {
|
||||||
|
// Die Liste ist leer, wenn es kein erstes Element gibt.
|
||||||
|
return first == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert den Wert true, wenn es ein aktuelles Objekt gibt,
|
||||||
|
* sonst liefert sie den Wert false.
|
||||||
|
*
|
||||||
|
* @return true, falls Zugriff moeglich, sonst false
|
||||||
|
*/
|
||||||
|
public boolean hasAccess() {
|
||||||
|
// Es gibt keinen Zugriff, wenn current auf kein Element verweist.
|
||||||
|
return current != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Falls die Liste nicht leer ist, es ein aktuelles Objekt gibt und dieses
|
||||||
|
* nicht das letzte Objekt der Liste ist, wird das dem aktuellen Objekt in
|
||||||
|
* der Liste folgende Objekt zum aktuellen Objekt, andernfalls gibt es nach
|
||||||
|
* Ausfuehrung des Auftrags kein aktuelles Objekt, d.h. hasAccess() liefert
|
||||||
|
* den Wert false.
|
||||||
|
*/
|
||||||
|
public void next() {
|
||||||
|
if (this.hasAccess()) {
|
||||||
|
current = current.getNextNode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Falls die Liste nicht leer ist, wird das erste Objekt der Liste aktuelles
|
||||||
|
* Objekt. Ist die Liste leer, geschieht nichts.
|
||||||
|
*/
|
||||||
|
public void toFirst() {
|
||||||
|
if (!isEmpty()) {
|
||||||
|
current = first;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Falls die Liste nicht leer ist, wird das letzte Objekt der Liste
|
||||||
|
* aktuelles Objekt. Ist die Liste leer, geschieht nichts.
|
||||||
|
*/
|
||||||
|
public void toLast() {
|
||||||
|
if (!isEmpty()) {
|
||||||
|
current = last;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Falls es ein aktuelles Objekt gibt (hasAccess() == true), wird das
|
||||||
|
* aktuelle Objekt zurueckgegeben, andernfalls (hasAccess() == false) gibt
|
||||||
|
* die Anfrage den Wert null zurueck.
|
||||||
|
*
|
||||||
|
* @return das aktuelle Objekt (vom Typ ContentType) oder null, wenn es
|
||||||
|
* kein aktuelles Objekt gibt
|
||||||
|
*/
|
||||||
|
public ContentType getContent() {
|
||||||
|
if (this.hasAccess()) {
|
||||||
|
return current.getContentObject();
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Falls es ein aktuelles Objekt gibt (hasAccess() == true) und pContent
|
||||||
|
* ungleich null ist, wird das aktuelle Objekt durch pContent ersetzt. Sonst
|
||||||
|
* geschieht nichts.
|
||||||
|
*
|
||||||
|
* @param pContent
|
||||||
|
* das zu schreibende Objekt vom Typ ContentType
|
||||||
|
*/
|
||||||
|
public void setContent(ContentType pContent) {
|
||||||
|
// Nichts tun, wenn es keinen Inhalt oder kein aktuelles Element gibt.
|
||||||
|
if (pContent != null && this.hasAccess()) {
|
||||||
|
current.setContentObject(pContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Falls es ein aktuelles Objekt gibt (hasAccess() == true), wird ein neues
|
||||||
|
* Objekt vor dem aktuellen Objekt in die Liste eingefuegt. Das aktuelle
|
||||||
|
* Objekt bleibt unveraendert. <br />
|
||||||
|
* Wenn die Liste leer ist, wird pContent in die Liste eingefuegt und es
|
||||||
|
* gibt weiterhin kein aktuelles Objekt (hasAccess() == false). <br />
|
||||||
|
* Falls es kein aktuelles Objekt gibt (hasAccess() == false) und die Liste
|
||||||
|
* nicht leer ist oder pContent gleich null ist, geschieht nichts.
|
||||||
|
*
|
||||||
|
* @param pContent
|
||||||
|
* das einzufuegende Objekt vom Typ ContentType
|
||||||
|
*/
|
||||||
|
public void insert(ContentType pContent) {
|
||||||
|
if (pContent != null) { // Nichts tun, wenn es keinen Inhalt gibt.
|
||||||
|
if (this.hasAccess()) { // Fall: Es gibt ein aktuelles Element.
|
||||||
|
|
||||||
|
// Neuen Knoten erstellen.
|
||||||
|
ListNode newNode = new ListNode(pContent);
|
||||||
|
|
||||||
|
if (current != first) { // Fall: Nicht an erster Stelle einfuegen.
|
||||||
|
ListNode previous = this.getPrevious(current);
|
||||||
|
newNode.setNextNode(previous.getNextNode());
|
||||||
|
previous.setNextNode(newNode);
|
||||||
|
} else { // Fall: An erster Stelle einfuegen.
|
||||||
|
newNode.setNextNode(first);
|
||||||
|
first = newNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else { //Fall: Es gibt kein aktuelles Element.
|
||||||
|
|
||||||
|
if (this.isEmpty()) { // Fall: In leere Liste einfuegen.
|
||||||
|
|
||||||
|
// Neuen Knoten erstellen.
|
||||||
|
ListNode newNode = new ListNode(pContent);
|
||||||
|
|
||||||
|
first = newNode;
|
||||||
|
last = newNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Falls pContent gleich null ist, geschieht nichts.<br />
|
||||||
|
* Ansonsten wird ein neues Objekt pContent am Ende der Liste eingefuegt.
|
||||||
|
* Das aktuelle Objekt bleibt unveraendert. <br />
|
||||||
|
* Wenn die Liste leer ist, wird das Objekt pContent in die Liste eingefuegt
|
||||||
|
* und es gibt weiterhin kein aktuelles Objekt (hasAccess() == false).
|
||||||
|
*
|
||||||
|
* @param pContent
|
||||||
|
* das anzuhaengende Objekt vom Typ ContentType
|
||||||
|
*/
|
||||||
|
public void append(ContentType pContent) {
|
||||||
|
if (pContent != null) { // Nichts tun, wenn es keine Inhalt gibt.
|
||||||
|
|
||||||
|
if (this.isEmpty()) { // Fall: An leere Liste anfuegen.
|
||||||
|
this.insert(pContent);
|
||||||
|
} else { // Fall: An nicht-leere Liste anfuegen.
|
||||||
|
|
||||||
|
// Neuen Knoten erstellen.
|
||||||
|
ListNode newNode = new ListNode(pContent);
|
||||||
|
|
||||||
|
last.setNextNode(newNode);
|
||||||
|
last = newNode; // Letzten Knoten aktualisieren.
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Falls es sich bei der Liste und pList um dasselbe Objekt handelt,
|
||||||
|
* pList null oder eine leere Liste ist, geschieht nichts.<br />
|
||||||
|
* Ansonsten wird die Liste pList an die aktuelle Liste angehaengt.
|
||||||
|
* Anschliessend wird pList eine leere Liste. Das aktuelle Objekt bleibt
|
||||||
|
* unveraendert. Insbesondere bleibt hasAccess identisch.
|
||||||
|
*
|
||||||
|
* @param pList
|
||||||
|
* die am Ende anzuhaengende Liste vom Typ List<ContentType>
|
||||||
|
*/
|
||||||
|
public void concat(List<ContentType> pList) {
|
||||||
|
if (pList != this && pList != null && !pList.isEmpty()) { // Nichts tun,
|
||||||
|
// wenn pList und this identisch, pList leer oder nicht existent.
|
||||||
|
|
||||||
|
if (this.isEmpty()) { // Fall: An leere Liste anfuegen.
|
||||||
|
this.first = pList.first;
|
||||||
|
this.last = pList.last;
|
||||||
|
} else { // Fall: An nicht-leere Liste anfuegen.
|
||||||
|
this.last.setNextNode(pList.first);
|
||||||
|
this.last = pList.last;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Liste pList loeschen.
|
||||||
|
pList.first = null;
|
||||||
|
pList.last = null;
|
||||||
|
pList.current = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wenn die Liste leer ist oder es kein aktuelles Objekt gibt (hasAccess()
|
||||||
|
* == false), geschieht nichts.<br />
|
||||||
|
* Falls es ein aktuelles Objekt gibt (hasAccess() == true), wird das
|
||||||
|
* aktuelle Objekt geloescht und das Objekt hinter dem geloeschten Objekt
|
||||||
|
* wird zum aktuellen Objekt. <br />
|
||||||
|
* Wird das Objekt, das am Ende der Liste steht, geloescht, gibt es kein
|
||||||
|
* aktuelles Objekt mehr.
|
||||||
|
*/
|
||||||
|
public void remove() {
|
||||||
|
// Nichts tun, wenn es kein aktuelle Element gibt oder die Liste leer ist.
|
||||||
|
if (this.hasAccess() && !this.isEmpty()) {
|
||||||
|
|
||||||
|
if (current == first) {
|
||||||
|
first = first.getNextNode();
|
||||||
|
} else {
|
||||||
|
ListNode previous = this.getPrevious(current);
|
||||||
|
if (current == last) {
|
||||||
|
last = previous;
|
||||||
|
}
|
||||||
|
previous.setNextNode(current.getNextNode());
|
||||||
|
}
|
||||||
|
|
||||||
|
ListNode temp = current.getNextNode();
|
||||||
|
current.setContentObject(null);
|
||||||
|
current.setNextNode(null);
|
||||||
|
current = temp;
|
||||||
|
|
||||||
|
//Beim loeschen des letzten Elements last auf null setzen.
|
||||||
|
if (this.isEmpty()) {
|
||||||
|
last = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Liefert den Vorgaengerknoten des Knotens pNode. Ist die Liste leer, pNode
|
||||||
|
* == null, pNode nicht in der Liste oder pNode der erste Knoten der Liste,
|
||||||
|
* wird null zurueckgegeben.
|
||||||
|
*
|
||||||
|
* @param pNode
|
||||||
|
* der Knoten, dessen Vorgaenger zurueckgegeben werden soll
|
||||||
|
* @return der Vorgaenger des Knotens pNode oder null, falls die Liste leer ist,
|
||||||
|
* pNode == null ist, pNode nicht in der Liste ist oder pNode der erste Knoten
|
||||||
|
* der Liste ist
|
||||||
|
*/
|
||||||
|
private ListNode getPrevious(ListNode pNode) {
|
||||||
|
if (pNode != null && pNode != first && !this.isEmpty()) {
|
||||||
|
ListNode temp = first;
|
||||||
|
while (temp != null && temp.getNextNode() != pNode) {
|
||||||
|
temp = temp.getNextNode();
|
||||||
|
}
|
||||||
|
return temp;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
78
src/test/java/schule/ngb/zm/util/abi/QueryResult.java
Executable file
78
src/test/java/schule/ngb/zm/util/abi/QueryResult.java
Executable file
@@ -0,0 +1,78 @@
|
|||||||
|
package schule.ngb.zm.util.abi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Materialien zu den zentralen NRW-Abiturpruefungen im Fach Informatik ab 2018
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Klasse QueryResult
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Ein Objekt der Klasse QueryResult stellt die Ergebnistabelle einer Datenbankanfrage mit Hilfe
|
||||||
|
* der Klasse DatabaseConnector dar. Objekte dieser Klasse werden nur von der Klasse DatabaseConnector erstellt.
|
||||||
|
* Die Klasse verfuegt ueber keinen oeffentlichen Konstruktor.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author Qualitaets- und UnterstuetzungsAgentur - Landesinstitut fuer Schule
|
||||||
|
* @version 2015-01-31
|
||||||
|
*/
|
||||||
|
public class QueryResult{
|
||||||
|
private String[][] data;
|
||||||
|
private String[] columnNames;
|
||||||
|
private String[] columnTypes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Paketinterner Konstruktor.
|
||||||
|
*/
|
||||||
|
QueryResult(String[][] pData, String[] pColumnNames, String[] pColumnTypes){
|
||||||
|
data = pData;
|
||||||
|
columnNames = pColumnNames;
|
||||||
|
columnTypes = pColumnTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert die Eintraege der Ergebnistabelle als zweidimensionales Feld
|
||||||
|
* vom Typ String. Der erste Index des Feldes stellt die Zeile und der zweite die
|
||||||
|
* Spalte dar (d.h. Object[zeile][spalte]).
|
||||||
|
*/
|
||||||
|
public String[][] getData(){
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert die Bezeichner der Spalten der Ergebnistabelle als Feld vom
|
||||||
|
* Typ String zurueck.
|
||||||
|
*/
|
||||||
|
public String[] getColumnNames(){
|
||||||
|
return columnNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert die Typenbezeichnung der Spalten der Ergebnistabelle als Feld
|
||||||
|
* vom Typ String zurueck. Die Bezeichnungen entsprechen den Angaben in der MySQL-Datenbank.
|
||||||
|
*/
|
||||||
|
public String[] getColumnTypes(){
|
||||||
|
return columnTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert die Anzahl der Zeilen der Ergebnistabelle als Integer.
|
||||||
|
*/
|
||||||
|
public int getRowCount(){
|
||||||
|
if (data != null )
|
||||||
|
return data.length;
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert die Anzahl der Spalten der Ergebnistabelle als Integer.
|
||||||
|
*/
|
||||||
|
public int getColumnCount(){
|
||||||
|
if (data != null && data.length > 0 && data[0] != null)
|
||||||
|
return data[0].length;
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
144
src/test/java/schule/ngb/zm/util/abi/Queue.java
Executable file
144
src/test/java/schule/ngb/zm/util/abi/Queue.java
Executable file
@@ -0,0 +1,144 @@
|
|||||||
|
package schule.ngb.zm.util.abi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Materialien zu den zentralen NRW-Abiturpruefungen im Fach Informatik ab 2018
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Generische Klasse Queue<ContentType>
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Objekte der generischen Klasse Queue (Warteschlange) verwalten beliebige
|
||||||
|
* Objekte vom Typ ContentType nach dem First-In-First-Out-Prinzip, d.h., das
|
||||||
|
* zuerst abgelegte Objekt wird als erstes wieder entnommen. Alle Methoden haben
|
||||||
|
* eine konstante Laufzeit, unabhaengig von der Anzahl der verwalteten Objekte.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author Qualitaets- und UnterstuetzungsAgentur - Landesinstitut fuer Schule
|
||||||
|
* @version Generisch_02 2014-02-21
|
||||||
|
*/
|
||||||
|
public class Queue<ContentType> {
|
||||||
|
|
||||||
|
/* --------- Anfang der privaten inneren Klasse -------------- */
|
||||||
|
|
||||||
|
private class QueueNode {
|
||||||
|
|
||||||
|
private ContentType content = null;
|
||||||
|
private QueueNode nextNode = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ein neues Objekt vom Typ QueueNode<ContentType> wird erschaffen.
|
||||||
|
* Der Inhalt wird per Parameter gesetzt. Der Verweis ist leer.
|
||||||
|
*
|
||||||
|
* @param pContent das Inhaltselement des Knotens vom Typ ContentType
|
||||||
|
*/
|
||||||
|
public QueueNode(ContentType pContent) {
|
||||||
|
content = pContent;
|
||||||
|
nextNode = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der Verweis wird auf das Objekt, das als Parameter uebergeben wird,
|
||||||
|
* gesetzt.
|
||||||
|
*
|
||||||
|
* @param pNext der Nachfolger des Knotens
|
||||||
|
*/
|
||||||
|
public void setNext(QueueNode pNext) {
|
||||||
|
nextNode = pNext;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Liefert das naechste Element des aktuellen Knotens.
|
||||||
|
*
|
||||||
|
* @return das Objekt vom Typ QueueNode, auf das der aktuelle Verweis zeigt
|
||||||
|
*/
|
||||||
|
public QueueNode getNext() {
|
||||||
|
return nextNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Liefert das Inhaltsobjekt des Knotens vom Typ ContentType.
|
||||||
|
*
|
||||||
|
* @return das Inhaltsobjekt des Knotens
|
||||||
|
*/
|
||||||
|
public ContentType getContent() {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------- Ende der privaten inneren Klasse -------------- */
|
||||||
|
|
||||||
|
private QueueNode head;
|
||||||
|
private QueueNode tail;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Eine leere Schlange wird erzeugt.
|
||||||
|
* Objekte, die in dieser Schlange verwaltet werden, muessen vom Typ
|
||||||
|
* ContentType sein.
|
||||||
|
*/
|
||||||
|
public Queue() {
|
||||||
|
head = null;
|
||||||
|
tail = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert den Wert true, wenn die Schlange keine Objekte enthaelt,
|
||||||
|
* sonst liefert sie den Wert false.
|
||||||
|
*
|
||||||
|
* @return true, falls die Schlange leer ist, sonst false
|
||||||
|
*/
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return head == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Das Objekt pContentType wird an die Schlange angehaengt.
|
||||||
|
* Falls pContentType gleich null ist, bleibt die Schlange unveraendert.
|
||||||
|
*
|
||||||
|
* @param pContent
|
||||||
|
* das anzuhaengende Objekt vom Typ ContentType
|
||||||
|
*/
|
||||||
|
public void enqueue(ContentType pContent) {
|
||||||
|
if (pContent != null) {
|
||||||
|
QueueNode newNode = new QueueNode(pContent);
|
||||||
|
if (this.isEmpty()) {
|
||||||
|
head = newNode;
|
||||||
|
tail = newNode;
|
||||||
|
} else {
|
||||||
|
tail.setNext(newNode);
|
||||||
|
tail = newNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Das erste Objekt wird aus der Schlange entfernt.
|
||||||
|
* Falls die Schlange leer ist, wird sie nicht veraendert.
|
||||||
|
*/
|
||||||
|
public void dequeue() {
|
||||||
|
if (!this.isEmpty()) {
|
||||||
|
head = head.getNext();
|
||||||
|
if (this.isEmpty()) {
|
||||||
|
head = null;
|
||||||
|
tail = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert das erste Objekt der Schlange.
|
||||||
|
* Die Schlange bleibt unveraendert.
|
||||||
|
* Falls die Schlange leer ist, wird null zurueckgegeben.
|
||||||
|
*
|
||||||
|
* @return das erste Objekt der Schlange vom Typ ContentType oder null,
|
||||||
|
* falls die Schlange leer ist
|
||||||
|
*/
|
||||||
|
public ContentType front() {
|
||||||
|
if (this.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return head.getContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
359
src/test/java/schule/ngb/zm/util/abi/Server.java
Executable file
359
src/test/java/schule/ngb/zm/util/abi/Server.java
Executable file
@@ -0,0 +1,359 @@
|
|||||||
|
package schule.ngb.zm.util.abi; /**
|
||||||
|
* <p>
|
||||||
|
* Materialien zu den zentralen NRW-Abiturpruefungen im Fach Informatik ab 2018
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Klasse Server
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Objekte von Unterklassen der abstrakten Klasse Server ermoeglichen das
|
||||||
|
* Anbieten von Serverdiensten, so dass Clients Verbindungen zum Server mittels
|
||||||
|
* TCP/IP-Protokoll aufbauen koennen. Zur Vereinfachung finden Nachrichtenversand
|
||||||
|
* und -empfang zeilenweise statt, d. h., beim Senden einer Zeichenkette wird ein
|
||||||
|
* Zeilentrenner ergaenzt und beim Empfang wird dieser entfernt.
|
||||||
|
* Verbindungsannahme, Nachrichtenempfang und Verbindungsende geschehen
|
||||||
|
* nebenlaeufig. Auf diese Ereignisse muss durch Ueberschreiben der entsprechenden
|
||||||
|
* Ereignisbehandlungsmethoden reagiert werden. Es findet nur eine rudimentaere
|
||||||
|
* Fehlerbehandlung statt, so dass z.B. Verbindungsabbrueche nicht zu einem
|
||||||
|
* Programmabbruch fuehren. Einmal unterbrochene oder getrennte Verbindungen
|
||||||
|
* koennen nicht reaktiviert werden.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author Qualitaets- und UnterstuetzungsAgentur - Landesinstitut fuer Schule
|
||||||
|
* @version 30.08.2016
|
||||||
|
*/
|
||||||
|
import java.net.*;
|
||||||
|
import java.io.*;
|
||||||
|
|
||||||
|
public abstract class Server
|
||||||
|
{
|
||||||
|
private NewConnectionHandler connectionHandler;
|
||||||
|
private List<ClientMessageHandler> messageHandlers;
|
||||||
|
|
||||||
|
private class NewConnectionHandler extends Thread
|
||||||
|
{
|
||||||
|
private ServerSocket serverSocket;
|
||||||
|
private boolean active;
|
||||||
|
|
||||||
|
public NewConnectionHandler(int pPort)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
serverSocket = new ServerSocket(pPort);
|
||||||
|
start();
|
||||||
|
active = true;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
serverSocket = null;
|
||||||
|
active = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
while (active)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//Warten auf Verbdinungsversuch durch Client:
|
||||||
|
Socket clientSocket = serverSocket.accept();
|
||||||
|
// Eingehende Nachrichten vom neu verbundenen Client werden
|
||||||
|
// in einem eigenen Thread empfangen:
|
||||||
|
addNewClientMessageHandler(clientSocket);
|
||||||
|
processNewConnection(clientSocket.getInetAddress().getHostAddress(),clientSocket.getPort());
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Kann keine Verbindung zum anfragenden Client aufgebaut werden,
|
||||||
|
* geschieht nichts.
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close()
|
||||||
|
{
|
||||||
|
active = false;
|
||||||
|
if(serverSocket != null)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
serverSocket.close();
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Befindet sich der ServerSocket im accept()-Wartezustand oder wurde
|
||||||
|
* er bereits geschlossen, geschieht nichts.
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ClientMessageHandler extends Thread
|
||||||
|
{
|
||||||
|
private ClientSocketWrapper socketWrapper;
|
||||||
|
private boolean active;
|
||||||
|
|
||||||
|
private class ClientSocketWrapper
|
||||||
|
{
|
||||||
|
private Socket clientSocket;
|
||||||
|
private BufferedReader fromClient;
|
||||||
|
private PrintWriter toClient;
|
||||||
|
|
||||||
|
public ClientSocketWrapper(Socket pSocket)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
clientSocket = pSocket;
|
||||||
|
toClient = new PrintWriter(clientSocket.getOutputStream(), true);
|
||||||
|
fromClient = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
clientSocket = null;
|
||||||
|
toClient = null;
|
||||||
|
fromClient = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String receive()
|
||||||
|
{
|
||||||
|
if(fromClient != null)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return fromClient.readLine();
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
return(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void send(String pMessage)
|
||||||
|
{
|
||||||
|
if(toClient != null)
|
||||||
|
{
|
||||||
|
toClient.println(pMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClientIP()
|
||||||
|
{
|
||||||
|
if(clientSocket != null)
|
||||||
|
return(clientSocket.getInetAddress().getHostAddress());
|
||||||
|
else
|
||||||
|
return(null); //Gemaess Java-API Rueckgabe bei nicht-verbundenen Sockets
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getClientPort()
|
||||||
|
{
|
||||||
|
if(clientSocket != null)
|
||||||
|
return(clientSocket.getPort());
|
||||||
|
else
|
||||||
|
return(0); //Gemaess Java-API Rueckgabe bei nicht-verbundenen Sockets
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close()
|
||||||
|
{
|
||||||
|
if(clientSocket != null)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
clientSocket.close();
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Falls eine Verbindung getrennt werden soll, deren Endpunkt
|
||||||
|
* nicht mehr existiert bzw. ihrerseits bereits beendet worden ist,
|
||||||
|
* geschieht nichts.
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ClientMessageHandler(Socket pClientSocket)
|
||||||
|
{
|
||||||
|
socketWrapper = new ClientSocketWrapper(pClientSocket);
|
||||||
|
if(pClientSocket!=null)
|
||||||
|
{
|
||||||
|
start();
|
||||||
|
active = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
active = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
String message = null;
|
||||||
|
while (active)
|
||||||
|
{
|
||||||
|
message = socketWrapper.receive();
|
||||||
|
if (message != null)
|
||||||
|
processMessage(socketWrapper.getClientIP(), socketWrapper.getClientPort(), message);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ClientMessageHandler aMessageHandler = findClientMessageHandler(socketWrapper.getClientIP(), socketWrapper.getClientPort());
|
||||||
|
if (aMessageHandler != null)
|
||||||
|
{
|
||||||
|
aMessageHandler.close();
|
||||||
|
removeClientMessageHandler(aMessageHandler);
|
||||||
|
processClosingConnection(socketWrapper.getClientIP(), socketWrapper.getClientPort());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void send(String pMessage)
|
||||||
|
{
|
||||||
|
if(active)
|
||||||
|
socketWrapper.send(pMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close()
|
||||||
|
{
|
||||||
|
if(active)
|
||||||
|
{
|
||||||
|
active=false;
|
||||||
|
socketWrapper.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClientIP()
|
||||||
|
{
|
||||||
|
return(socketWrapper.getClientIP());
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getClientPort()
|
||||||
|
{
|
||||||
|
return(socketWrapper.getClientPort());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public Server(int pPort)
|
||||||
|
{
|
||||||
|
connectionHandler = new NewConnectionHandler(pPort);
|
||||||
|
messageHandlers = new List<ClientMessageHandler>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isOpen()
|
||||||
|
{
|
||||||
|
return(connectionHandler.active);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isConnectedTo(String pClientIP, int pClientPort)
|
||||||
|
{
|
||||||
|
ClientMessageHandler aMessageHandler = findClientMessageHandler(pClientIP, pClientPort);
|
||||||
|
if (aMessageHandler != null)
|
||||||
|
return(aMessageHandler.active);
|
||||||
|
else
|
||||||
|
return(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void send(String pClientIP, int pClientPort, String pMessage)
|
||||||
|
{
|
||||||
|
ClientMessageHandler aMessageHandler = this.findClientMessageHandler(pClientIP, pClientPort);
|
||||||
|
if (aMessageHandler != null)
|
||||||
|
aMessageHandler.send(pMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendToAll(String pMessage)
|
||||||
|
{
|
||||||
|
synchronized(messageHandlers)
|
||||||
|
{
|
||||||
|
messageHandlers.toFirst();
|
||||||
|
while (messageHandlers.hasAccess())
|
||||||
|
{
|
||||||
|
messageHandlers.getContent().send(pMessage);
|
||||||
|
messageHandlers.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void closeConnection(String pClientIP, int pClientPort)
|
||||||
|
{
|
||||||
|
ClientMessageHandler aMessageHandler = findClientMessageHandler(pClientIP, pClientPort);
|
||||||
|
if (aMessageHandler != null)
|
||||||
|
{
|
||||||
|
processClosingConnection(pClientIP, pClientPort);
|
||||||
|
aMessageHandler.close();
|
||||||
|
removeClientMessageHandler(aMessageHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close()
|
||||||
|
{
|
||||||
|
connectionHandler.close();
|
||||||
|
|
||||||
|
synchronized(messageHandlers)
|
||||||
|
{
|
||||||
|
ClientMessageHandler aMessageHandler;
|
||||||
|
messageHandlers.toFirst();
|
||||||
|
while (messageHandlers.hasAccess())
|
||||||
|
{
|
||||||
|
aMessageHandler = messageHandlers.getContent();
|
||||||
|
processClosingConnection(aMessageHandler.getClientIP(), aMessageHandler.getClientPort());
|
||||||
|
aMessageHandler.close();
|
||||||
|
messageHandlers.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
public abstract void processNewConnection(String pClientIP, int pClientPort);
|
||||||
|
|
||||||
|
public abstract void processMessage(String pClientIP, int pClientPort, String pMessage);
|
||||||
|
|
||||||
|
public abstract void processClosingConnection(String pClientIP, int pClientPort);
|
||||||
|
|
||||||
|
private void addNewClientMessageHandler(Socket pClientSocket)
|
||||||
|
{
|
||||||
|
synchronized(messageHandlers)
|
||||||
|
{
|
||||||
|
messageHandlers.append(new ClientMessageHandler(pClientSocket));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeClientMessageHandler(ClientMessageHandler pClientMessageHandler)
|
||||||
|
{
|
||||||
|
synchronized(messageHandlers)
|
||||||
|
{
|
||||||
|
messageHandlers.toFirst();
|
||||||
|
while (messageHandlers.hasAccess())
|
||||||
|
{
|
||||||
|
if (pClientMessageHandler == messageHandlers.getContent())
|
||||||
|
{
|
||||||
|
messageHandlers.remove();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
messageHandlers.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ClientMessageHandler findClientMessageHandler(String pClientIP, int pClientPort)
|
||||||
|
{
|
||||||
|
synchronized(messageHandlers)
|
||||||
|
{
|
||||||
|
ClientMessageHandler aMessageHandler;
|
||||||
|
messageHandlers.toFirst();
|
||||||
|
|
||||||
|
while (messageHandlers.hasAccess())
|
||||||
|
{
|
||||||
|
aMessageHandler = messageHandlers.getContent();
|
||||||
|
if (aMessageHandler.getClientIP().equals(pClientIP) && aMessageHandler.getClientPort() == pClientPort)
|
||||||
|
return (aMessageHandler);
|
||||||
|
messageHandlers.next();
|
||||||
|
}
|
||||||
|
return (null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
128
src/test/java/schule/ngb/zm/util/abi/Stack.java
Executable file
128
src/test/java/schule/ngb/zm/util/abi/Stack.java
Executable file
@@ -0,0 +1,128 @@
|
|||||||
|
package schule.ngb.zm.util.abi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Materialien zu den zentralen NRW-Abiturpruefungen im Fach Informatik ab 2018
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Generische Klasse Stack<ContentType>
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Objekte der generischen Klasse Stack (Keller, Stapel) verwalten beliebige
|
||||||
|
* Objekte vom Typ ContentType nach dem Last-In-First-Out-Prinzip, d.h., das
|
||||||
|
* zuletzt abgelegte Objekt wird als erstes wieder entnommen. Alle Methoden
|
||||||
|
* haben eine konstante Laufzeit, unabhaengig von der Anzahl der verwalteten
|
||||||
|
* Objekte.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author Qualitaets- und UnterstuetzungsAgentur - Landesinstitut fuer Schule
|
||||||
|
* @version Generisch_02 2014-02-21
|
||||||
|
*/
|
||||||
|
public class Stack<ContentType> {
|
||||||
|
|
||||||
|
/* --------- Anfang der privaten inneren Klasse -------------- */
|
||||||
|
|
||||||
|
private class StackNode {
|
||||||
|
|
||||||
|
private ContentType content = null;
|
||||||
|
private StackNode nextNode = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ein neues Objekt vom Typ StackNode<ContentType> wird erschaffen. <br />
|
||||||
|
* Der Inhalt wird per Parameter gesetzt. Der Verweis ist leer.
|
||||||
|
*
|
||||||
|
* @param pContent der Inhalt des Knotens
|
||||||
|
*/
|
||||||
|
public StackNode(ContentType pContent) {
|
||||||
|
content = pContent;
|
||||||
|
nextNode = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der Verweis wird auf das Objekt, das als Parameter uebergeben wird,
|
||||||
|
* gesetzt.
|
||||||
|
*
|
||||||
|
* @param pNext der Nachfolger des Knotens
|
||||||
|
*/
|
||||||
|
public void setNext(StackNode pNext) {
|
||||||
|
nextNode = pNext;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return das Objekt, auf das der aktuelle Verweis zeigt
|
||||||
|
*/
|
||||||
|
public StackNode getNext() {
|
||||||
|
return nextNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return das Inhaltsobjekt vom Typ ContentType
|
||||||
|
*/
|
||||||
|
public ContentType getContent() {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------- Ende der privaten inneren Klasse -------------- */
|
||||||
|
|
||||||
|
private StackNode head;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ein leerer Stapel wird erzeugt. Objekte, die in diesem Stapel verwaltet
|
||||||
|
* werden, muessen vom Typ ContentType sein.
|
||||||
|
*/
|
||||||
|
public Stack() {
|
||||||
|
head = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert den Wert true, wenn der Stapel keine Objekte
|
||||||
|
* enthaelt, sonst liefert sie den Wert false.
|
||||||
|
*
|
||||||
|
* @return true, falls der Stapel leer ist, sonst false
|
||||||
|
*/
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return (head == null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Das Objekt pContentType wird oben auf den Stapel gelegt. Falls
|
||||||
|
* pContentType gleich null ist, bleibt der Stapel unveraendert.
|
||||||
|
*
|
||||||
|
* @param pContent
|
||||||
|
* das einzufuegende Objekt vom Typ ContentType
|
||||||
|
*/
|
||||||
|
public void push(ContentType pContent) {
|
||||||
|
if (pContent != null) {
|
||||||
|
StackNode node = new StackNode(pContent);
|
||||||
|
node.setNext(head);
|
||||||
|
head = node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Das zuletzt eingefuegte Objekt wird von dem Stapel entfernt. Falls der
|
||||||
|
* Stapel leer ist, bleibt er unveraendert.
|
||||||
|
*/
|
||||||
|
public void pop() {
|
||||||
|
if (!isEmpty()) {
|
||||||
|
head = head.getNext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert das oberste Stapelobjekt. Der Stapel bleibt
|
||||||
|
* unveraendert. Falls der Stapel leer ist, wird null zurueckgegeben.
|
||||||
|
*
|
||||||
|
* @return das oberste Stackelement vom Typ ContentType oder null, falls
|
||||||
|
* der Stack leer ist
|
||||||
|
*/
|
||||||
|
public ContentType top() {
|
||||||
|
if (!this.isEmpty()) {
|
||||||
|
return head.getContent();
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
53
src/test/java/schule/ngb/zm/util/abi/Vertex.java
Executable file
53
src/test/java/schule/ngb/zm/util/abi/Vertex.java
Executable file
@@ -0,0 +1,53 @@
|
|||||||
|
package schule.ngb.zm.util.abi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Materialien zu den zentralen NRW-Abiturpruefungen im Fach Informatik ab 2018
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Klasse Vertex
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Die Klasse Vertex stellt einen einzelnen Knoten eines Graphen dar. Jedes Objekt
|
||||||
|
* dieser Klasse verfuegt ueber eine im Graphen eindeutige ID als String und kann diese
|
||||||
|
* ID zurueckliefern. Darueber hinaus kann eine Markierung gesetzt und abgefragt werden.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author Qualitaets- und UnterstuetzungsAgentur - Landesinstitut fuer Schule
|
||||||
|
* @version Oktober 2015
|
||||||
|
*/
|
||||||
|
public class Vertex{
|
||||||
|
//Einmalige ID des Knotens und Markierung
|
||||||
|
private String id;
|
||||||
|
private boolean mark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ein neues Objekt vom Typ Vertex wird erstellt. Seine Markierung hat den Wert false.
|
||||||
|
*/
|
||||||
|
public Vertex(String pID){
|
||||||
|
id = pID;
|
||||||
|
mark = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert die ID des Knotens als String.
|
||||||
|
*/
|
||||||
|
public String getID(){
|
||||||
|
return new String(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der Auftrag setzt die Markierung des Knotens auf den Wert pMark.
|
||||||
|
*/
|
||||||
|
public void setMark(boolean pMark){
|
||||||
|
mark = pMark;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert true, wenn die Markierung des Knotens den Wert true hat, ansonsten false.
|
||||||
|
*/
|
||||||
|
public boolean isMarked(){
|
||||||
|
return mark;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
165
src/test/java/schule/ngb/zm/util/test/ImageAssertions.java
Normal file
165
src/test/java/schule/ngb/zm/util/test/ImageAssertions.java
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
package schule.ngb.zm.util.test;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.opentest4j.AssertionFailedError;
|
||||||
|
import schule.ngb.zm.util.io.ImageLoader;
|
||||||
|
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
public final class ImageAssertions {
|
||||||
|
|
||||||
|
private static boolean SAVE_DIFF_IMAGE_ON_FAIL = false;
|
||||||
|
|
||||||
|
private static File DIFF_IMAGE_PATH = new File("build/test-results/diff");
|
||||||
|
|
||||||
|
private static AssertionFailedError ASSERTION_FAILED_ERROR = null;
|
||||||
|
|
||||||
|
|
||||||
|
public static boolean isSaveDiffImageOnFail() {
|
||||||
|
return SAVE_DIFF_IMAGE_ON_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final void setSaveDiffImageOnFail( boolean saveOnFail ) {
|
||||||
|
SAVE_DIFF_IMAGE_ON_FAIL = saveOnFail;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static File getDiffImagePath() {
|
||||||
|
return DIFF_IMAGE_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void assertEquals( BufferedImage expected, BufferedImage actual ) {
|
||||||
|
assertEquals(expected, actual, () -> "Actual image differs from expected buffer.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void assertEquals( BufferedImage expected, BufferedImage actual, String message ) {
|
||||||
|
assertEquals(expected, actual, () -> message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void assertEquals( BufferedImage expected, BufferedImage actual, Supplier<String> messageSupplier ) {
|
||||||
|
// Compare image dimensions
|
||||||
|
int expectedHeight = expected.getHeight(), expectedWidth = expected.getWidth();
|
||||||
|
int actualHeight = actual.getHeight(), actualWidth = actual.getWidth();
|
||||||
|
try {
|
||||||
|
Assertions.assertEquals(expectedHeight, actualHeight);
|
||||||
|
Assertions.assertEquals(expectedWidth, actualWidth);
|
||||||
|
} catch( AssertionFailedError afe ) {
|
||||||
|
ASSERTION_FAILED_ERROR = afe;
|
||||||
|
fail(expected, actual, messageSupplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Fix comparison of transparent pixels
|
||||||
|
for( int x = 0; x < actualWidth; x++ ) {
|
||||||
|
for( int y = 0; y < actualHeight; y++ ) {
|
||||||
|
try {
|
||||||
|
Assertions.assertTrue(comparePixels(expected.getRGB(x, y), actual.getRGB(x, y)));
|
||||||
|
} catch( AssertionFailedError afe ) {
|
||||||
|
ASSERTION_FAILED_ERROR = afe;
|
||||||
|
fail(expected, actual, messageSupplier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void assertNotEquals( BufferedImage expected, BufferedImage actual ) {
|
||||||
|
assertNotEquals(expected, actual, () -> "Actual image is the same as expected buffer.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void assertNotEquals( BufferedImage expected, BufferedImage actual, String message ) {
|
||||||
|
assertNotEquals(expected, actual, () -> message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void assertNotEquals( BufferedImage expected, BufferedImage actual, Supplier<String> messageSupplier ) {
|
||||||
|
// Compare image dimensions
|
||||||
|
int expectedHeight = expected.getHeight(), expectedWidth = expected.getWidth();
|
||||||
|
int actualHeight = actual.getHeight(), actualWidth = actual.getWidth();
|
||||||
|
if( expectedHeight != actualHeight || expectedWidth != actualWidth ) {
|
||||||
|
// Image dimensions differ, assertion is true
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for( int x = 0; x < actualWidth; x++ ) {
|
||||||
|
for( int y = 0; y < actualHeight; y++ ) {
|
||||||
|
if( !comparePixels(expected.getRGB(x, y), actual.getRGB(x, y)) ) {
|
||||||
|
// Found different pixels, assertion is true
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Images are the same, fail without diff
|
||||||
|
fail(expected, actual, messageSupplier, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void fail( BufferedImage expected, BufferedImage actual, Supplier<String> messageSupplier ) {
|
||||||
|
fail(expected, actual, messageSupplier, SAVE_DIFF_IMAGE_ON_FAIL);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void fail( BufferedImage expected, BufferedImage actual, Supplier<String> messageSupplier, boolean saveDiffImage ) {
|
||||||
|
if( saveDiffImage ) {
|
||||||
|
saveDiffImage(expected, actual);
|
||||||
|
}
|
||||||
|
throw new AssertionFailedError(
|
||||||
|
messageSupplier != null ? messageSupplier.get() : null,
|
||||||
|
ASSERTION_FAILED_ERROR
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean comparePixels( int a, int b ) {
|
||||||
|
// TODO: Fix comparison of transparent pixels
|
||||||
|
return a == b || ((0xFF000000 & a) == 0 && (0xFF000000 & b) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BufferedImage createDiffImage( BufferedImage expected, BufferedImage actual ) {
|
||||||
|
// Error color (white)
|
||||||
|
int errorColor = 0xFF00FF;
|
||||||
|
|
||||||
|
int expectedHeight = expected.getHeight(), expectedWidth = expected.getWidth();
|
||||||
|
int actualHeight = actual.getHeight(), actualWidth = actual.getWidth();
|
||||||
|
int maxHeight = Math.max(expectedHeight, actualHeight), maxWidth = Math.max(expectedWidth, actualWidth);
|
||||||
|
|
||||||
|
BufferedImage diff = ImageLoader.createImage(maxWidth, maxHeight);
|
||||||
|
for( int x = 0; x < maxWidth; x++ ) {
|
||||||
|
for( int y = 0; y < maxHeight; y++ ) {
|
||||||
|
diff.setRGB(x, y, 0);
|
||||||
|
if( x > actualWidth || y > actualHeight || x > expectedWidth || y > expectedHeight ) {
|
||||||
|
// Set overflow pixels to error color
|
||||||
|
diff.setRGB(x, y, errorColor);
|
||||||
|
} else if( !comparePixels(actual.getRGB(x, y), expected.getRGB(x, y)) ) {
|
||||||
|
// Set differences to error color
|
||||||
|
// If both pixels are transparent, the color dows not matter ...
|
||||||
|
// TODO: saturate error color based on how different the colors are?
|
||||||
|
diff.setRGB(x, y, errorColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return diff;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean saveDiffImage( BufferedImage expected, BufferedImage actual ) {
|
||||||
|
BufferedImage diff = createDiffImage(expected, actual);
|
||||||
|
try {
|
||||||
|
File diffFile = new File(DIFF_IMAGE_PATH, makeDiffName());
|
||||||
|
if( !diffFile.getParentFile().exists() ) {
|
||||||
|
diffFile.mkdirs();
|
||||||
|
}
|
||||||
|
ImageLoader.saveImage(diff, diffFile);
|
||||||
|
} catch( IOException ioe ) {
|
||||||
|
// We fail anyways at this point
|
||||||
|
// TODO: Log something?
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String makeDiffName() {
|
||||||
|
return System.currentTimeMillis() + ".png";
|
||||||
|
}
|
||||||
|
|
||||||
|
private ImageAssertions() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
25
src/test/java/schule/ngb/zm/util/test/TestEnv.java
Normal file
25
src/test/java/schule/ngb/zm/util/test/TestEnv.java
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package schule.ngb.zm.util.test;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.extension.ExtensionContext;
|
||||||
|
import org.junit.jupiter.api.extension.ParameterContext;
|
||||||
|
import org.junit.jupiter.api.extension.ParameterResolutionException;
|
||||||
|
import org.junit.jupiter.api.extension.ParameterResolver;
|
||||||
|
import schule.ngb.zm.Testmaschine;
|
||||||
|
import schule.ngb.zm.Zeichenmaschine;
|
||||||
|
|
||||||
|
public class TestEnv implements ParameterResolver {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsParameter( ParameterContext parameterContext, ExtensionContext extensionContext ) throws ParameterResolutionException {
|
||||||
|
return (
|
||||||
|
parameterContext.getParameter().getType() == Zeichenmaschine.class ||
|
||||||
|
parameterContext.getParameter().getType() == Testmaschine.class
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object resolveParameter( ParameterContext parameterContext, ExtensionContext extensionContext ) throws ParameterResolutionException {
|
||||||
|
return new Testmaschine();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user