mirror of
https://github.com/jneug/zeichenmaschine.git
synced 2026-10-12 18:05:17 +02:00
Compare commits
58 Commits
c7e1eb11ed
...
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 | |||
| d500c130ed | |||
| b4d390cd9b | |||
| 97ea610f34 | |||
| e2e1f24e3e | |||
| a09b956b48 | |||
| c92a4517b3 | |||
| 1260a38bb7 | |||
| 20772da813 | |||
| 8898d6e8ee | |||
| cb0ee9c842 | |||
| 3cf7871591 |
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/
|
||||||
|
|
||||||
|
|||||||
27
CHANGELOG.md
27
CHANGELOG.md
@@ -6,6 +6,33 @@ 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
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- `Faker`-Klasse zur Erzeugung von Fake-Daten hinzugefügt.
|
||||||
|
- Dokumentation unter [zeichenmaschine.xyz](https://zeichenmaschine.xyz) mit
|
||||||
|
[MkDocs](https://www.mkdocs.org) und [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/).
|
||||||
|
- Neue `image` methoden im `DrawingLayer`.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- `FilledShape` und `StrokedShape` durch `Fillable` und `Strokeable` Interfaces ersetzt.
|
- `FilledShape` und `StrokedShape` durch `Fillable` und `Strokeable` Interfaces ersetzt.
|
||||||
- `Shape` erweitert nun `BasisDrawable` als abstrakte Grundlage.
|
- `Shape` erweitert nun `BasisDrawable` als abstrakte Grundlage.
|
||||||
|
|||||||
116
build.gradle
116
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.32-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,15 +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
|
||||||
|
}
|
||||||
|
|
||||||
|
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,4 +1,3 @@
|
|||||||
|
|
||||||
<figure markdown>
|
<figure markdown>
|
||||||
{ width=128 }
|
{ width=128 }
|
||||||
</figure>
|
</figure>
|
||||||
@@ -9,10 +8,11 @@ Informatikunterricht.</h2>
|
|||||||
|
|
||||||
## Projektidee
|
## Projektidee
|
||||||
|
|
||||||
Die **Zeichenmaschine** ist eine für den Informatikunterricht entwickelte Bibliothek,
|
Die **Zeichenmaschine** ist eine für den Informatikunterricht entwickelte
|
||||||
die unter anderem an [Processing](https://processing.org/) angelehnt ist. Die
|
Bibliothek, die unter anderem an [Processing](https://processing.org/) angelehnt
|
||||||
Bibliothek soll einige der üblichen Anfängerschwierigkeiten mit Java vereinfachen
|
ist. Die Bibliothek soll einige der üblichen Anfängerschwierigkeiten mit Java
|
||||||
und für Schülerinnen und Schüler im Unterricht nutzbar machen.
|
vereinfachen und grafische Ausgaben für Schülerinnen und Schüler im Unterricht
|
||||||
|
leichter nutzbar machen.
|
||||||
|
|
||||||
!!! warning
|
!!! warning
|
||||||
|
|
||||||
@@ -29,15 +29,43 @@ und für Schülerinnen und Schüler im Unterricht nutzbar machen.
|
|||||||
(Gleiches gilt für diese Webseite zum Projekt.)
|
(Gleiches gilt für diese Webseite zum Projekt.)
|
||||||
|
|
||||||
## Dokumentation
|
## Dokumentation
|
||||||
* [Schnellstart](quickstart.md)
|
|
||||||
* [Installation](install.md)
|
* [Schnellstart](schnellstart.md)
|
||||||
* {{ javadoc_link() }}
|
* [Installation](installation.md)
|
||||||
|
* [Javadoc]({{ javadoc() }})
|
||||||
|
|
||||||
## Über die Zeichenmaschine
|
## Ü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
|
!!! info
|
||||||
|
|
||||||
In der Zeichenmaschine werden bewusst nur englischsprachige Bezeichner für
|
In der Zeichenmaschine werden bewusst nur englischsprachige Bezeichner für
|
||||||
Klassen, Methoden und Variablen verwendet. Ausnahme sind einzelne Klassen,
|
Klassen, Methoden und Variablen verwendet. Ausnahme sind einzelne Klassen,
|
||||||
die im Zusammnehang mit dem Namen der Bibliothek stehen, wie die
|
die im Zusammnehang mit dem Namen der Bibliothek stehen, wie die
|
||||||
Hauptklasse `Zeichenmaschine`.
|
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
|
||||||
|
|
||||||
@@ -42,3 +41,5 @@ Projekt in den Classpath aufgenommen werden:
|
|||||||
### NetBeans
|
### NetBeans
|
||||||
|
|
||||||
> Coming soon
|
> Coming soon
|
||||||
|
|
||||||
|
## Unterstützung für MP3
|
||||||
@@ -50,7 +50,6 @@ def define_env(env):
|
|||||||
# _target = re.sub(r"([^(][^,]*?\.)*?([^)]+)", lambda m: m.group(2), target)
|
# _target = re.sub(r"([^(][^,]*?\.)*?([^)]+)", lambda m: m.group(2), target)
|
||||||
_target = target
|
_target = target
|
||||||
|
|
||||||
print(f'{_target.strip()=}')
|
|
||||||
if m := re.match(r'^(.+?)\((.*)\)$', _target):
|
if m := re.match(r'^(.+?)\((.*)\)$', _target):
|
||||||
if strip_params and m.group(2):
|
if strip_params and m.group(2):
|
||||||
params = m.group(2).split(',')
|
params = m.group(2).split(',')
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ die [JAR-Datei der aktuellen Version](https://github.com/jneug/zeichenmaschine/r
|
|||||||
herunterzuladen und
|
herunterzuladen und
|
||||||
dem [Classpath](https://www.delftstack.com/de/howto/java/java-classpath-/)
|
dem [Classpath](https://www.delftstack.com/de/howto/java/java-classpath-/)
|
||||||
hinzuzufügen. Eine Beschreibung für verschiedene Entwicklungsumgebungen findet
|
hinzuzufügen. Eine Beschreibung für verschiedene Entwicklungsumgebungen findet
|
||||||
sich im Abschnitt [Installation](install.md).
|
sich im Abschnitt [Installation](installation.md).
|
||||||
|
|
||||||
## Die Basisklasse
|
## Die Basisklasse
|
||||||
|
|
||||||
@@ -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).
|
||||||
16
mkdocs.yml
16
mkdocs.yml
@@ -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,13 +37,13 @@ extra_css:
|
|||||||
- assets/zmstyles.css
|
- assets/zmstyles.css
|
||||||
|
|
||||||
nav:
|
nav:
|
||||||
- Einführung: index.md
|
- Einführung: index.md
|
||||||
- Schnellstart: quickstart.md
|
- Schnellstart: schnellstart.md
|
||||||
- Installation: install.md
|
- Installation: installation.md
|
||||||
- Tutorials:
|
- Tutorials:
|
||||||
- Aquarium: tutorials/aquarium/aquarium1.md
|
- Aquarium: tutorials/aquarium/aquarium1.md
|
||||||
- Beispiele:
|
- Beispiele:
|
||||||
- Mondrian: beispiele/mondrian.md
|
- Mondrian: beispiele/mondrian.md
|
||||||
|
|
||||||
markdown_extensions:
|
markdown_extensions:
|
||||||
- admonition
|
- admonition
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package schule.ngb.zm;
|
package schule.ngb.zm;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.MultipleGradientPaint;
|
||||||
|
import java.awt.Paint;
|
||||||
|
import java.awt.Stroke;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Basisimplementierung der {@link Strokeable} und {@link Fillable} APIs.
|
* Basisimplementierung der {@link Strokeable} und {@link Fillable} APIs.
|
||||||
@@ -31,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
|
||||||
@@ -50,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
|
||||||
|
|
||||||
@@ -90,9 +99,6 @@ public abstract class BasicDrawable extends Constants implements Strokeable, Fil
|
|||||||
|
|
||||||
// Implementierung Fillable Interface
|
// Implementierung Fillable Interface
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void setFill( Paint fill ) {
|
public void setFill( Paint fill ) {
|
||||||
if( fill == null ) {
|
if( fill == null ) {
|
||||||
@@ -106,9 +112,6 @@ public abstract class BasicDrawable extends Constants implements Strokeable, Fil
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public Paint getFill() {
|
public Paint getFill() {
|
||||||
if( fill != null ) {
|
if( fill != null ) {
|
||||||
@@ -120,42 +123,27 @@ public abstract class BasicDrawable extends Constants implements Strokeable, Fil
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasFillColor() {
|
public boolean hasFillColor() {
|
||||||
return fillColor != null;
|
return fillColor != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasGradient() {
|
public boolean hasGradient() {
|
||||||
return fill != null;
|
return fill != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public Color getFillColor() {
|
public Color getFillColor() {
|
||||||
return fillColor;
|
return fillColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void setFillColor( Color color ) {
|
public void setFillColor( Color color ) {
|
||||||
fillColor = color;
|
fillColor = color;
|
||||||
fill = null;
|
fill = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public MultipleGradientPaint getGradient() {
|
public MultipleGradientPaint getGradient() {
|
||||||
return fill;
|
return fill;
|
||||||
@@ -164,73 +152,60 @@ public abstract class BasicDrawable extends Constants implements Strokeable, Fil
|
|||||||
|
|
||||||
// Implementierung Strokeable Interface
|
// Implementierung Strokeable Interface
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void setStroke( Stroke stroke ) {
|
public void setStroke( Stroke stroke ) {
|
||||||
this.stroke = stroke;
|
this.stroke = stroke;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public Color getStrokeColor() {
|
public Color getStrokeColor() {
|
||||||
return strokeColor;
|
return strokeColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void setStrokeColor( Color color ) {
|
public void setStrokeColor( Color color ) {
|
||||||
strokeColor = color;
|
strokeColor = color;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public double getStrokeWeight() {
|
public double getStrokeWeight() {
|
||||||
return strokeWeight;
|
return strokeWeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void setStrokeWeight( double weight ) {
|
public void setStrokeWeight( double weight ) {
|
||||||
strokeWeight = weight;
|
strokeWeight = weight;
|
||||||
this.stroke = null;
|
this.stroke = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public Options.StrokeType getStrokeType() {
|
public Options.StrokeType getStrokeType() {
|
||||||
return strokeType;
|
return strokeType;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void setStrokeType( Options.StrokeType type ) {
|
public void setStrokeType( Options.StrokeType type ) {
|
||||||
strokeType = type;
|
strokeType = type;
|
||||||
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.
|
||||||
*/
|
*/
|
||||||
@@ -1256,7 +1271,7 @@ public class Constants {
|
|||||||
public static final double distance( double fromX, double fromY, double toX, double toY ) {
|
public static final double distance( double fromX, double fromY, double toX, double toY ) {
|
||||||
double diffX = toX - fromX;
|
double diffX = toX - fromX;
|
||||||
double diffY = toY - fromY;
|
double diffY = toY - fromY;
|
||||||
return sqrt(diffX*diffX + diffY*diffY);
|
return sqrt(diffX * diffX + diffY * diffY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -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.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -88,7 +88,11 @@ public abstract class Layer extends Constants implements Drawable, Updatable {
|
|||||||
} else {
|
} else {
|
||||||
createCanvas(width, height);
|
createCanvas(width, height);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: prevent access to graphics context?
|
||||||
|
public Graphics2D getGraphics() {
|
||||||
|
return this.drawing;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -165,9 +169,6 @@ public abstract class Layer extends Constants implements Drawable, Updatable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isVisible() {
|
public boolean isVisible() {
|
||||||
return visible;
|
return visible;
|
||||||
@@ -197,16 +198,10 @@ public abstract class Layer extends Constants implements Drawable, Updatable {
|
|||||||
visible = !visible;
|
visible = !visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void update( double delta ) {
|
public void update( double delta ) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isActive() {
|
public boolean isActive() {
|
||||||
return active;
|
return active;
|
||||||
|
|||||||
@@ -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,7 +194,11 @@ public class Zeichenfenster extends JFrame {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setIconImages(icons);
|
if( icons.isEmpty() ) {
|
||||||
|
LOG.warn("Could not load dock-icon");
|
||||||
|
} else {
|
||||||
|
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());
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return anims.get(anims.size() - 1).getAnimationTarget();
|
if( this.finished ) {
|
||||||
|
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;
|
|
||||||
this.stop();
|
|
||||||
}
|
}
|
||||||
|
if( this.finished ) {
|
||||||
while( active < anims.size() && elapsedTime >= active * lag ) {
|
return anims.get(anims.size() - 1).getEasing();
|
||||||
anims.get(active).start();
|
} else {
|
||||||
active += 1;
|
return anims.get(0).getEasing();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for( int i = 0; i < active; i++ ) {
|
// @Override
|
||||||
double t = 0.0;
|
// public void update( double delta ) {
|
||||||
if( overrideRuntime > 0 ) {
|
// elapsedTime += (int) (delta * 1000);
|
||||||
t = (double) (elapsedTime - i*lag) / (double) overrideRuntime;
|
//
|
||||||
} else {
|
// // Animation is done. Stop all Animations.
|
||||||
t = (double) (elapsedTime - i*lag) / (double) anims.get(i).getRuntime();
|
// 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();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// 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);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
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);
|
@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;
|
||||||
@@ -57,9 +57,6 @@ public class ColorLayer extends Layer {
|
|||||||
clear();
|
clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void setSize( int width, int height ) {
|
public void setSize( int width, int height ) {
|
||||||
super.setSize(width, height);
|
super.setSize(width, height);
|
||||||
|
|||||||
@@ -87,9 +87,6 @@ public class DrawableLayer extends Layer {
|
|||||||
this.clearBeforeDraw = pClearBeforeDraw;
|
this.clearBeforeDraw = pClearBeforeDraw;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void draw( Graphics2D graphics ) {
|
public void draw( Graphics2D graphics ) {
|
||||||
if( clearBeforeDraw ) {
|
if( clearBeforeDraw ) {
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
package schule.ngb.zm.layers;
|
package schule.ngb.zm.layers;
|
||||||
|
|
||||||
import schule.ngb.zm.Color;
|
import schule.ngb.zm.Color;
|
||||||
import schule.ngb.zm.Layer;
|
import schule.ngb.zm.*;
|
||||||
import schule.ngb.zm.Options;
|
|
||||||
import schule.ngb.zm.Fillable;
|
|
||||||
import schule.ngb.zm.Strokeable;
|
|
||||||
import schule.ngb.zm.shapes.Text;
|
import schule.ngb.zm.shapes.Text;
|
||||||
import schule.ngb.zm.util.io.ImageLoader;
|
import schule.ngb.zm.util.io.ImageLoader;
|
||||||
|
|
||||||
@@ -407,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}.
|
||||||
@@ -418,9 +420,6 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
|
|||||||
shapeDelegate.setStrokeType(type);
|
shapeDelegate.setStrokeType(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public Stroke getStroke() {
|
public Stroke getStroke() {
|
||||||
return shapeDelegate.getStroke();
|
return shapeDelegate.getStroke();
|
||||||
@@ -929,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() {
|
||||||
path.closePath();
|
endShape(CLOSED);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void endShape( Options.PathType closingType ) {
|
||||||
|
if( closingType == Options.PathType.CLOSED ) {
|
||||||
|
path.closePath();
|
||||||
|
}
|
||||||
path.trimToSize();
|
path.trimToSize();
|
||||||
pathStarted = false;
|
pathStarted = false;
|
||||||
|
|
||||||
@@ -944,7 +974,7 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Zeichnet das Bild von der angegebenen Quelle an der angegebenen Position
|
* Zeichnet das Bild von der angegebenen Quelle an der angegebenen Position
|
||||||
* auf dei Zeichenebene.
|
* auf die Zeichenebene.
|
||||||
* <p>
|
* <p>
|
||||||
* Die Bildquelle wird mithilfe von {@link ImageLoader#loadImage(String)}
|
* Die Bildquelle wird mithilfe von {@link ImageLoader#loadImage(String)}
|
||||||
* geladen. Schlägt dies fehl, wird nichts gezeichnet.
|
* geladen. Schlägt dies fehl, wird nichts gezeichnet.
|
||||||
@@ -952,14 +982,15 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
|
|||||||
* @param imageSource Die Bildquelle.
|
* @param imageSource Die Bildquelle.
|
||||||
* @param x x-Koordinate des Ankerpunktes.
|
* @param x x-Koordinate des Ankerpunktes.
|
||||||
* @param y y-Koordinate des Ankerpunktes.
|
* @param y y-Koordinate des Ankerpunktes.
|
||||||
|
* @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());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Zeichnet das Bild von der angegebenen Quelle an der angegebenen Position
|
* Zeichnet das Bild von der angegebenen Quelle an der angegebenen Position
|
||||||
* auf dei Zeichenebene.
|
* auf die Zeichenebene.
|
||||||
* <p>
|
* <p>
|
||||||
* Die Bildquelle wird mithilfe von {@link ImageLoader#loadImage(String)}
|
* Die Bildquelle wird mithilfe von {@link ImageLoader#loadImage(String)}
|
||||||
* geladen. Schlägt dies fehl, wird nichts gezeichnet.
|
* geladen. Schlägt dies fehl, wird nichts gezeichnet.
|
||||||
@@ -967,56 +998,95 @@ public class DrawingLayer extends Layer implements Strokeable, Fillable {
|
|||||||
* @param imageSource Die Bildquelle.
|
* @param imageSource Die Bildquelle.
|
||||||
* @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 anchor
|
* @param anchor Der Ankerpunkt.
|
||||||
|
* @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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param imageSource
|
* Zeichnet das Bild von der angegebenen Quelle an den angegebenen
|
||||||
* @param x
|
* Koordinaten auf die Zeichenebene. Das Bild wird um den angegebenen Faktor
|
||||||
* @param y
|
* skaliert.
|
||||||
* @param scale
|
* <p>
|
||||||
|
* Siehe
|
||||||
|
* {@link #imageScale(Image, double, double, double, Options.Direction)} für
|
||||||
|
* mehr Details.
|
||||||
|
*
|
||||||
|
* @param imageSource Die Bildquelle.
|
||||||
|
* @param x x-Koordinate des Ankerpunktes.
|
||||||
|
* @param y y-Koordinate des Ankerpunktes.
|
||||||
|
* @param scale Der Skalierungsfaktor des Bildes.
|
||||||
|
* @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());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param imageSource
|
* Zeichnet das Bild von der angegebenen Quelle an den angegebenen
|
||||||
* @param x
|
* Koordinaten auf die Zeichenebene. Das Bild wird um den angegebenen Faktor
|
||||||
* @param y
|
* skaliert und der angegebene Ankerpunkt verwendet.
|
||||||
* @param scale
|
* <p>
|
||||||
* @param anchor
|
* Siehe
|
||||||
|
* {@link #imageScale(Image, double, double, double, Options.Direction)} für
|
||||||
|
* mehr Details.
|
||||||
|
*
|
||||||
|
* @param imageSource Die Bildquelle.
|
||||||
|
* @param x x-Koordinate des Ankerpunktes.
|
||||||
|
* @param y y-Koordinate des Ankerpunktes.
|
||||||
|
* @param scale Der Skalierungsfaktor des Bildes.
|
||||||
|
* @param anchor Der Ankerpunkt.
|
||||||
|
* @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);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param image
|
|
||||||
* @param x
|
|
||||||
* @param y
|
|
||||||
*/
|
|
||||||
public void image( Image image, double x, double y ) {
|
|
||||||
image(image, x, y, 1.0, shapeDelegate.getAnchor());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param image
|
|
||||||
* @param x
|
|
||||||
* @param y
|
|
||||||
* @param scale
|
|
||||||
*/
|
|
||||||
public void image( Image image, double x, double y, double scale ) {
|
|
||||||
image(image, x, y, scale, 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 skaliert und der angegebene Ankerpunkt
|
* Zeichenebene.
|
||||||
* verwendet.
|
*
|
||||||
|
* @param image Das vorher geladene Bild.
|
||||||
|
* @param x x-Koordinate des Ankerpunktes.
|
||||||
|
* @param y y-Koordinate des Ankerpunktes.
|
||||||
|
*/
|
||||||
|
public void image( Image image, double x, double y ) {
|
||||||
|
imageScale(image, x, y, 1.0, shapeDelegate.getAnchor());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zeichnet das angegebene Bild an den angegebenen Koordinaten auf die
|
||||||
|
* Zeichenebene. Das Bild wird um den angegebenen Faktor skaliert.
|
||||||
|
* <p>
|
||||||
|
* Siehe
|
||||||
|
* {@link #imageScale(Image, double, double, double, Options.Direction)} für
|
||||||
|
* mehr Details.
|
||||||
|
*
|
||||||
|
* @param image Das vorher geladene Bild.
|
||||||
|
* @param x x-Koordinate des Ankerpunktes.
|
||||||
|
* @param y y-Koordinate des Ankerpunktes.
|
||||||
|
* @param scale Der Skalierungsfaktor des Bildes.
|
||||||
|
*/
|
||||||
|
public void imageScale( Image image, double x, double y, double scale ) {
|
||||||
|
imageScale(image, x, y, scale, shapeDelegate.getAnchor());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zeichnet das angegebene Bild an den angegebenen Koordinaten auf die
|
||||||
|
* Zeichenebene. Das Bild wird um den angegebenen Faktor skaliert und der
|
||||||
|
* angegebene Ankerpunkt verwendet.
|
||||||
|
* <p>
|
||||||
|
* Bei einem Faktor größer 0 wird das Bild vergrößert, bei einem Faktor
|
||||||
|
* kleiner 0 verkleinert. Bei negativen Werten wird das Bild entlang der x-
|
||||||
|
* bzw. y-Achse gespiegelt.
|
||||||
|
* <p>
|
||||||
|
* Das Seitenverhältnis wird immer beibehalten.
|
||||||
|
* <p>
|
||||||
|
* Soll das Bild innerhalb eines vorgegebenen Rechtecks liegen, sollte
|
||||||
|
* {@link #imageScale(Image, double, double, double, double,
|
||||||
|
* 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.
|
||||||
@@ -1024,17 +1094,263 @@ 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;
|
||||||
Point2D.Double anchorPoint = getOriginPoint(x, y, neww, newh, anchor);
|
Point2D.Double anchorPoint = getOriginPoint(x, y, neww, newh, anchor);
|
||||||
drawing.drawImage(image, (int) anchorPoint.x, (int) anchorPoint.y, (int) neww, (int) newh, null);
|
drawing.drawImage(image, (int) anchorPoint.x, (int) anchorPoint.y, (int) neww, (int) newh, null);
|
||||||
|
}*/
|
||||||
|
double neww = image.getWidth(null) * scale;
|
||||||
|
double newh = image.getHeight(null) * scale;
|
||||||
|
imageScale(image, x, y, neww, newh, anchor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zeichnet das Bild von der angegebenen Quelle an den angegebenen
|
||||||
|
* Koordinaten in der angegebenen Größe auf die Zeichenebene.
|
||||||
|
* <p>
|
||||||
|
* Siehe
|
||||||
|
* {@link #imageScale(Image, double, double, double, double,
|
||||||
|
* Options.Direction)} für mehr Details.
|
||||||
|
*
|
||||||
|
* @param imageSource Die Bildquelle.
|
||||||
|
* @param x x-Koordinate des Ankerpunktes.
|
||||||
|
* @param y y-Koordinate des Ankerpunktes.
|
||||||
|
* @param width Breite des Bildes auf der Zeichenebene oder 0.
|
||||||
|
* @param height Höhe des Bildes auf der Zeichenebene oder 0.
|
||||||
|
* @see ImageLoader#loadImage(String)
|
||||||
|
*/
|
||||||
|
public void imageScale( String imageSource, double x, double y, double width, double height ) {
|
||||||
|
imageScale(ImageLoader.loadImage(imageSource), x, y, width, height, shapeDelegate.getAnchor());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zeichnet das Bild von der angegebenen Quelle an den angegebenen
|
||||||
|
* Koordinaten in der angegebenen Größe auf die Zeichenebene. Es wird der
|
||||||
|
* angegebene Ankerpunkt verwendet.
|
||||||
|
* <p>
|
||||||
|
* Siehe
|
||||||
|
* {@link #imageScale(Image, double, double, double, double,
|
||||||
|
* Options.Direction)} für mehr Details.
|
||||||
|
*
|
||||||
|
* @param imageSource Die Bildquelle.
|
||||||
|
* @param x x-Koordinate des Ankerpunktes.
|
||||||
|
* @param y y-Koordinate des Ankerpunktes.
|
||||||
|
* @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 ImageLoader#loadImage(String)
|
||||||
|
*/
|
||||||
|
public void imageScale( String imageSource, double x, double y, double width, double height, Options.Direction anchor ) {
|
||||||
|
imageScale(ImageLoader.loadImage(imageSource), x, y, width, height, anchor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zeichnet das angegebene Bild an den angegebenen Koordinaten in der
|
||||||
|
* angegebenen Größe auf die Zeichenebene.
|
||||||
|
* <p>
|
||||||
|
* Siehe
|
||||||
|
* {@link #imageScale(Image, double, double, double, double,
|
||||||
|
* Options.Direction)} für mehr Details.
|
||||||
|
*
|
||||||
|
* @param image Ein Bild-Objekt.
|
||||||
|
* @param x x-Koordinate des Ankerpunktes.
|
||||||
|
* @param y y-Koordinate des Ankerpunktes.
|
||||||
|
* @param width Breite des Bildes auf der Zeichenebene oder 0.
|
||||||
|
* @param height Höhe des Bildes auf der Zeichenebene oder 0.
|
||||||
|
*/
|
||||||
|
public void imageScale( Image image, double x, double y, double width, double height ) {
|
||||||
|
imageScale(image, x, y, width, height, shapeDelegate.getAnchor());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zeichnet das angegebene Bild an den angegebenen Koordinaten in der
|
||||||
|
* angegebenen Größe auf die Zeichenebene. Der angegebene Ankerpunkt wird
|
||||||
|
* verwendet.
|
||||||
|
* <p>
|
||||||
|
* Das Bild wird innerhalb eines Rechtecks mit der angegebenen Breite und
|
||||||
|
* Höhe gezeichnet. Dabei wird das Abbild verzerrt, wenn beim Aufruf nicht
|
||||||
|
* auf ein passendes Seitenverhältnis der Werte zueinander geachtet wird.
|
||||||
|
* <p>
|
||||||
|
* Um das Bild auf eine bestimmte Breite oder Höhe festzulegen und die
|
||||||
|
* andere Größe passend zu skalieren, kann einer der Parameter auf 0 gesetzt
|
||||||
|
* werden.
|
||||||
|
* <p>
|
||||||
|
* Soll die Bildgröße unter Beachtung der Abmessungen um einen Faktor
|
||||||
|
* verändert werden, sollte
|
||||||
|
* {@link #imageScale(Image, double, double, double, Options.Direction)}
|
||||||
|
* verwendet werden.
|
||||||
|
*
|
||||||
|
* @param image Ein Bild-Objekt.
|
||||||
|
* @param x x-Koordinate des Ankerpunktes.
|
||||||
|
* @param y y-Koordinate des Ankerpunktes.
|
||||||
|
* @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.
|
||||||
|
*/
|
||||||
|
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?
|
||||||
|
if( image != null ) {
|
||||||
|
AffineTransform orig = drawing.getTransform();
|
||||||
|
|
||||||
|
int imgWidth = image.getWidth(null);
|
||||||
|
int imgHeight = image.getHeight(null);
|
||||||
|
|
||||||
|
if( width == 0 ) {
|
||||||
|
width = (height / imgHeight) * imgWidth;
|
||||||
|
} else if( height == 0 ) {
|
||||||
|
height = (width / imgWidth) * imgHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
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.setTransform(orig);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Git aktuelle verwendete Schriftart.
|
* @return Die aktuell verwendete Schriftart.
|
||||||
*/
|
*/
|
||||||
public Font getFont() {
|
public Font getFont() {
|
||||||
return shapeDelegate.getFont();
|
return shapeDelegate.getFont();
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -1,10 +1,21 @@
|
|||||||
package schule.ngb.zm.media;
|
package schule.ngb.zm.media;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface für Audio-Medien.
|
* Schnittstelle für Audio-Medien.
|
||||||
|
*
|
||||||
|
* <h2>MP3-Dateien verwenden</h2>
|
||||||
|
* Java kann nativ nur Waveform ({@code .wav}) Dateien wiedergeben. Um auch
|
||||||
|
* MP3-Dateien zu nutzen, müssen die Bibliotheken <a href="#">jlayer</a>, <a
|
||||||
|
* href="#">tritonus-share</a> und <a href="#">mp3spi</a> eingebunden werden.
|
||||||
|
* Details zur Verwendung können in der <a
|
||||||
|
* href="https://zeichenmaschine.xyz/installation/#unterstutzung-fur-mp3">Dokumentation
|
||||||
|
* der Zeichenmaschine</a> nachgelesen werden.
|
||||||
*/
|
*/
|
||||||
public interface Audio {
|
public interface Audio {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Die Quelle, aus der das Medium geladen wurde.
|
||||||
|
*/
|
||||||
String getSource();
|
String getSource();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -17,7 +28,7 @@ public interface Audio {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Prüft, ob das Medium gerade in einer Schleife abgespielt wird. Wenn
|
* Prüft, ob das Medium gerade in einer Schleife abgespielt wird. Wenn
|
||||||
* {@code isLooping() == true}, dann muss auch immer
|
* {@code isLooping() == true} gilt, dann muss auch immer
|
||||||
* {@code isPlaying() == true} gelten.
|
* {@code isPlaying() == true} gelten.
|
||||||
*
|
*
|
||||||
* @return {@code true}, wenn das Medium in einer Schleife abgespielt wird,
|
* @return {@code true}, wenn das Medium in einer Schleife abgespielt wird,
|
||||||
@@ -30,7 +41,7 @@ public interface Audio {
|
|||||||
* <p>
|
* <p>
|
||||||
* Die Lautstärke wird auf einer linearen Skale festgelegt, wobei 0 kein Ton
|
* Die Lautstärke wird auf einer linearen Skale festgelegt, wobei 0 kein Ton
|
||||||
* und 1 volle Lautstärke bedeutet. Werte über 1 verstärken den Ton des
|
* und 1 volle Lautstärke bedeutet. Werte über 1 verstärken den Ton des
|
||||||
* Mediums.
|
* Mediums. Negative Werte setzen die Lautstärke aud 0.
|
||||||
*
|
*
|
||||||
* @param volume Die neue Lautstärke zwischen 0 und 1.
|
* @param volume Die neue Lautstärke zwischen 0 und 1.
|
||||||
* @see <a
|
* @see <a
|
||||||
@@ -39,7 +50,7 @@ public interface Audio {
|
|||||||
void setVolume( double volume );
|
void setVolume( double volume );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gibt die aktuelle Lautstärkeeinstellung dieses Mediums zurück.
|
* Liefert die aktuelle Lautstärke dieses Mediums.
|
||||||
* <p>
|
* <p>
|
||||||
* Die Lautstärke wird auf einer linearen Skale angegeben, wobei 0 kein Ton
|
* Die Lautstärke wird auf einer linearen Skale angegeben, wobei 0 kein Ton
|
||||||
* und 1 volle Lautstärke bedeutet. Werte über 1 verstärken den Ton des
|
* und 1 volle Lautstärke bedeutet. Werte über 1 verstärken den Ton des
|
||||||
@@ -50,8 +61,16 @@ public interface Audio {
|
|||||||
double getVolume();
|
double getVolume();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Startet die Wiedergabe des Mediums und beendet die Methode. Das
|
* Startet die Wiedergabe des Mediums. Das Audio-Medium wird einmal
|
||||||
* Audio-Medium wird einmal abgespielt und stoppt dann.
|
* abgespielt und stoppt dann.
|
||||||
|
* <p>
|
||||||
|
* Die Methode beendet sofort und die Wiedergabe erfolgt im Hintergrund.
|
||||||
|
* Soll die Programmausführung erst nach Wiedergabe des Mediums fortgesetzt
|
||||||
|
* werden, sollte {@link #playAndWait()} verwendet werden.
|
||||||
|
* <p>
|
||||||
|
* Soll die Wiedergabe im Hintergrund ablaufen, aber dennoch auf das Ende
|
||||||
|
* reagiert werden, kann ein
|
||||||
|
* {@link #addAudioListener(AudioListener) AudioListener} verwendet werden.
|
||||||
*/
|
*/
|
||||||
void play();
|
void play();
|
||||||
|
|
||||||
@@ -63,28 +82,28 @@ public interface Audio {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Spielt das Medium in einer kontinuierlichen Schleife ab. Die Methode
|
* Spielt das Medium in einer kontinuierlichen Schleife ab. Die Methode
|
||||||
* startet die Wiedergabe und beendet dann direkt die Methode. Um die
|
* startet die Wiedergabe im Hintergrund und beendet dann sofort. Um die
|
||||||
* Wiedergabe zu stoppen muss {@link #stop()} aufgerufen werden.
|
* Wiedergabe zu stoppen, muss {@link #stop()} aufgerufen werden.
|
||||||
*/
|
*/
|
||||||
void loop();
|
void loop();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stoppt die Wiedergabe. Wird das Medium gerade nicht abgespielt
|
* Stoppt die Wiedergabe. Wird das Medium gerade nicht abgespielt
|
||||||
* {@code isPlaying() == false}, dann passiert nichts.
|
* ({@code isPlaying() == false}), dann passiert nichts.
|
||||||
*/
|
*/
|
||||||
void stop();
|
void stop();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stoppt die Wiedergabe und gibt alle Resourcen, die für das Medium
|
* Stoppt die Wiedergabe und gibt alle Ressourcen, die für das Medium
|
||||||
* verwendet werden, frei.
|
* verwendet werden, frei.
|
||||||
*/
|
*/
|
||||||
void dispose();
|
void dispose();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fügt dem Medium das angegbene Objekt als {@code AudioListener} hinzu, der
|
* Fügt dem Medium das angegebene Objekt als {@code AudioListener} hinzu,
|
||||||
* bei Start und Stopp der Wiedergabe informiert werden soll.
|
* der bei Start und Stopp der Wiedergabe informiert wird.
|
||||||
*
|
*
|
||||||
* @param listener Das zu informierende Objekt.
|
* @param listener Das Listener-Objekt.
|
||||||
*/
|
*/
|
||||||
void addAudioListener( AudioListener listener );
|
void addAudioListener( AudioListener listener );
|
||||||
|
|
||||||
|
|||||||
@@ -14,20 +14,20 @@ import schule.ngb.zm.util.events.Listener;
|
|||||||
public interface AudioListener extends Listener<Audio> {
|
public interface AudioListener extends Listener<Audio> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wird aufgerufen, sobald die Wiedergabe eines Audio-Objektes startet, dem
|
* Wird aufgerufen, sobald die Wiedergabe eines Audio-Mediums startet, dem
|
||||||
* dieses Objekt mittels {@link Audio#addAudioListener(AudioListener)}
|
* dieses Objekt mittels {@link Audio#addAudioListener(AudioListener)}
|
||||||
* hinzugefügt wurde.
|
* hinzugefügt wurde.
|
||||||
*
|
*
|
||||||
* @param source Das Audio-Objekt, dessen Wiedergabe gestartet wurde.
|
* @param source Das Audio-Medium, dessen Wiedergabe gestartet wurde.
|
||||||
*/
|
*/
|
||||||
void playbackStarted( Audio source );
|
void playbackStarted( Audio source );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wird aufgerufen, sobald die Wiedergabe eines Audio-Objektes stoppt, dem
|
* Wird aufgerufen, sobald die Wiedergabe eines Audio-Mediums stoppt, dem
|
||||||
* dieses Objekt mittels {@link Audio#addAudioListener(AudioListener)}
|
* dieses Objekt mittels {@link Audio#addAudioListener(AudioListener)}
|
||||||
* hinzugefügt wurde.
|
* hinzugefügt wurde.
|
||||||
*
|
*
|
||||||
* @param source Das Audio-Objekt, dessen Wiedergabe gestoppt wurde.
|
* @param source Das Audio-Medium, dessen Wiedergabe gestoppt wurde.
|
||||||
*/
|
*/
|
||||||
void playbackStopped( Audio source );
|
void playbackStopped( Audio source );
|
||||||
|
|
||||||
|
|||||||
@@ -98,50 +98,32 @@ public class Mixer implements Audio, AudioListener {
|
|||||||
audios.clear();
|
audios.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isPlaying() {
|
public boolean isPlaying() {
|
||||||
return audios.stream().anyMatch(aw -> aw.audio.isPlaying());
|
return audios.stream().anyMatch(aw -> aw.audio.isPlaying());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isLooping() {
|
public boolean isLooping() {
|
||||||
return audios.stream().anyMatch(aw -> aw.audio.isLooping());
|
return audios.stream().anyMatch(aw -> aw.audio.isLooping());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void setVolume( double pVolume ) {
|
public void setVolume( double volume ) {
|
||||||
volume = (float) pVolume;
|
this.volume = volume < 0 ? 0f : (float) volume;
|
||||||
audios.stream().forEach(aw -> aw.audio.setVolume(aw.volumeFactor * pVolume));
|
audios.stream().forEach(aw -> aw.audio.setVolume(aw.volumeFactor * volume));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public double getVolume() {
|
public double getVolume() {
|
||||||
return volume;
|
return volume;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void play() {
|
public void play() {
|
||||||
audios.stream().forEach(aw -> aw.audio.play());
|
audios.stream().forEach(aw -> aw.audio.play());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void playAndWait() {
|
public void playAndWait() {
|
||||||
audios.stream().forEach(aw -> aw.audio.play());
|
audios.stream().forEach(aw -> aw.audio.play());
|
||||||
@@ -154,25 +136,16 @@ public class Mixer implements Audio, AudioListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void loop() {
|
public void loop() {
|
||||||
audios.stream().forEach(aw -> aw.audio.loop());
|
audios.stream().forEach(aw -> aw.audio.loop());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void stop() {
|
public void stop() {
|
||||||
audios.stream().forEach(aw -> aw.audio.stop());
|
audios.stream().forEach(aw -> aw.audio.stop());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
if( isPlaying() ) {
|
if( isPlaying() ) {
|
||||||
@@ -243,7 +216,8 @@ public class Mixer implements Audio, AudioListener {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Interne Methode, um den Listener-Mechanismus zu initialisieren. Wird erst
|
* Interne Methode, um den Listener-Mechanismus zu initialisieren. Wird erst
|
||||||
* aufgerufen, soblad sich auch ein Listener registrieren möchte.
|
* aufgerufen, sobald sich auch ein Listener registrieren möchte.
|
||||||
|
*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
private EventDispatcher<Audio, AudioListener> initializeEventDispatcher() {
|
private EventDispatcher<Audio, AudioListener> initializeEventDispatcher() {
|
||||||
|
|||||||
@@ -1,84 +1,103 @@
|
|||||||
package schule.ngb.zm.media;
|
package schule.ngb.zm.media;
|
||||||
|
|
||||||
import schule.ngb.zm.util.events.EventDispatcher;
|
|
||||||
import schule.ngb.zm.util.tasks.TaskRunner;
|
|
||||||
import schule.ngb.zm.util.Log;
|
import schule.ngb.zm.util.Log;
|
||||||
import schule.ngb.zm.util.io.ResourceStreamProvider;
|
|
||||||
import schule.ngb.zm.util.Validator;
|
import schule.ngb.zm.util.Validator;
|
||||||
|
import schule.ngb.zm.util.events.EventDispatcher;
|
||||||
|
import schule.ngb.zm.util.io.ResourceStreamProvider;
|
||||||
|
import schule.ngb.zm.util.tasks.TaskRunner;
|
||||||
|
|
||||||
import javax.sound.sampled.*;
|
import javax.sound.sampled.*;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ein Musikstück, dass im Projekt abgespielt werden soll.
|
* Eine Musik, die abgespielt werden kann.
|
||||||
* <p>
|
* <p>
|
||||||
* Im gegensatz zu einem {@link Sound} sind Musikstücke längere Audiodateien,
|
* Im Gegensatz zu einem {@link Sound} wird {@code Music} für längere
|
||||||
* die zum Beispiel als Hintergrundmusik ablaufen sollen. Die Musik wird daher
|
* Audiodateien benutzt, die zum Beispiel als Hintergrundmusik gespielt werden.
|
||||||
* nicht komplett in den Speicher geladen, sondern direkt aus der Audioquelle
|
* Die Audiodaten werden daher nicht vollständig in den Speicher geladen,
|
||||||
* gestreamt und wiedergegeben.
|
* sondern direkt aus der Quelle gestreamt und direkt wiedergegeben.
|
||||||
|
* <p>
|
||||||
|
* Daher ist es nicht möglich, die länge der Musik im Vorfeld abzufragen oder zu
|
||||||
|
* einer bestimmten Stelle im Stream zu springen.
|
||||||
|
*
|
||||||
|
* <h2>MP3-Dateien verwenden</h2>
|
||||||
|
* Java kann nativ nur Waveform ({@code .wav}) Dateien wiedergeben. Um auch
|
||||||
|
* MP3-Dateien zu nutzen, müssen die Bibliotheken <a href="#">jlayer</a>, <a
|
||||||
|
* href="#">tritonus-share</a> und <a href="#">mp3spi</a> eingebunden werden.
|
||||||
|
* Details zur Verwendung können in der <a
|
||||||
|
* href="https://zeichenmaschine.xyz/installation/#unterstutzung-fur-mp3">Dokumentation
|
||||||
|
* der Zeichenmaschine</a> nachgelesen werden.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unused")
|
// TODO: Wann sollten Listener beim Loopen informiert werden? Nach jedem Loop oder erst ganz am Ende?
|
||||||
|
@SuppressWarnings( "unused" )
|
||||||
public class Music implements Audio {
|
public class Music implements Audio {
|
||||||
|
|
||||||
// size of the byte buffer used to read/write the audio stream
|
/**
|
||||||
|
* Größe des verwendeten Input-Puffers für die Audiodaten.
|
||||||
|
*/
|
||||||
private static final int BUFFER_SIZE = 4096;
|
private static final int BUFFER_SIZE = 4096;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ob der Sound gerade abgespielt wird.
|
* Ob der Sound aktuell abgespielt wird.
|
||||||
*/
|
*/
|
||||||
private boolean playing = false;
|
private boolean playing = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ob der Sound gerade in einer Schleife abgespielt wird.
|
* Ob der Sound aktuell in einer Schleife abgespielt wird.
|
||||||
*/
|
*/
|
||||||
private boolean looping = false;
|
private boolean looping = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Die Quelle des Musikstücks.
|
* Die Quelle der Audiodaten.
|
||||||
*/
|
*/
|
||||||
private String audioSource;
|
private String audioSource;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Der AudioStream, um die AUdiosdaten zulsen, falls dieser schon geöffnet
|
* Der {@link AudioInputStream}, um die Audiosdaten zu lesen. {@code null},
|
||||||
* wurde. Sonst {@code null}.
|
* falls noch kein Stream geöffnet wurde.
|
||||||
*/
|
*/
|
||||||
private AudioInputStream audioStream;
|
private AudioInputStream audioStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Die Line für die Ausgabe, falls diese schon geöffnet wurde. Sonst
|
* Die {@link SourceDataLine} für die Ausgabe. {@code null}, falls die
|
||||||
* {@code null}.
|
* Audiodatei noch nicht geöffnet wurde.
|
||||||
*/
|
*/
|
||||||
private SourceDataLine audioLine;
|
private SourceDataLine audioLine;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Die Lautstärke der Musik.
|
* Die aktuelle Lautstärke des Mediums.
|
||||||
*/
|
*/
|
||||||
private float volume = 0.8f;
|
private float volume = 0.8f;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatcher für Audio-Events (start und stop).
|
||||||
|
*/
|
||||||
EventDispatcher<Audio, AudioListener> eventDispatcher;
|
EventDispatcher<Audio, AudioListener> eventDispatcher;
|
||||||
|
|
||||||
public Music( String source ) {
|
/**
|
||||||
Validator.requireNotNull(source);
|
* Erstellt eine Musik aus der angegebenen Audioquelle.
|
||||||
this.audioSource = source;
|
*
|
||||||
|
* @param audioSource Quelle der Audiodaten.
|
||||||
|
* @throws NullPointerException Falls die Quelle {@code null} ist.
|
||||||
|
* @see ResourceStreamProvider#getResourceURL(String)
|
||||||
|
*/
|
||||||
|
public Music( String audioSource ) {
|
||||||
|
Validator.requireNotNull(audioSource, "audioSource");
|
||||||
|
this.audioSource = audioSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public String getSource() {
|
public String getSource() {
|
||||||
return audioSource;
|
return audioSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isPlaying() {
|
public boolean isPlaying() {
|
||||||
return playing;
|
return playing;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isLooping() {
|
public boolean isLooping() {
|
||||||
if( !playing ) {
|
if( !playing ) {
|
||||||
@@ -87,28 +106,21 @@ public class Music implements Audio {
|
|||||||
return looping;
|
return looping;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void setVolume( double volume ) {
|
public void setVolume( double volume ) {
|
||||||
this.volume = (float) volume;
|
this.volume = volume < 0 ? 0f : (float) volume;
|
||||||
if( audioLine != null ) {
|
if( audioLine != null ) {
|
||||||
applyVolume();
|
applyVolume();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public double getVolume() {
|
public double getVolume() {
|
||||||
return volume;
|
return volume;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interne Methode, um die gesetzte Lautstärke vor dem Abspielen
|
* Wendet die Lautstärke vor dem Abspielen auf den Audiostream an.
|
||||||
* anzuwenden.
|
|
||||||
*/
|
*/
|
||||||
private void applyVolume() {
|
private void applyVolume() {
|
||||||
FloatControl gainControl =
|
FloatControl gainControl =
|
||||||
@@ -119,24 +131,13 @@ public class Music implements Audio {
|
|||||||
gainControl.setValue(vol);
|
gainControl.setValue(vol);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void play() {
|
public void play() {
|
||||||
if( openLine() ) {
|
if( openLine() ) {
|
||||||
TaskRunner.run(new Runnable() {
|
TaskRunner.run(this::stream);
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
stream();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void playAndWait() {
|
public void playAndWait() {
|
||||||
if( openLine() ) {
|
if( openLine() ) {
|
||||||
@@ -144,18 +145,12 @@ public class Music implements Audio {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void loop() {
|
public void loop() {
|
||||||
looping = true;
|
looping = true;
|
||||||
play();
|
play();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void stop() {
|
public void stop() {
|
||||||
playing = false;
|
playing = false;
|
||||||
@@ -163,9 +158,6 @@ public class Music implements Audio {
|
|||||||
dispose();
|
dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void dispose() {
|
public synchronized void dispose() {
|
||||||
if( audioLine != null ) {
|
if( audioLine != null ) {
|
||||||
@@ -189,7 +181,17 @@ public class Music implements Audio {
|
|||||||
audioStream = null;
|
audioStream = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Startet den Stream der Audiodaten und damit die Wiedergabe.
|
||||||
|
* <p>
|
||||||
|
* Die {@link #audioLine} muss vorher mit {@link #openLine()} geöffnet
|
||||||
|
* werden, ansonsten passiert nichts.
|
||||||
|
*/
|
||||||
private synchronized void stream() {
|
private synchronized void stream() {
|
||||||
|
if( audioLine == null ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
audioLine.start();
|
audioLine.start();
|
||||||
playing = true;
|
playing = true;
|
||||||
if( eventDispatcher != null ) {
|
if( eventDispatcher != null ) {
|
||||||
@@ -226,6 +228,14 @@ public class Music implements Audio {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Öffnet eine {@link SourceDataLine} für die
|
||||||
|
* {@link #audioSource Audioquelle} und bereitet die Wiedergabe vor. Es wird
|
||||||
|
* noch nichts abgespielt.
|
||||||
|
*
|
||||||
|
* @return {@code true}, wenn die Line geöffnet werden konnte, {@code false}
|
||||||
|
* sonst.
|
||||||
|
*/
|
||||||
private boolean openLine() {
|
private boolean openLine() {
|
||||||
if( audioLine != null ) {
|
if( audioLine != null ) {
|
||||||
return true;
|
return true;
|
||||||
@@ -262,6 +272,15 @@ public class Music implements Audio {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wird aufgerufen, wenn die Wiedergabe beendet wurde. Entweder durch einen
|
||||||
|
* Aufruf von {@link #stop()} oder weil keine Audiodaten mehr vorhanden
|
||||||
|
* sind.
|
||||||
|
* <p>
|
||||||
|
* Nach dem Ende des Streams wird {@link #dispose()} aufgerufen und, falls
|
||||||
|
* das Musikstück in einer Schleife abgespielt wird, der Stream direkt
|
||||||
|
* wieder gestartet.
|
||||||
|
*/
|
||||||
private void streamingStopped() {
|
private void streamingStopped() {
|
||||||
dispose();
|
dispose();
|
||||||
|
|
||||||
@@ -289,14 +308,15 @@ public class Music implements Audio {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Interne Methode, um den Listener-Mechanismus zu initialisieren. Wird erst
|
* Interne Methode, um den Listener-Mechanismus zu initialisieren. Wird erst
|
||||||
* aufgerufen, soblad sich auch ein Listener registrieren möchte.
|
* aufgerufen, sobald sich der erste Listener anmelden möchte.
|
||||||
* @return
|
*
|
||||||
|
* @return Der {@code EventDispatcher} für dieses Objekt.
|
||||||
*/
|
*/
|
||||||
private EventDispatcher<Audio, AudioListener> initializeEventDispatcher() {
|
private EventDispatcher<Audio, AudioListener> initializeEventDispatcher() {
|
||||||
if( eventDispatcher == null ) {
|
if( eventDispatcher == null ) {
|
||||||
eventDispatcher = new EventDispatcher<>();
|
eventDispatcher = new EventDispatcher<>();
|
||||||
eventDispatcher.registerEventType("start", (a,l) -> l.playbackStarted(a));
|
eventDispatcher.registerEventType("start", ( a, l ) -> l.playbackStarted(a));
|
||||||
eventDispatcher.registerEventType("stop", (a,l) -> l.playbackStopped(a));
|
eventDispatcher.registerEventType("stop", ( a, l ) -> l.playbackStopped(a));
|
||||||
}
|
}
|
||||||
return eventDispatcher;
|
return eventDispatcher;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,80 +10,87 @@ import java.io.IOException;
|
|||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wiedergabe kurzer Soundclips, die mehrmals wiederverwendet werden.
|
* Ein kurzer Soundclip, der mehrmals wiederverwendet werden kann.
|
||||||
* <p>
|
* <p>
|
||||||
* In Spielen und anderen Projekten gibt es oftmals eine Reihe kurzer Sounds,
|
* In Spielen und anderen Projekten gibt es oftmals eine Reihe kurzer
|
||||||
* die zusammen mit bestimmten Aktionen wiedergegeben werden (zum Beispiel, wenn
|
* Soundclips, die zusammen mit bestimmten Aktionen wiedergegeben werden (zum
|
||||||
* die Spielfigur springt, wenn zwei Objekte kollidieren, usw.). Sounds werden
|
* Beispiel, wenn die Spielfigur springt, wenn zwei Objekte kollidieren, usw.).
|
||||||
* komplett in den Speicher geladen und können dadurch immer wieder, als
|
* Sounds werden vollständig in den Speicher geladen und können immer wieder,
|
||||||
* Schleife oder auch nur Abschnittsweise abgespielt werden.
|
* als Schleife oder auch nur Abschnittsweise, abgespielt werden.
|
||||||
* <p>
|
* <p>
|
||||||
* Für längre Musikstücke (beispielsweise Hintergrundmusik) bietet sich eher die
|
* Für längere Musikstücke (beispielsweise Hintergrundmusik) bietet sich eher
|
||||||
* KLasse {@link Music} an.
|
* die Klasse {@link Music} an.
|
||||||
|
*
|
||||||
|
* <h2>MP3-Dateien verwenden</h2>
|
||||||
|
* Java kann nativ nur Waveform ({@code .wav}) Dateien wiedergeben. Um auch
|
||||||
|
* MP3-Dateien zu nutzen, müssen die Bibliotheken <a href="#">jlayer</a>, <a
|
||||||
|
* href="#">tritonus-share</a> und <a href="#">mp3spi</a> eingebunden werden.
|
||||||
|
* Details zur Verwendung können in der <a
|
||||||
|
* href="https://zeichenmaschine.xyz/installation/#unterstutzung-fur-mp3">Dokumentation
|
||||||
|
* der Zeichenmaschine</a> nachgelesen werden.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings( "unused" )
|
@SuppressWarnings( "unused" )
|
||||||
public class Sound implements Audio {
|
public class Sound implements Audio {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ob der Sound gerade abgespielt wird.
|
* Ob der Sound aktuell abgespielt wird.
|
||||||
*/
|
*/
|
||||||
private boolean playing = false;
|
private boolean playing = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ob der Sound gerade in einer Schleife abgespielt wird.
|
* Ob der Sound aktuell in einer Schleife abgespielt wird.
|
||||||
*/
|
*/
|
||||||
private boolean looping = false;
|
private boolean looping = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Die Quelle des Musikstücks.
|
* Die Quelle der Audiodaten.
|
||||||
*/
|
*/
|
||||||
private String audioSource;
|
private String audioSource;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Der Clip, falls er schon geladen wurde, sonst {@code null}.
|
* Der Clip, falls er schon geladen wurde. Ansonsten {@code null}.
|
||||||
*/
|
*/
|
||||||
private Clip audioClip;
|
private Clip audioClip;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ob die Resourcen des Clips im Speicher nach dem nächsten Abspielen
|
* Ob die Ressourcen des Clips im Speicher nach dem nächsten Abspielen
|
||||||
* freigegeben werden sollen.
|
* freigegeben werden sollen.
|
||||||
*/
|
*/
|
||||||
private boolean disposeAfterPlay = false;
|
private boolean disposeAfterPlay = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Die Lautstärke des Clips.
|
* Die aktuelle Lautstärke des Clips.
|
||||||
*/
|
*/
|
||||||
private float volume = 0.8f;
|
private float volume = 0.8f;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatcher für Audio-Events (start und stop).
|
||||||
|
*/
|
||||||
EventDispatcher<Audio, AudioListener> eventDispatcher;
|
EventDispatcher<Audio, AudioListener> eventDispatcher;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Erstellt einen Sound aus der angegebene Quelle.
|
* Erstellt einen Sound aus der angegebene Quelle.
|
||||||
*
|
*
|
||||||
* @param source Ein Dateipfad oder eine Webadresse.
|
* @param source Quelle der Audiodaten.
|
||||||
* @throws NullPointerException Falls die Quelle {@code null} ist.
|
* @throws NullPointerException Falls die Quelle {@code null} ist.
|
||||||
|
* @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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public String getSource() {
|
public String getSource() {
|
||||||
return audioSource;
|
return audioSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isPlaying() {
|
public boolean isPlaying() {
|
||||||
// return audioClip != null && audioClip.isRunning();
|
// return audioClip != null && audioClip.isRunning();
|
||||||
return playing;
|
return playing;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isLooping() {
|
public boolean isLooping() {
|
||||||
if( !playing ) {
|
if( !playing ) {
|
||||||
@@ -92,28 +99,21 @@ public class Sound implements Audio {
|
|||||||
return looping;
|
return looping;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void setVolume( double volume ) {
|
public void setVolume( double volume ) {
|
||||||
this.volume = (float) volume;
|
this.volume = volume < 0 ? 0f : (float) volume;
|
||||||
if( audioClip != null ) {
|
if( audioClip != null ) {
|
||||||
applyVolume();
|
applyVolume();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public double getVolume() {
|
public double getVolume() {
|
||||||
return volume;
|
return volume;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interne Methode, um die gesetzte Lautstärke vor dem Abspielen
|
* Wendet die Lautstärke vor dem Abspielen auf den Clip an.
|
||||||
* anzuwenden.
|
|
||||||
*/
|
*/
|
||||||
private void applyVolume() {
|
private void applyVolume() {
|
||||||
FloatControl gainControl =
|
FloatControl gainControl =
|
||||||
@@ -124,9 +124,6 @@ public class Sound implements Audio {
|
|||||||
gainControl.setValue(vol);
|
gainControl.setValue(vol);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void stop() {
|
public void stop() {
|
||||||
looping = false;
|
looping = false;
|
||||||
@@ -136,9 +133,6 @@ public class Sound implements Audio {
|
|||||||
playing = false;
|
playing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void play() {
|
public void play() {
|
||||||
if( this.openClip() ) {
|
if( this.openClip() ) {
|
||||||
@@ -147,9 +141,6 @@ public class Sound implements Audio {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void playAndWait() {
|
public void playAndWait() {
|
||||||
this.play();
|
this.play();
|
||||||
@@ -168,7 +159,7 @@ public class Sound implements Audio {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Spielt den Sound genau einmal ab und gibt danach alle Resourcen des Clips
|
* Spielt den Sound einmal ab und gibt danach alle Ressourcen des Clips
|
||||||
* frei.
|
* frei.
|
||||||
* <p>
|
* <p>
|
||||||
* Der Aufruf ist effektiv gleich zu
|
* Der Aufruf ist effektiv gleich zu
|
||||||
@@ -185,7 +176,7 @@ public class Sound implements Audio {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Spielt den Sound genau einmal ab und gibt danach alle Resourcen des Clips
|
* Spielt den Sound einmal ab und gibt danach alle Ressourcen des Clips
|
||||||
* frei.
|
* frei.
|
||||||
* <p>
|
* <p>
|
||||||
* Der Aufruf entspricht
|
* Der Aufruf entspricht
|
||||||
@@ -199,17 +190,18 @@ public class Sound implements Audio {
|
|||||||
playAndWait();
|
playAndWait();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void loop() {
|
public void loop() {
|
||||||
loop(Clip.LOOP_CONTINUOUSLY);
|
loop(Clip.LOOP_CONTINUOUSLY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wiederholt den Sound die angegebene Anzahl an Wiederholungen ab und
|
* Wiederholt den Sound die angegebene Anzahl an Wiederholungen und stoppt
|
||||||
* stoppt die Wiedergabe dann.
|
* die Wiedergabe dann.
|
||||||
|
* <p>
|
||||||
|
* Wird {@code count} auf {@link Clip#LOOP_CONTINUOUSLY} gesetzt (-1), wird
|
||||||
|
* der Clip unendlich oft wiederholt. Der Aufruf entspricht dann
|
||||||
|
* {@link #loop()}.
|
||||||
*
|
*
|
||||||
* @param count Anzahl der Wiederholungen.
|
* @param count Anzahl der Wiederholungen.
|
||||||
*/
|
*/
|
||||||
@@ -231,9 +223,6 @@ public class Sound implements Audio {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void dispose() {
|
public synchronized void dispose() {
|
||||||
if( audioClip != null ) {
|
if( audioClip != null ) {
|
||||||
@@ -245,6 +234,13 @@ public class Sound implements Audio {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lädt, falls nötig, den {@link Clip} für die
|
||||||
|
* {@link #audioSource Audioquelle}.
|
||||||
|
*
|
||||||
|
* @return {@code true}, wenn der Clip geöffnet werden konnte, {@code false}
|
||||||
|
* sonst.
|
||||||
|
*/
|
||||||
private synchronized boolean openClip() {
|
private synchronized boolean openClip() {
|
||||||
if( audioClip != null ) {
|
if( audioClip != null ) {
|
||||||
audioClip.setFramePosition(0);
|
audioClip.setFramePosition(0);
|
||||||
@@ -268,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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -307,13 +299,20 @@ public class Sound implements Audio {
|
|||||||
}*/
|
}*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interne Methode, die aufgerufen wird, wenn die Wiedergabe gestoppt wird.
|
* Wird aufgerufen, wenn die Wiedergabe beendet wurde. Entweder durch einen
|
||||||
* Entweder durch einen Aufruf von {@link #stop()} oder, weil die Wiedergabe
|
* Aufruf von {@link #stop()} oder, weil die Wiedergabe nach
|
||||||
* nach {@link #playOnce()} beendet wurde.
|
* {@link #playOnce()} beendet wurde.
|
||||||
|
* <p>
|
||||||
|
* Falls {@link #disposeAfterPlay} gesetzt ist, wird nach dem Ende der
|
||||||
|
* Wiedergabe {@link #dispose()} aufgerufen.
|
||||||
*/
|
*/
|
||||||
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;
|
||||||
@@ -332,14 +331,15 @@ public class Sound implements Audio {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Interne Methode, um den Listener-Mechanismus zu initialisieren. Wird erst
|
* Interne Methode, um den Listener-Mechanismus zu initialisieren. Wird erst
|
||||||
* aufgerufen, soblad sich auch ein Listener registrieren möchte.
|
* aufgerufen, sobald sich der erste Listener anmelden möchte.
|
||||||
* @return
|
*
|
||||||
|
* @return Der {@code EventDispatcher} für dieses Objekt.
|
||||||
*/
|
*/
|
||||||
private EventDispatcher<Audio, AudioListener> initializeEventDispatcher() {
|
private EventDispatcher<Audio, AudioListener> initializeEventDispatcher() {
|
||||||
if( eventDispatcher == null ) {
|
if( eventDispatcher == null ) {
|
||||||
eventDispatcher = new EventDispatcher<>();
|
eventDispatcher = new EventDispatcher<>();
|
||||||
eventDispatcher.registerEventType("start", (a,l) -> l.playbackStarted(a));
|
eventDispatcher.registerEventType("start", ( a, l ) -> l.playbackStarted(a));
|
||||||
eventDispatcher.registerEventType("stop", (a,l) -> l.playbackStopped(a));
|
eventDispatcher.registerEventType("stop", ( a, l ) -> l.playbackStopped(a));
|
||||||
}
|
}
|
||||||
return eventDispatcher;
|
return eventDispatcher;
|
||||||
}
|
}
|
||||||
|
|||||||
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;
|
||||||
@@ -93,32 +93,20 @@ public final class DoubleMatrix implements MLMatrix {
|
|||||||
rows * columns);
|
rows * columns);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public int columns() {
|
public int columns() {
|
||||||
return columns;
|
return columns;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public int rows() {
|
public int rows() {
|
||||||
return rows;
|
return rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
int idx( int r, int c ) {
|
int idx( int r, int c ) {
|
||||||
return c * rows + r;
|
return c * rows + r;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public double get( int row, int col ) {
|
public double get( int row, int col ) {
|
||||||
try {
|
try {
|
||||||
@@ -128,9 +116,6 @@ public final class DoubleMatrix implements MLMatrix {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public MLMatrix set( int row, int col, double value ) {
|
public MLMatrix set( int row, int col, double value ) {
|
||||||
try {
|
try {
|
||||||
@@ -141,52 +126,34 @@ public final class DoubleMatrix implements MLMatrix {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public MLMatrix initializeRandom() {
|
public MLMatrix initializeRandom() {
|
||||||
return initializeRandom(-1.0, 1.0);
|
return initializeRandom(-1.0, 1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public MLMatrix initializeRandom( double lower, double upper ) {
|
public MLMatrix initializeRandom( double lower, double upper ) {
|
||||||
applyInPlace(( d ) -> ((upper - lower) * Constants.random()) + lower);
|
applyInPlace(( d ) -> ((upper - lower) * Constants.random()) + lower);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public MLMatrix initializeOne() {
|
public MLMatrix initializeOne() {
|
||||||
applyInPlace(( d ) -> 1.0);
|
applyInPlace(( d ) -> 1.0);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public MLMatrix initializeZero() {
|
public MLMatrix initializeZero() {
|
||||||
applyInPlace(( d ) -> 0.0);
|
applyInPlace(( d ) -> 0.0);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public MLMatrix duplicate() {
|
public MLMatrix duplicate() {
|
||||||
return new DoubleMatrix(this);
|
return new DoubleMatrix(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public MLMatrix multiplyTransposed( MLMatrix B ) {
|
public MLMatrix multiplyTransposed( MLMatrix B ) {
|
||||||
/*return new DoubleMatrix(IntStream.range(0, rows).parallel().mapToObj(
|
/*return new DoubleMatrix(IntStream.range(0, rows).parallel().mapToObj(
|
||||||
@@ -208,9 +175,6 @@ public final class DoubleMatrix implements MLMatrix {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public MLMatrix multiplyAddBias( final MLMatrix B, final MLMatrix C ) {
|
public MLMatrix multiplyAddBias( final MLMatrix B, final MLMatrix C ) {
|
||||||
/*return new DoubleMatrix(IntStream.range(0, rows).parallel().mapToObj(
|
/*return new DoubleMatrix(IntStream.range(0, rows).parallel().mapToObj(
|
||||||
@@ -233,9 +197,6 @@ public final class DoubleMatrix implements MLMatrix {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public MLMatrix transposedMultiplyAndScale( final MLMatrix B, final double scalar ) {
|
public MLMatrix transposedMultiplyAndScale( final MLMatrix B, final double scalar ) {
|
||||||
/*return new DoubleMatrix(IntStream.range(0, columns).parallel().mapToObj(
|
/*return new DoubleMatrix(IntStream.range(0, columns).parallel().mapToObj(
|
||||||
@@ -258,9 +219,6 @@ public final class DoubleMatrix implements MLMatrix {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public MLMatrix add( MLMatrix B ) {
|
public MLMatrix add( MLMatrix B ) {
|
||||||
/*return new DoubleMatrix(IntStream.range(0, rows).parallel().mapToObj(
|
/*return new DoubleMatrix(IntStream.range(0, rows).parallel().mapToObj(
|
||||||
@@ -277,9 +235,6 @@ public final class DoubleMatrix implements MLMatrix {
|
|||||||
return sum;
|
return sum;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public MLMatrix addInPlace( MLMatrix B ) {
|
public MLMatrix addInPlace( MLMatrix B ) {
|
||||||
for( int j = 0; j < columns; j++ ) {
|
for( int j = 0; j < columns; j++ ) {
|
||||||
@@ -290,9 +245,6 @@ public final class DoubleMatrix implements MLMatrix {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public MLMatrix sub( MLMatrix B ) {
|
public MLMatrix sub( MLMatrix B ) {
|
||||||
/*return new DoubleMatrix(IntStream.range(0, rows).parallel().mapToObj(
|
/*return new DoubleMatrix(IntStream.range(0, rows).parallel().mapToObj(
|
||||||
@@ -309,9 +261,6 @@ public final class DoubleMatrix implements MLMatrix {
|
|||||||
return diff;
|
return diff;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public MLMatrix colSums() {
|
public MLMatrix colSums() {
|
||||||
/*DoubleMatrix colSums = new DoubleMatrix(1, columns);
|
/*DoubleMatrix colSums = new DoubleMatrix(1, columns);
|
||||||
@@ -331,9 +280,6 @@ public final class DoubleMatrix implements MLMatrix {
|
|||||||
return colSums;
|
return colSums;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public MLMatrix scaleInPlace( final double scalar ) {
|
public MLMatrix scaleInPlace( final double scalar ) {
|
||||||
for( int i = 0; i < coefficients.length; i++ ) {
|
for( int i = 0; i < coefficients.length; i++ ) {
|
||||||
@@ -342,9 +288,6 @@ public final class DoubleMatrix implements MLMatrix {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public MLMatrix scaleInPlace( final MLMatrix S ) {
|
public MLMatrix scaleInPlace( final MLMatrix S ) {
|
||||||
for( int j = 0; j < columns; j++ ) {
|
for( int j = 0; j < columns; j++ ) {
|
||||||
@@ -355,9 +298,6 @@ public final class DoubleMatrix implements MLMatrix {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public MLMatrix apply( DoubleUnaryOperator op ) {
|
public MLMatrix apply( DoubleUnaryOperator op ) {
|
||||||
DoubleMatrix result = new DoubleMatrix(rows, columns);
|
DoubleMatrix result = new DoubleMatrix(rows, columns);
|
||||||
@@ -367,9 +307,6 @@ public final class DoubleMatrix implements MLMatrix {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public MLMatrix applyInPlace( DoubleUnaryOperator op ) {
|
public MLMatrix applyInPlace( DoubleUnaryOperator op ) {
|
||||||
for( int i = 0; i < coefficients.length; i++ ) {
|
for( int i = 0; i < coefficients.length; i++ ) {
|
||||||
|
|||||||
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
package schule.ngb.zm.shapes;
|
package schule.ngb.zm.shapes;
|
||||||
|
|
||||||
import schule.ngb.zm.*;
|
import schule.ngb.zm.BasicDrawable;
|
||||||
|
import schule.ngb.zm.Fillable;
|
||||||
|
import schule.ngb.zm.Options;
|
||||||
|
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>
|
||||||
@@ -16,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);
|
||||||
@@ -36,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;
|
||||||
@@ -79,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.
|
||||||
*/
|
*/
|
||||||
@@ -97,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.
|
||||||
*/
|
*/
|
||||||
@@ -106,63 +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();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* Liefert die Rotation in Grad.
|
||||||
*/
|
|
||||||
@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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@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.
|
|
||||||
*
|
*
|
||||||
* @return Rotation in Grad.
|
* @return Rotation in Grad.
|
||||||
*/
|
*/
|
||||||
@@ -179,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;
|
||||||
|
|
||||||
@@ -201,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.
|
||||||
*
|
*
|
||||||
@@ -210,33 +214,91 @@ 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;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -245,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.
|
||||||
@@ -262,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;
|
||||||
@@ -279,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.
|
||||||
@@ -296,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 ) {
|
||||||
@@ -318,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());
|
||||||
@@ -338,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.
|
||||||
*/
|
*/
|
||||||
@@ -351,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}.
|
||||||
@@ -372,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);
|
||||||
@@ -409,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);
|
||||||
@@ -426,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);
|
||||||
@@ -449,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
|
||||||
@@ -472,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);
|
||||||
}*/
|
}*/
|
||||||
|
|||||||
@@ -136,57 +136,36 @@ public class BasicChartValue implements ChartValue {
|
|||||||
this.color = color;
|
this.color = color;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public double getX() {
|
public double getX() {
|
||||||
return xValue;
|
return xValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public double getValue() {
|
public double getValue() {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void setValue( double pValue ) {
|
public void setValue( double pValue ) {
|
||||||
this.value = pValue;
|
this.value = pValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public String getLabel() {
|
public String getLabel() {
|
||||||
return label;
|
return label;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void setLabel( String pLabel ) {
|
public void setLabel( String pLabel ) {
|
||||||
this.label = pLabel;
|
this.label = pLabel;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public Color getColor() {
|
public Color getColor() {
|
||||||
return color;
|
return color;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void setColor( Color pColor ) {
|
public void setColor( Color pColor ) {
|
||||||
this.color = pColor;
|
this.color = pColor;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ package schule.ngb.zm.util;
|
|||||||
|
|
||||||
import schule.ngb.zm.Color;
|
import schule.ngb.zm.Color;
|
||||||
import schule.ngb.zm.Constants;
|
import schule.ngb.zm.Constants;
|
||||||
import schule.ngb.zm.Zeichenmaschine;
|
|
||||||
import schule.ngb.zm.util.io.FileLoader;
|
|
||||||
import schule.ngb.zm.util.io.ImageLoader;
|
import schule.ngb.zm.util.io.ImageLoader;
|
||||||
|
|
||||||
import java.awt.Font;
|
import java.awt.Font;
|
||||||
@@ -11,112 +9,330 @@ import java.awt.FontMetrics;
|
|||||||
import java.awt.Graphics2D;
|
import java.awt.Graphics2D;
|
||||||
import java.awt.font.LineMetrics;
|
import java.awt.font.LineMetrics;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.lang.reflect.Array;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.*;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import static java.lang.Math.exp;
|
||||||
|
import static java.lang.Math.log;
|
||||||
|
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 Platzhalterbilder.
|
* Unter anderem Namen, E-Mail-Adressen, Passwörter oder Platzhalter-Bilder.
|
||||||
*/
|
*/
|
||||||
|
@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";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger Nutzernamen.
|
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger Benutzerdaten.
|
||||||
|
* <p>
|
||||||
|
* Jeder Datensatz besteht aus einem String-Array mit den folgenden Daten
|
||||||
|
* <ul>
|
||||||
|
* <li><code>fakeUser[i][0]</code>: Vorname</li>
|
||||||
|
* <li><code>fakeUser[i][1]</code>: Nachname</li>
|
||||||
|
* <li><code>fakeUser[i][2]</code>: Geschlecht</li>
|
||||||
|
* <li><code>fakeUser[i][3]</code>: Nutzername</li>
|
||||||
|
* <li><code>fakeUser[i][4]</code>: Passwort</li>
|
||||||
|
* <li><code>fakeUser[i][5]</code>: E-Mail</li>
|
||||||
|
* <li><code>fakeUser[i][6]</code>: Geburtsdatum</li>
|
||||||
|
* </ul>
|
||||||
*
|
*
|
||||||
* @param n Anzahl der Beispieldaten.
|
* @param count Anzahl der Beispieldaten.
|
||||||
* @return Ein Array mit den Beispieldaten.
|
* @return Ein Array mit den Beispieldaten.
|
||||||
*/
|
*/
|
||||||
public static String[][] fakeUsers( int n ) {
|
public static String[][] fakeUsers( int count ) {
|
||||||
String[][] data = loadMockfile("users");
|
return randomSample("users", count, ( line ) -> line.split(","), String[].class);
|
||||||
String[][] result = new String[n][];
|
|
||||||
for( int i = 0; i < n; i++ ) {
|
|
||||||
result[i] = Constants.choice(data);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger Vornamen.
|
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger Vornamen.
|
||||||
*
|
*
|
||||||
* @param n Anzahl der Beispieldaten.
|
* @param count Anzahl der Beispieldaten.
|
||||||
* @return Ein Array mit den Beispieldaten.
|
* @return Ein Array mit den Beispieldaten.
|
||||||
*/
|
*/
|
||||||
public static String[] fakeNames( int n ) {
|
public static String[] fakeNames( int count ) {
|
||||||
String[][] data = loadMockfile("users");
|
return randomSample("users", count, ( line ) -> line.split(",")[0], String.class);
|
||||||
String[] result = new String[n];
|
|
||||||
for( int i = 0; i < n; i++ ) {
|
|
||||||
String[] row = Constants.choice(data);
|
|
||||||
result[i] = row[0];
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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 n Anzahl der Beispieldaten.
|
* @param count Anzahl der Beispieldaten.
|
||||||
* @return Ein Array mit den Beispieldaten.
|
* @return Ein Array mit den Beispieldaten.
|
||||||
*/
|
*/
|
||||||
public static String[] fakeFullnames( int n ) {
|
public static String[] fakeFullnames( int count ) {
|
||||||
String[][] data = loadMockfile("users");
|
return randomSample("users", count, ( line ) -> {
|
||||||
String[] result = new String[n];
|
String[] parts = line.split(",");
|
||||||
for( int i = 0; i < n; i++ ) {
|
return parts[0] + " " + parts[1];
|
||||||
String[] row = Constants.choice(data);
|
}, String.class);
|
||||||
result[i] = row[0] + " " + row[1];
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger Nutzernamen.
|
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger Nutzernamen.
|
||||||
*
|
*
|
||||||
* @param n Anzahl der Beispieldaten.
|
* @param count Anzahl der Beispieldaten.
|
||||||
* @return Ein Array mit den Beispieldaten.
|
* @return Ein Array mit den Beispieldaten.
|
||||||
*/
|
*/
|
||||||
public static String[] fakeUsernames( int n ) {
|
public static String[] fakeUsernames( int count ) {
|
||||||
String[][] data = loadMockfile("users");
|
return randomSample("users", count, ( line ) -> line.split(",")[3], String.class);
|
||||||
String[] result = new String[n];
|
|
||||||
for( int i = 0; i < n; i++ ) {
|
|
||||||
String[] row = Constants.choice(data);
|
|
||||||
result[i] = row[3];
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger Passwörter.
|
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger Passwörter.
|
||||||
*
|
*
|
||||||
* @param n Anzahl der Beispieldaten.
|
* @param count Anzahl der Beispieldaten.
|
||||||
* @return Ein Array mit den Beispieldaten.
|
* @return Ein Array mit den Beispieldaten.
|
||||||
*/
|
*/
|
||||||
public static String[] fakePasswords( int n ) {
|
public static String[] fakePasswords( int count ) {
|
||||||
String[][] data = loadMockfile("users");
|
return randomSample("users", count, ( line ) -> line.split(",")[4], String.class);
|
||||||
String[] result = new String[n];
|
|
||||||
for( int i = 0; i < n; i++ ) {
|
|
||||||
String[] row = Constants.choice(data);
|
|
||||||
result[i] = row[4];
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger E-Mail-Adressen.
|
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger E-Mail-Adressen.
|
||||||
*
|
*
|
||||||
* @param n Anzahl der Beispieldaten.
|
* @param count Anzahl der Beispieldaten.
|
||||||
* @return Ein Array mit den Beispieldaten.
|
* @return Ein Array mit den Beispieldaten.
|
||||||
*/
|
*/
|
||||||
public static String[] fakeEmails( int n ) {
|
public static String[] fakeEmails( int count ) {
|
||||||
String[][] data = loadMockfile("users");
|
return randomSample("users", count, ( line ) -> line.split(",")[5], String.class);
|
||||||
String[] result = new String[n];
|
}
|
||||||
for( int i = 0; i < n; i++ ) {
|
|
||||||
String[] row = Constants.choice(data);
|
/**
|
||||||
result[i] = row[5];
|
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger deutscher
|
||||||
|
* Wörter.
|
||||||
|
*
|
||||||
|
* @param count Anzahl der Beispieldaten.
|
||||||
|
* @return Ein Array mit den Beispieldaten.
|
||||||
|
*/
|
||||||
|
public static String[] fakeStrings( int count ) {
|
||||||
|
return randomSample("words", count, ( line ) -> line, String.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger
|
||||||
|
* {@code LocalDate}-Objekte, die ein Datum ohne Uhrzeit beschreiben.
|
||||||
|
*
|
||||||
|
* @param count Anzahl der Beispieldaten.
|
||||||
|
* @return Ein Array mit den Beispieldaten.
|
||||||
|
*/
|
||||||
|
public static LocalDate[] fakeDates( int count ) {
|
||||||
|
long nowEpoch = LocalDate.now().toEpochDay();
|
||||||
|
long from = LocalDate.ofEpochDay(nowEpoch - 18 * 365).toEpochDay();
|
||||||
|
long to = LocalDate.ofEpochDay(nowEpoch - 14 * 365).toEpochDay();
|
||||||
|
|
||||||
|
LocalDate[] result = new LocalDate[count];
|
||||||
|
for( int i = 0; i < count; i++ ) {
|
||||||
|
result[i] = LocalDate.ofEpochDay((int) Constants.interpolate(from, to, random()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 ) {
|
||||||
|
long nowEpoch = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC);
|
||||||
|
long from = LocalDateTime.ofEpochSecond(nowEpoch - 18 * 365, 0, ZoneOffset.UTC).toEpochSecond(ZoneOffset.UTC);
|
||||||
|
long to = LocalDateTime.ofEpochSecond(nowEpoch - 14 * 365, 0, ZoneOffset.UTC).toEpochSecond(ZoneOffset.UTC);
|
||||||
|
|
||||||
|
LocalDateTime[] result = new LocalDateTime[count];
|
||||||
|
for( int i = 0; i < count; i++ ) {
|
||||||
|
result[i] = LocalDateTime.ofEpochSecond((int) Constants.interpolate(from, to, random()), 0, ZoneOffset.UTC);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 ) {
|
||||||
|
String basetext = "";
|
||||||
|
try(
|
||||||
|
InputStream in = Faker.class.getResourceAsStream("mock-text.txt");
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(in))
|
||||||
|
) {
|
||||||
|
basetext = reader.readLine();
|
||||||
|
} catch( IOException ex ) {
|
||||||
|
LOG.error(ex, "Error generating fake blindtext: " + ex.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
String result = basetext.repeat(words / 283);
|
||||||
|
|
||||||
|
int w = (words % 283);
|
||||||
|
if( w > 0 ) {
|
||||||
|
Matcher m = Pattern.compile("([a-zA-Z_0-9\\u00C0-\\u017F]+\\W+){" + w + "}").matcher(basetext);
|
||||||
|
if( m.find() ) {
|
||||||
|
result += m.group().stripTrailing();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( paragraphs > 1 ) {
|
||||||
|
int half = words / paragraphs;
|
||||||
|
final int maxLength = result.length();
|
||||||
|
Matcher m = Pattern.compile("([a-zA-Z_0-9\\u00C0-\\u017F]+\\W+){" + (half - 10) + "," + (half + 10) + "}([a-zA-Z_0-9\\u00C0-\\u017F]+)\\.").matcher(result);
|
||||||
|
if( m.find() ) {
|
||||||
|
result = m.replaceAll(( mr ) -> mr.end() == maxLength ? mr.group().trim() : mr.group().trim() + "\n\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 ) {
|
||||||
|
int[] arr = new int[count];
|
||||||
|
for( int i = 0; i < count; i++ ) {
|
||||||
|
arr[i] = random(min, max);
|
||||||
|
}
|
||||||
|
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 ) {
|
||||||
|
List<Integer> result = (list == null) ? new ArrayList<>(count) : list;
|
||||||
|
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++ ) {
|
||||||
|
con.accept(random(min, max));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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" )
|
||||||
|
private static <T> T[] randomSample( String filename, int count, Function<String, T> transformer, Class<T> type ) {
|
||||||
|
T[] result = (T[]) Array.newInstance(type, count);
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
double k = count; // cast to double
|
||||||
|
|
||||||
|
double W = exp(log(random()) / k);
|
||||||
|
|
||||||
|
try(
|
||||||
|
InputStream in = Faker.class.getResourceAsStream("mock-" + filename + ".csv");
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(in))
|
||||||
|
) {
|
||||||
|
String line;
|
||||||
|
while( (line = reader.readLine()) != null ) {
|
||||||
|
if( i < count ) {
|
||||||
|
result[i] = transformer.apply(line);
|
||||||
|
i += 1;
|
||||||
|
} else {
|
||||||
|
int j = (int) (log(random()) / log(1 - W)) + 1;
|
||||||
|
while( j > 0 ) {
|
||||||
|
line = reader.readLine();
|
||||||
|
j -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( line != null ) {
|
||||||
|
result[random(0, count - 1)] = transformer.apply(line);
|
||||||
|
i += 1;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
W *= exp(log(random()) / k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill remaining array
|
||||||
|
while( i < count ) {
|
||||||
|
result[i] = result[random(0, i - 1)];
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
} catch( IOException ex ) {
|
||||||
|
LOG.error(ex, "Error loading mock data file: " + ex.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,7 +381,7 @@ public final class Faker {
|
|||||||
FontMetrics fontMerics = graphics.getFontMetrics();
|
FontMetrics fontMerics = graphics.getFontMetrics();
|
||||||
LineMetrics lineMetrics = fontMerics.getLineMetrics(text, graphics);
|
LineMetrics lineMetrics = fontMerics.getLineMetrics(text, graphics);
|
||||||
graphics.drawString(text,
|
graphics.drawString(text,
|
||||||
(int) ((width - fontMerics.stringWidth(text)) / 2),
|
(width - fontMerics.stringWidth(text)) / 2,
|
||||||
(int) (height / 2 - lineMetrics.getDescent() + lineMetrics.getAscent() / 2)
|
(int) (height / 2 - lineMetrics.getDescent() + lineMetrics.getAscent() / 2)
|
||||||
);
|
);
|
||||||
graphics.dispose();
|
graphics.dispose();
|
||||||
@@ -176,15 +392,9 @@ public final class Faker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String fakeText( int words, int paragraphs ) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String[][] loadMockfile( String name ) {
|
|
||||||
return FileLoader.loadCsv("schule/ngb/zm/util/mock-" + name + ".csv", true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Faker() {
|
private Faker() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final Log LOG = Log.getLogger(Faker.class);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,63 @@ import java.util.concurrent.ConcurrentMap;
|
|||||||
import java.util.concurrent.CopyOnWriteArraySet;
|
import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementierung einer EventListener-API basierend auf dem Artikel
|
||||||
|
* <a href="https://org.coloradomesa.edu/~wmacevoy/listen/paper.html">Skilled
|
||||||
|
* Listening in Java</a> von Dr. Warren D. MacEvoy.
|
||||||
|
* <p>
|
||||||
|
* Der {@code EventDispatcher} verwaltet eine Liste von angemeldeten Listenern
|
||||||
|
* in einem {@link CopyOnWriteArraySet}, dass besonders für Situationen geeignet
|
||||||
|
* ist, in denen in der Regel wenige Objekte verwaltet werden müssen, auf die
|
||||||
|
* schnell zugegriffen werden soll und die sich selten ändern. Das trifft in den
|
||||||
|
* meisten Fällen auf die Listener eines Objekts zu.
|
||||||
|
* <p>
|
||||||
|
* Um einen {@code EventDispatcher} zu nutzen, muss eine Schnittstelle als
|
||||||
|
* Unterinterface von {@link Listener} erstellt werden, die Methoden für die
|
||||||
|
* einzelnen Events definiert. In der Regel wird eine Methode pro Event
|
||||||
|
* angelegt, aber dies ist nicht unbedingt notwendig.
|
||||||
|
* <p>
|
||||||
|
* Ein Objekt, dass Events für diese Art von Listener erzeugt, erstellt einen
|
||||||
|
* {@code EventDispatcher} und
|
||||||
|
* {@link #registerEventType(String, BiConsumer) registriert} die notwendigen
|
||||||
|
* Events.
|
||||||
|
*
|
||||||
|
* <pre><code>
|
||||||
|
* EventDispatcher<MyEvent, MyEventListener> dispatcher = new EventDispatcher<>();
|
||||||
|
* dispatcher.registerEventType("start", (evt, listener) -> listener.started(evt));
|
||||||
|
* dispatcher.registerEventType("stop", (evt, listener) -> listener.stopped(evt));
|
||||||
|
* </code></pre>
|
||||||
|
* <p>
|
||||||
|
* Hier werden zwei Events registriert "start" und "stop". Die Bezeichnung der
|
||||||
|
* Events wird nut intern verwendet. Jedes Event registriert eine Funktion, die
|
||||||
|
* den Aufruf der Listener-Schnittstelle umsetzt. In der Regel ist dies ein
|
||||||
|
* Lambda-Ausdruck von zwei Parametern: dem Event-Objekt vom Typ {@code E} und
|
||||||
|
* der Listener, der aufgerufen werden soll, vom Typ {@code L}.
|
||||||
|
* <p>
|
||||||
|
* Nun können {@code MyEventListener} angemeldet und Events ausgelöst werden.
|
||||||
|
*
|
||||||
|
* <pre><code>
|
||||||
|
* public void addEventListener( MyEventListener listener ) {
|
||||||
|
* dispatcher.addListener(listener);
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* public void removeEventListener( MyEventListener listener ) {
|
||||||
|
* dispatcher.removeListener(listener);
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* public void methodThatCreatesEvents() {
|
||||||
|
* dispatcher.dispatchEvent("start", new MyEvent());
|
||||||
|
* // Do something
|
||||||
|
* dispatcher.dispatchEvent("stop", new MyEvent());
|
||||||
|
* }
|
||||||
|
* </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 <L> Typ der verwendeten Listener-Schnittstelle.
|
||||||
|
*/
|
||||||
public class EventDispatcher<E, L extends Listener<E>> {
|
public class EventDispatcher<E, L extends Listener<E>> {
|
||||||
|
|
||||||
private final CopyOnWriteArraySet<L> listeners;
|
private final CopyOnWriteArraySet<L> listeners;
|
||||||
@@ -19,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);
|
||||||
@@ -35,6 +92,7 @@ public class EventDispatcher<E, L extends Listener<E>> {
|
|||||||
listeners.remove(listener);
|
listeners.remove(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings( "unused" )
|
||||||
public boolean hasListeners() {
|
public boolean hasListeners() {
|
||||||
return !listeners.isEmpty();
|
return !listeners.isEmpty();
|
||||||
}
|
}
|
||||||
@@ -44,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);
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
package schule.ngb.zm.util.events;
|
package schule.ngb.zm.util.events;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generische Basisschnittstelle für die Implementierung von EventListenern.
|
||||||
|
*
|
||||||
|
* @param <E> Typ der Event-Objekte, die von diesem Listener empfangen werden.
|
||||||
|
* @see EventDispatcher
|
||||||
|
*/
|
||||||
public interface Listener<E> {
|
public interface Listener<E> {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -113,7 +126,7 @@ public class FontLoader {
|
|||||||
* @see #loadFont(String, String)
|
* @see #loadFont(String, String)
|
||||||
*/
|
*/
|
||||||
public static Font loadFonts( String name, String... sources ) {
|
public static Font loadFonts( String name, String... sources ) {
|
||||||
for( String fontSource: sources ) {
|
for( String fontSource : sources ) {
|
||||||
// TODO: Ignore exceptions in this case and throw own at end?
|
// TODO: Ignore exceptions in this case and throw own at end?
|
||||||
Font font = loadFont(name, fontSource);
|
Font font = loadFont(name, fontSource);
|
||||||
if( font != null ) {
|
if( font != null ) {
|
||||||
|
|||||||
@@ -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>
|
||||||
1
src/main/resources/schule/ngb/zm/util/mock-text.txt
Normal file
1
src/main/resources/schule/ngb/zm/util/mock-text.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Weit hinten, hinter den Wortbergen, fern der Länder Vokalien und Konsonantien leben die Blindtexte. Abgeschieden wohnen Sie in Buchstabhausen an der Küste des Semantik, eines großen Sprachozeans. Ein kleines Bächlein namens Duden fließt durch ihren Ort und versorgt sie mit den nötigen Regelialien. Es ist ein paradiesmatisches Land, in dem einem gebratene Satzteile in den Mund fliegen. Nicht einmal von der allmächtigen Interpunktion werden die Blindtexte beherrscht - ein geradezu unorthographisches Leben. Eines Tages aber beschloß eine kleine Zeile Blindtext, ihr Name war Lorem Ipsum, hinaus zu gehen in die weite Grammatik. Der große Oxmox riet ihr davon ab, da es dort wimmele von bösen Kommata, wilden Fragezeichen und hinterhältigen Semikoli, doch das Blindtextchen ließ sich nicht beirren. Es packte seine sieben Versalien, schob sich sein Initial in den Gürtel und machte sich auf den Weg. Als es die ersten Hügel des Kursivgebirges erklommen hatte, warf es einen letzten Blick zurück auf die Skyline seiner Heimatstadt Buchstabhausen, die Headline von Alphabetdorf und die Subline seiner eigenen Straße, der Zeilengasse. Wehmütig lief ihm eine rethorische Frage über die Wange, dann setzte es seinen Weg fort. Unterwegs traf es eine Copy. Die Copy warnte das Blindtextchen, da, wo sie herkäme wäre sie zigmal umgeschrieben worden und alles, was von ihrem Ursprung noch übrig wäre, sei das Wort "und" und das Blindtextchen solle umkehren und wieder in sein eigenes, sicheres Land zurückkehren. Doch alles Gutzureden konnte es nicht überzeugen und so dauerte es nicht lange, bis ihm ein paar heimtückische Werbetexter auflauerten, es mit Longe und Parole betrunken machten und es dann in ihre Agentur schleppten, wo sie es für ihre Projekte wieder und wieder mißbrauchten. Und wenn es nicht umgeschrieben wurde, dann benutzen Sie es immernoch.
|
||||||
File diff suppressed because it is too large
Load Diff
949
src/main/resources/schule/ngb/zm/util/mock-words.csv
Normal file
949
src/main/resources/schule/ngb/zm/util/mock-words.csv
Normal file
@@ -0,0 +1,949 @@
|
|||||||
|
Abbau
|
||||||
|
Abend
|
||||||
|
Abzug
|
||||||
|
Adler
|
||||||
|
Adolf
|
||||||
|
Agent
|
||||||
|
Akten
|
||||||
|
Aktie
|
||||||
|
Alarm
|
||||||
|
Album
|
||||||
|
Allee
|
||||||
|
Alpen
|
||||||
|
Ampel
|
||||||
|
Amtes
|
||||||
|
Anbau
|
||||||
|
Angst
|
||||||
|
Anruf
|
||||||
|
Anton
|
||||||
|
Anzug
|
||||||
|
Apple
|
||||||
|
April
|
||||||
|
Areal
|
||||||
|
Arena
|
||||||
|
Armee
|
||||||
|
Armut
|
||||||
|
Arten
|
||||||
|
Asien
|
||||||
|
Athen
|
||||||
|
Augen
|
||||||
|
Autor
|
||||||
|
Autos
|
||||||
|
Baden
|
||||||
|
Bande
|
||||||
|
Bands
|
||||||
|
Basel
|
||||||
|
Basis
|
||||||
|
Bauch
|
||||||
|
Bauer
|
||||||
|
Bayer
|
||||||
|
Beate
|
||||||
|
Beide
|
||||||
|
Beine
|
||||||
|
Beleg
|
||||||
|
Bernd
|
||||||
|
Beruf
|
||||||
|
Beste
|
||||||
|
Beton
|
||||||
|
Beute
|
||||||
|
Bevor
|
||||||
|
Bezug
|
||||||
|
Bibel
|
||||||
|
Bitte
|
||||||
|
Blatt
|
||||||
|
Blick
|
||||||
|
Block
|
||||||
|
Blues
|
||||||
|
Bogen
|
||||||
|
Bombe
|
||||||
|
Boris
|
||||||
|
Brand
|
||||||
|
Braun
|
||||||
|
Brief
|
||||||
|
Bruch
|
||||||
|
Bruno
|
||||||
|
Brust
|
||||||
|
Buben
|
||||||
|
Buche
|
||||||
|
Busse
|
||||||
|
Bäume
|
||||||
|
Böden
|
||||||
|
Börse
|
||||||
|
Bühne
|
||||||
|
Büros
|
||||||
|
Chaos
|
||||||
|
Chefs
|
||||||
|
Chile
|
||||||
|
China
|
||||||
|
Clubs
|
||||||
|
Coach
|
||||||
|
Daher
|
||||||
|
Damen
|
||||||
|
Daran
|
||||||
|
Darum
|
||||||
|
Datei
|
||||||
|
Daten
|
||||||
|
Datum
|
||||||
|
Dauer
|
||||||
|
David
|
||||||
|
Davon
|
||||||
|
Decke
|
||||||
|
Deren
|
||||||
|
Diana
|
||||||
|
Dinge
|
||||||
|
Donau
|
||||||
|
Doris
|
||||||
|
Drama
|
||||||
|
Dreck
|
||||||
|
Druck
|
||||||
|
Duell
|
||||||
|
Durch
|
||||||
|
Ecken
|
||||||
|
Edith
|
||||||
|
Eiern
|
||||||
|
Eisen
|
||||||
|
Elite
|
||||||
|
Engel
|
||||||
|
Enkel
|
||||||
|
Erbin
|
||||||
|
Erich
|
||||||
|
Erika
|
||||||
|
Erlös
|
||||||
|
Ernst
|
||||||
|
Erste
|
||||||
|
Erwin
|
||||||
|
Essen
|
||||||
|
Ethik
|
||||||
|
Fahne
|
||||||
|
Fahrt
|
||||||
|
Falls
|
||||||
|
Farbe
|
||||||
|
Faust
|
||||||
|
Fazit
|
||||||
|
Feier
|
||||||
|
Feind
|
||||||
|
Felix
|
||||||
|
Ferne
|
||||||
|
Feuer
|
||||||
|
Figur
|
||||||
|
Filme
|
||||||
|
Films
|
||||||
|
Firma
|
||||||
|
Fisch
|
||||||
|
Flüge
|
||||||
|
Focus
|
||||||
|
Folge
|
||||||
|
Fonds
|
||||||
|
Forum
|
||||||
|
Fotos
|
||||||
|
Foyer
|
||||||
|
Franc
|
||||||
|
Frank
|
||||||
|
Franz
|
||||||
|
Frist
|
||||||
|
Fritz
|
||||||
|
Front
|
||||||
|
Frust
|
||||||
|
Fuchs
|
||||||
|
Fulda
|
||||||
|
Fälle
|
||||||
|
Fülle
|
||||||
|
Füßen
|
||||||
|
Geben
|
||||||
|
Gebot
|
||||||
|
Gehen
|
||||||
|
Gehör
|
||||||
|
Geist
|
||||||
|
Georg
|
||||||
|
Gerda
|
||||||
|
Gerät
|
||||||
|
Geste
|
||||||
|
Glanz
|
||||||
|
Glück
|
||||||
|
Gnade
|
||||||
|
Gramm
|
||||||
|
Griff
|
||||||
|
Group
|
||||||
|
Grund
|
||||||
|
Grüne
|
||||||
|
Gunst
|
||||||
|
Gutes
|
||||||
|
Gäste
|
||||||
|
Güter
|
||||||
|
Haare
|
||||||
|
Haars
|
||||||
|
Hafen
|
||||||
|
Halle
|
||||||
|
Hanau
|
||||||
|
Handy
|
||||||
|
Harry
|
||||||
|
Hauch
|
||||||
|
Hause
|
||||||
|
Hebel
|
||||||
|
Heike
|
||||||
|
Heims
|
||||||
|
Heinz
|
||||||
|
Helga
|
||||||
|
Henry
|
||||||
|
Herrn
|
||||||
|
Hilfe
|
||||||
|
Hitze
|
||||||
|
Horst
|
||||||
|
Hosen
|
||||||
|
Hotel
|
||||||
|
Huber
|
||||||
|
Humor
|
||||||
|
Hunde
|
||||||
|
Hände
|
||||||
|
Härte
|
||||||
|
Hätte
|
||||||
|
Höhen
|
||||||
|
Hölle
|
||||||
|
Hörer
|
||||||
|
Hügel
|
||||||
|
Hürde
|
||||||
|
Hütte
|
||||||
|
Ideen
|
||||||
|
Image
|
||||||
|
Index
|
||||||
|
Indiz
|
||||||
|
Infos
|
||||||
|
Insel
|
||||||
|
Intel
|
||||||
|
Irene
|
||||||
|
Islam
|
||||||
|
Jahre
|
||||||
|
Jakob
|
||||||
|
James
|
||||||
|
Japan
|
||||||
|
Jeans
|
||||||
|
Jesus
|
||||||
|
Josef
|
||||||
|
Jubel
|
||||||
|
Juden
|
||||||
|
Julia
|
||||||
|
Jungs
|
||||||
|
Jutta
|
||||||
|
Jäger
|
||||||
|
Kabel
|
||||||
|
Kabul
|
||||||
|
Kader
|
||||||
|
Kairo
|
||||||
|
Kampf
|
||||||
|
Kanal
|
||||||
|
Karin
|
||||||
|
Karte
|
||||||
|
Kasse
|
||||||
|
Kenia
|
||||||
|
Kette
|
||||||
|
Kinos
|
||||||
|
Kirch
|
||||||
|
Klage
|
||||||
|
Klaus
|
||||||
|
Klima
|
||||||
|
Klubs
|
||||||
|
Kohle
|
||||||
|
Kohls
|
||||||
|
Konto
|
||||||
|
Kopie
|
||||||
|
Korea
|
||||||
|
Kraft
|
||||||
|
Krebs
|
||||||
|
Kreis
|
||||||
|
Kreml
|
||||||
|
Kreuz
|
||||||
|
Krieg
|
||||||
|
Kripo
|
||||||
|
Krise
|
||||||
|
Krone
|
||||||
|
Krupp
|
||||||
|
Kugel
|
||||||
|
Kunde
|
||||||
|
Kunst
|
||||||
|
Kurse
|
||||||
|
Kurve
|
||||||
|
König
|
||||||
|
Köpfe
|
||||||
|
Küche
|
||||||
|
Kürze
|
||||||
|
Küste
|
||||||
|
Labor
|
||||||
|
Lager
|
||||||
|
Laien
|
||||||
|
Lande
|
||||||
|
Lange
|
||||||
|
Laufe
|
||||||
|
Laune
|
||||||
|
Leder
|
||||||
|
Legen
|
||||||
|
Lehre
|
||||||
|
Leser
|
||||||
|
Leute
|
||||||
|
Licht
|
||||||
|
Linie
|
||||||
|
Linke
|
||||||
|
Liste
|
||||||
|
Liter
|
||||||
|
Logik
|
||||||
|
Lokal
|
||||||
|
Luxus
|
||||||
|
Lyrik
|
||||||
|
Löhne
|
||||||
|
Löwen
|
||||||
|
Lücke
|
||||||
|
Macht
|
||||||
|
Mafia
|
||||||
|
Magen
|
||||||
|
Mainz
|
||||||
|
Major
|
||||||
|
Maler
|
||||||
|
Maria
|
||||||
|
Marie
|
||||||
|
Mario
|
||||||
|
Marke
|
||||||
|
Markt
|
||||||
|
Maske
|
||||||
|
Masse
|
||||||
|
Match
|
||||||
|
Mauer
|
||||||
|
Media
|
||||||
|
Meist
|
||||||
|
Menge
|
||||||
|
Meter
|
||||||
|
Metro
|
||||||
|
Meyer
|
||||||
|
Miami
|
||||||
|
Milch
|
||||||
|
Minen
|
||||||
|
Mitte
|
||||||
|
Monat
|
||||||
|
Moral
|
||||||
|
Motiv
|
||||||
|
Motor
|
||||||
|
Motto
|
||||||
|
Musik
|
||||||
|
Möbel
|
||||||
|
Nacht
|
||||||
|
Nagel
|
||||||
|
Namen
|
||||||
|
Natur
|
||||||
|
Nazis
|
||||||
|
Nebel
|
||||||
|
Netze
|
||||||
|
Noten
|
||||||
|
Ohren
|
||||||
|
Onkel
|
||||||
|
Opfer
|
||||||
|
Orden
|
||||||
|
Organ
|
||||||
|
Orten
|
||||||
|
Oskar
|
||||||
|
Osten
|
||||||
|
Paare
|
||||||
|
Paket
|
||||||
|
Panik
|
||||||
|
Papst
|
||||||
|
Paris
|
||||||
|
Parks
|
||||||
|
Party
|
||||||
|
Pause
|
||||||
|
Pence
|
||||||
|
Peter
|
||||||
|
Petra
|
||||||
|
Pferd
|
||||||
|
Pfund
|
||||||
|
Phase
|
||||||
|
Pilot
|
||||||
|
Platz
|
||||||
|
Polen
|
||||||
|
Preis
|
||||||
|
Prinz
|
||||||
|
Probe
|
||||||
|
Profi
|
||||||
|
Prosa
|
||||||
|
Punkt
|
||||||
|
Quote
|
||||||
|
Rache
|
||||||
|
Radio
|
||||||
|
Rande
|
||||||
|
Rasen
|
||||||
|
Ratte
|
||||||
|
Rauch
|
||||||
|
Regel
|
||||||
|
Regen
|
||||||
|
Regie
|
||||||
|
Reihe
|
||||||
|
Rente
|
||||||
|
Reste
|
||||||
|
Rhein
|
||||||
|
Rolle
|
||||||
|
Roman
|
||||||
|
Route
|
||||||
|
Rover
|
||||||
|
Rubel
|
||||||
|
Russe
|
||||||
|
Räder
|
||||||
|
Römer
|
||||||
|
Sache
|
||||||
|
Sauna
|
||||||
|
Schau
|
||||||
|
Schoß
|
||||||
|
Seele
|
||||||
|
Segen
|
||||||
|
Seite
|
||||||
|
Sekte
|
||||||
|
Senat
|
||||||
|
Seoul
|
||||||
|
Serie
|
||||||
|
Shell
|
||||||
|
Sicht
|
||||||
|
Simon
|
||||||
|
Sinne
|
||||||
|
Smith
|
||||||
|
Songs
|
||||||
|
Sonne
|
||||||
|
Sound
|
||||||
|
Spiel
|
||||||
|
Sport
|
||||||
|
Spree
|
||||||
|
Staat
|
||||||
|
Stadt
|
||||||
|
Stamm
|
||||||
|
Stars
|
||||||
|
Start
|
||||||
|
Stasi
|
||||||
|
Staub
|
||||||
|
Staus
|
||||||
|
Stein
|
||||||
|
Stern
|
||||||
|
Stirn
|
||||||
|
Stock
|
||||||
|
Stoff
|
||||||
|
Story
|
||||||
|
Strom
|
||||||
|
Stufe
|
||||||
|
Stuhl
|
||||||
|
Sturm
|
||||||
|
Sturz
|
||||||
|
Stück
|
||||||
|
Summe
|
||||||
|
Szene
|
||||||
|
Sätze
|
||||||
|
Söhne
|
||||||
|
Süden
|
||||||
|
Tagen
|
||||||
|
Tages
|
||||||
|
Tante
|
||||||
|
Tarif
|
||||||
|
Teams
|
||||||
|
Tempo
|
||||||
|
Tenor
|
||||||
|
Tests
|
||||||
|
Texas
|
||||||
|
Texte
|
||||||
|
Thema
|
||||||
|
These
|
||||||
|
Thron
|
||||||
|
Tiefs
|
||||||
|
Tiere
|
||||||
|
Tiger
|
||||||
|
Tisch
|
||||||
|
Titel
|
||||||
|
Todes
|
||||||
|
Tokio
|
||||||
|
Tonne
|
||||||
|
Toren
|
||||||
|
Traum
|
||||||
|
Treff
|
||||||
|
Trend
|
||||||
|
Trick
|
||||||
|
Trios
|
||||||
|
Trost
|
||||||
|
Typen
|
||||||
|
Täter
|
||||||
|
Tönen
|
||||||
|
Türen
|
||||||
|
Türke
|
||||||
|
UdSSR
|
||||||
|
Uhren
|
||||||
|
Ulmer
|
||||||
|
Umbau
|
||||||
|
Umzug
|
||||||
|
Union
|
||||||
|
Unmut
|
||||||
|
Vater
|
||||||
|
Vetos
|
||||||
|
Video
|
||||||
|
Villa
|
||||||
|
Viren
|
||||||
|
Virus
|
||||||
|
Vogts
|
||||||
|
Votum
|
||||||
|
Vögel
|
||||||
|
Waffe
|
||||||
|
Weber
|
||||||
|
Weile
|
||||||
|
Welle
|
||||||
|
Werke
|
||||||
|
Werks
|
||||||
|
Wesen
|
||||||
|
Wiese
|
||||||
|
Wille
|
||||||
|
Willi
|
||||||
|
Wirte
|
||||||
|
Wirts
|
||||||
|
Witwe
|
||||||
|
Witze
|
||||||
|
Woche
|
||||||
|
Wolke
|
||||||
|
World
|
||||||
|
Worte
|
||||||
|
Wände
|
||||||
|
Wüste
|
||||||
|
Zaire
|
||||||
|
Zeile
|
||||||
|
Zelle
|
||||||
|
Zeugs
|
||||||
|
Ziels
|
||||||
|
Zitat
|
||||||
|
Zorns
|
||||||
|
Zunge
|
||||||
|
Zweck
|
||||||
|
Zähne
|
||||||
|
Zügen
|
||||||
|
ahnen
|
||||||
|
ahnst
|
||||||
|
aktiv
|
||||||
|
allem
|
||||||
|
allen
|
||||||
|
aller
|
||||||
|
alles
|
||||||
|
allzu
|
||||||
|
altem
|
||||||
|
alten
|
||||||
|
altes
|
||||||
|
angab
|
||||||
|
armen
|
||||||
|
außen
|
||||||
|
außer
|
||||||
|
bauen
|
||||||
|
baute
|
||||||
|
bekam
|
||||||
|
berge
|
||||||
|
berät
|
||||||
|
besaß
|
||||||
|
bezog
|
||||||
|
biete
|
||||||
|
birgt
|
||||||
|
blaue
|
||||||
|
blieb
|
||||||
|
blind
|
||||||
|
bloße
|
||||||
|
boten
|
||||||
|
brach
|
||||||
|
breit
|
||||||
|
bunte
|
||||||
|
bösen
|
||||||
|
dabei
|
||||||
|
dafür
|
||||||
|
dahin
|
||||||
|
damit
|
||||||
|
darin
|
||||||
|
davor
|
||||||
|
deine
|
||||||
|
denen
|
||||||
|
denke
|
||||||
|
denkt
|
||||||
|
derer
|
||||||
|
desto
|
||||||
|
dicht
|
||||||
|
dicke
|
||||||
|
dient
|
||||||
|
diese
|
||||||
|
drauf
|
||||||
|
dreht
|
||||||
|
drohe
|
||||||
|
droht
|
||||||
|
dürfe
|
||||||
|
ebene
|
||||||
|
echte
|
||||||
|
ehren
|
||||||
|
eigen
|
||||||
|
einem
|
||||||
|
einen
|
||||||
|
einer
|
||||||
|
eines
|
||||||
|
einig
|
||||||
|
einst
|
||||||
|
eisig
|
||||||
|
elend
|
||||||
|
enden
|
||||||
|
endet
|
||||||
|
engem
|
||||||
|
engen
|
||||||
|
enger
|
||||||
|
enges
|
||||||
|
enorm
|
||||||
|
erben
|
||||||
|
ergab
|
||||||
|
erhob
|
||||||
|
ernte
|
||||||
|
etwas
|
||||||
|
ewige
|
||||||
|
exakt
|
||||||
|
extra
|
||||||
|
fehle
|
||||||
|
fehlt
|
||||||
|
feine
|
||||||
|
feste
|
||||||
|
finde
|
||||||
|
folgt
|
||||||
|
frage
|
||||||
|
fragt
|
||||||
|
freie
|
||||||
|
fremd
|
||||||
|
freut
|
||||||
|
frühe
|
||||||
|
fähig
|
||||||
|
fällt
|
||||||
|
fängt
|
||||||
|
fügte
|
||||||
|
fühle
|
||||||
|
fühlt
|
||||||
|
führe
|
||||||
|
führt
|
||||||
|
füllt
|
||||||
|
gaben
|
||||||
|
ganze
|
||||||
|
gegen
|
||||||
|
gelbe
|
||||||
|
gelte
|
||||||
|
gemäß
|
||||||
|
genau
|
||||||
|
genug
|
||||||
|
gerne
|
||||||
|
getan
|
||||||
|
geübt
|
||||||
|
ginge
|
||||||
|
gingt
|
||||||
|
glatt
|
||||||
|
graue
|
||||||
|
große
|
||||||
|
gutem
|
||||||
|
guten
|
||||||
|
haben
|
||||||
|
halbe
|
||||||
|
halte
|
||||||
|
heben
|
||||||
|
hebst
|
||||||
|
hefte
|
||||||
|
heiße
|
||||||
|
heißt
|
||||||
|
helle
|
||||||
|
heran
|
||||||
|
herum
|
||||||
|
heuer
|
||||||
|
heute
|
||||||
|
hielt
|
||||||
|
hilft
|
||||||
|
hinzu
|
||||||
|
hoffe
|
||||||
|
hofft
|
||||||
|
hohem
|
||||||
|
hohes
|
||||||
|
holen
|
||||||
|
holte
|
||||||
|
hängt
|
||||||
|
höher
|
||||||
|
hören
|
||||||
|
hörte
|
||||||
|
ideal
|
||||||
|
ihnen
|
||||||
|
ihrem
|
||||||
|
ihren
|
||||||
|
ihrer
|
||||||
|
ihres
|
||||||
|
immer
|
||||||
|
indem
|
||||||
|
indes
|
||||||
|
innen
|
||||||
|
jedem
|
||||||
|
jeden
|
||||||
|
jeder
|
||||||
|
jedes
|
||||||
|
jeher
|
||||||
|
jenem
|
||||||
|
jenen
|
||||||
|
jener
|
||||||
|
jenes
|
||||||
|
jetzt
|
||||||
|
junge
|
||||||
|
kalte
|
||||||
|
kauft
|
||||||
|
kehrt
|
||||||
|
keine
|
||||||
|
kenne
|
||||||
|
kennt
|
||||||
|
klagt
|
||||||
|
klang
|
||||||
|
klare
|
||||||
|
klein
|
||||||
|
knapp
|
||||||
|
komme
|
||||||
|
kommt
|
||||||
|
krank
|
||||||
|
kämen
|
||||||
|
könne
|
||||||
|
lacht
|
||||||
|
laden
|
||||||
|
lagen
|
||||||
|
lasse
|
||||||
|
leben
|
||||||
|
lebte
|
||||||
|
leere
|
||||||
|
legte
|
||||||
|
lehnt
|
||||||
|
lehrt
|
||||||
|
leise
|
||||||
|
lernt
|
||||||
|
lesen
|
||||||
|
liebe
|
||||||
|
liebt
|
||||||
|
liege
|
||||||
|
liegt
|
||||||
|
liest
|
||||||
|
ließe
|
||||||
|
links
|
||||||
|
lobte
|
||||||
|
lockt
|
||||||
|
lohnt
|
||||||
|
lässt
|
||||||
|
läuft
|
||||||
|
lösen
|
||||||
|
löste
|
||||||
|
mache
|
||||||
|
manch
|
||||||
|
meine
|
||||||
|
meint
|
||||||
|
merkt
|
||||||
|
messe
|
||||||
|
miete
|
||||||
|
minus
|
||||||
|
morde
|
||||||
|
mögen
|
||||||
|
müsse
|
||||||
|
nahen
|
||||||
|
neben
|
||||||
|
nebst
|
||||||
|
nehme
|
||||||
|
nennt
|
||||||
|
nette
|
||||||
|
neuem
|
||||||
|
neuen
|
||||||
|
neuer
|
||||||
|
neues
|
||||||
|
nicht
|
||||||
|
nimmt
|
||||||
|
näher
|
||||||
|
nötig
|
||||||
|
nützt
|
||||||
|
obere
|
||||||
|
offen
|
||||||
|
ordne
|
||||||
|
packe
|
||||||
|
packt
|
||||||
|
parat
|
||||||
|
passe
|
||||||
|
passt
|
||||||
|
plane
|
||||||
|
plant
|
||||||
|
prüfe
|
||||||
|
prüft
|
||||||
|
quasi
|
||||||
|
rasch
|
||||||
|
raten
|
||||||
|
reale
|
||||||
|
recht
|
||||||
|
reden
|
||||||
|
redet
|
||||||
|
reich
|
||||||
|
reine
|
||||||
|
reise
|
||||||
|
reist
|
||||||
|
rollt
|
||||||
|
rotem
|
||||||
|
roten
|
||||||
|
roter
|
||||||
|
rotes
|
||||||
|
rufen
|
||||||
|
ruhen
|
||||||
|
ruhig
|
||||||
|
runde
|
||||||
|
räume
|
||||||
|
räumt
|
||||||
|
rückt
|
||||||
|
sagen
|
||||||
|
sagte
|
||||||
|
sahen
|
||||||
|
sanft
|
||||||
|
satte
|
||||||
|
sauer
|
||||||
|
saßen
|
||||||
|
schuf
|
||||||
|
schön
|
||||||
|
sechs
|
||||||
|
sehen
|
||||||
|
seien
|
||||||
|
seine
|
||||||
|
sende
|
||||||
|
setze
|
||||||
|
setzt
|
||||||
|
siege
|
||||||
|
siehe
|
||||||
|
sieht
|
||||||
|
singt
|
||||||
|
sinkt
|
||||||
|
sitze
|
||||||
|
sitzt
|
||||||
|
sogar
|
||||||
|
solch
|
||||||
|
solle
|
||||||
|
sollt
|
||||||
|
somit
|
||||||
|
sonst
|
||||||
|
sorge
|
||||||
|
sorgt
|
||||||
|
sowie
|
||||||
|
spare
|
||||||
|
spart
|
||||||
|
spitz
|
||||||
|
späte
|
||||||
|
spürt
|
||||||
|
stahl
|
||||||
|
stand
|
||||||
|
starb
|
||||||
|
stark
|
||||||
|
statt
|
||||||
|
stehe
|
||||||
|
steht
|
||||||
|
stets
|
||||||
|
stich
|
||||||
|
stieg
|
||||||
|
stieß
|
||||||
|
still
|
||||||
|
stolz
|
||||||
|
stoße
|
||||||
|
stört
|
||||||
|
stößt
|
||||||
|
suche
|
||||||
|
sucht
|
||||||
|
tanze
|
||||||
|
tanzt
|
||||||
|
taten
|
||||||
|
teile
|
||||||
|
teils
|
||||||
|
teilt
|
||||||
|
teuer
|
||||||
|
teure
|
||||||
|
tiefe
|
||||||
|
tolle
|
||||||
|
total
|
||||||
|
totem
|
||||||
|
toter
|
||||||
|
totes
|
||||||
|
traue
|
||||||
|
traut
|
||||||
|
treue
|
||||||
|
trieb
|
||||||
|
trink
|
||||||
|
tritt
|
||||||
|
trotz
|
||||||
|
träge
|
||||||
|
trägt
|
||||||
|
tätig
|
||||||
|
töten
|
||||||
|
tötet
|
||||||
|
unser
|
||||||
|
unten
|
||||||
|
unter
|
||||||
|
vagem
|
||||||
|
vagen
|
||||||
|
vager
|
||||||
|
vages
|
||||||
|
viele
|
||||||
|
volle
|
||||||
|
vorab
|
||||||
|
voran
|
||||||
|
vorne
|
||||||
|
wagen
|
||||||
|
wahre
|
||||||
|
waren
|
||||||
|
warnt
|
||||||
|
warst
|
||||||
|
warte
|
||||||
|
warum
|
||||||
|
wecke
|
||||||
|
weckt
|
||||||
|
weder
|
||||||
|
wegen
|
||||||
|
wehre
|
||||||
|
wehrt
|
||||||
|
weich
|
||||||
|
weist
|
||||||
|
weite
|
||||||
|
weiße
|
||||||
|
welch
|
||||||
|
wende
|
||||||
|
wenig
|
||||||
|
werbe
|
||||||
|
werde
|
||||||
|
werte
|
||||||
|
wider
|
||||||
|
widme
|
||||||
|
wiegt
|
||||||
|
wilde
|
||||||
|
wirbt
|
||||||
|
wirft
|
||||||
|
wirke
|
||||||
|
wirkt
|
||||||
|
wisse
|
||||||
|
wobei
|
||||||
|
wofür
|
||||||
|
woher
|
||||||
|
wohin
|
||||||
|
wohne
|
||||||
|
wohnt
|
||||||
|
wolle
|
||||||
|
womit
|
||||||
|
wovon
|
||||||
|
wuchs
|
||||||
|
wunde
|
||||||
|
wurde
|
||||||
|
wähle
|
||||||
|
wählt
|
||||||
|
wärme
|
||||||
|
zahle
|
||||||
|
zeige
|
||||||
|
zeigt
|
||||||
|
zeuge
|
||||||
|
zieht
|
||||||
|
ziele
|
||||||
|
zielt
|
||||||
|
zivil
|
||||||
|
zogen
|
||||||
|
zudem
|
||||||
|
zumal
|
||||||
|
zuvor
|
||||||
|
zwang
|
||||||
|
zwölf
|
||||||
|
zählt
|
||||||
|
zügig
|
||||||
|
Ämter
|
||||||
|
Ärger
|
||||||
|
Ärzte
|
||||||
|
Übung
|
||||||
|
älter
|
||||||
|
öffne
|
||||||
|
öfter
|
||||||
|
übrig
|
||||||
|
übten
|
||||||
|
@@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -5,7 +5,8 @@ import schule.ngb.zm.util.io.FileLoader;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
class FileLoaderTest {
|
class FileLoaderTest {
|
||||||
|
|
||||||
@@ -71,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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user