mirror of
https://github.com/jneug/zeichenmaschine.git
synced 2026-10-12 09:56:33 +02:00
Compare commits
16 Commits
v0.0.34-SN
...
f0a3c65552
| Author | SHA1 | Date | |
|---|---|---|---|
| f0a3c65552 | |||
| 13cad69e1d | |||
| bd718ba27d | |||
| 1895378978 | |||
| 2f845bdcd9 | |||
| 788ed888e9 | |||
| ce3ffee4da | |||
| 74c85e0f61 | |||
| c7b2a520c4 | |||
| c5c046521b | |||
| 9834b9c389 | |||
| eaaca6b90f | |||
| 1275af55f3 | |||
| 632038030e | |||
| cec17f0d7c | |||
| b29532bf6e |
12
CHANGELOG.md
12
CHANGELOG.md
@@ -6,6 +6,18 @@ und diese Projekt folgt [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## Added
|
||||||
|
- Dokumentation `EventDispatcher`.
|
||||||
|
|
||||||
|
## Changed
|
||||||
|
- Die Methoden in `Validator` erwarten nun als zweiten Parameter den Namen des Parameters, der geprüft wird.
|
||||||
|
|
||||||
|
## Fixed
|
||||||
|
- `Constants.choice(int...)` und `Constants.choice(double...)` wiederhergestellt.
|
||||||
|
|
||||||
|
## Removed
|
||||||
|
- `layers.Shape2DLayer` ist nur noch im Test-Paket verfügbar.
|
||||||
|
|
||||||
## Version 0.0.34
|
## Version 0.0.34
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
24
build.gradle
24
build.gradle
@@ -33,16 +33,36 @@ dependencies {
|
|||||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
|
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
javadoc {
|
||||||
|
options {
|
||||||
|
encoding = "UTF-8"
|
||||||
|
overview = "src/main/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")
|
||||||
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register('jarMP3SPI', Jar) {
|
tasks.register('jarMP3SPI', Jar) {
|
||||||
archiveClassifier = 'all'
|
archiveClassifier = 'mp3spi'
|
||||||
duplicatesStrategy = 'exclude'
|
duplicatesStrategy = 'exclude'
|
||||||
archivesBaseName = 'zeichenmaschine-mp3spi'
|
// archivesBaseName = 'zeichenmaschine-mp3spi'
|
||||||
from {
|
from {
|
||||||
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
|
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
|
||||||
}
|
}
|
||||||
with jar
|
with jar
|
||||||
}
|
}
|
||||||
|
|
||||||
|
task buildAll {
|
||||||
|
dependsOn 'jar'
|
||||||
|
dependsOn 'jarMP3SPI'
|
||||||
|
dependsOn 'sourcesJar'
|
||||||
|
dependsOn 'javadocJar'
|
||||||
|
}
|
||||||
|
|||||||
9
src/main/java/overview.html
Normal file
9
src/main/java/overview.html
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<HTML>
|
||||||
|
<BODY>
|
||||||
|
Die Zeichenmaschine
|
||||||
|
<p>
|
||||||
|
<h1>Die Zeichenmaschine</h1>
|
||||||
|
|
||||||
|
@author J. Neugebauer
|
||||||
|
</BODY>
|
||||||
|
</HTML>
|
||||||
@@ -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;
|
||||||
@@ -24,7 +23,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 {
|
||||||
@@ -1256,7 +1255,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1321,7 +1320,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 +1332,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.
|
||||||
@@ -1395,9 +1395,9 @@ public class Constants {
|
|||||||
* @param values Ein Array mit Werten, die zur Auswahl stehen.
|
* @param values Ein Array mit Werten, die zur Auswahl stehen.
|
||||||
* @return Ein zufälliges Element aus dem Array.
|
* @return Ein zufälliges Element aus dem Array.
|
||||||
*/
|
*/
|
||||||
/*public static final int choice( int... values ) {
|
public static final int choice( int... values ) {
|
||||||
return values[random(0, values.length - 1)];
|
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.
|
||||||
@@ -1405,9 +1405,9 @@ public class Constants {
|
|||||||
* @param values Ein Array mit Werten, die zur Auswahl stehen.
|
* @param values Ein Array mit Werten, die zur Auswahl stehen.
|
||||||
* @return Ein zufälliges Element aus dem Array.
|
* @return Ein zufälliges Element aus dem Array.
|
||||||
*/
|
*/
|
||||||
/*public static final double choice( double... values ) {
|
public static final double choice( double... values ) {
|
||||||
return values[random(0, values.length - 1)];
|
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 +1416,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)];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -201,6 +201,8 @@ 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 ) {
|
||||||
|
|||||||
@@ -156,7 +156,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>.");
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ public class AnimationFacade<S> extends Animation<S> {
|
|||||||
|
|
||||||
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
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ public class AnimationGroup<T> extends Animation<T> {
|
|||||||
|
|
||||||
List<Animation<T>> anims;
|
List<Animation<T>> anims;
|
||||||
|
|
||||||
|
|
||||||
private boolean overrideEasing = false;
|
private boolean overrideEasing = false;
|
||||||
|
|
||||||
private int overrideRuntime = -1;
|
private int overrideRuntime = -1;
|
||||||
|
|||||||
@@ -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)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
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;
|
||||||
8
src/main/java/schule/ngb/zm/layers/package-info.java
Normal file
8
src/main/java/schule/ngb/zm/layers/package-info.java
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* Dieses Paket enthält implementationen der abstrakten
|
||||||
|
* {@link schule.ngb.zm.Layer} Klasse.
|
||||||
|
* <p>
|
||||||
|
* {@code Layer} sind Ebenen, die der {@link schule.ngb.zm.Zeichenleinwand}
|
||||||
|
* hinzugefügt und pro Frame gerendert werden.
|
||||||
|
*/
|
||||||
|
package schule.ngb.zm.layers;
|
||||||
@@ -84,7 +84,7 @@ public class Music implements Audio {
|
|||||||
* @see ResourceStreamProvider#getResourceURL(String)
|
* @see ResourceStreamProvider#getResourceURL(String)
|
||||||
*/
|
*/
|
||||||
public Music( String audioSource ) {
|
public Music( String audioSource ) {
|
||||||
Validator.requireNotNull(audioSource);
|
Validator.requireNotNull(audioSource, "audioSource");
|
||||||
this.audioSource = audioSource;
|
this.audioSource = audioSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ public class Sound implements Audio {
|
|||||||
* @see ResourceStreamProvider#getResourceURL(String)
|
* @see ResourceStreamProvider#getResourceURL(String)
|
||||||
*/
|
*/
|
||||||
public Sound( String source ) {
|
public Sound( String source ) {
|
||||||
Validator.requireNotNull(source);
|
Validator.requireNotNull(source, "source");
|
||||||
this.audioSource = source;
|
this.audioSource = source;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
7
src/main/java/schule/ngb/zm/media/package-info.java
Normal file
7
src/main/java/schule/ngb/zm/media/package-info.java
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* Dieses Paket enthält Klassen zur Einbettung von Mediendateien.
|
||||||
|
* <p>
|
||||||
|
* Mit Medien sind vor allem Audio und Videodateien gemeint. Aktuell kann die
|
||||||
|
* Zeichenmaschine Audiodateien verwenden.
|
||||||
|
*/
|
||||||
|
package schule.ngb.zm.media;
|
||||||
16
src/main/java/schule/ngb/zm/ml/package-info.java
Normal file
16
src/main/java/schule/ngb/zm/ml/package-info.java
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Dieses Paekt enthält Klassen für Experimente mit Verfahren des maschinellen
|
||||||
|
* Lernens (ML).
|
||||||
|
* <p>
|
||||||
|
* Die hier implementierten Klassen sind eine prototypische Umsetzung von
|
||||||
|
* einfachen neuronalen Netzwerken, mit denen an kleinen Problemstellungen
|
||||||
|
* experimentell Modelle trainiert und angewandt werden können.
|
||||||
|
* <p>
|
||||||
|
* Die Implementierungen sind nicht optimiert und setzen auf native
|
||||||
|
* Java-Methoden. Daher sind sie nur für die Anwendung auf extrem kleine Modelle
|
||||||
|
* in Bildungskontexten gedacht.
|
||||||
|
* <p>
|
||||||
|
* Durch Einbettung wissenschaftlicher Bibliotheken mit optimierten Operationen
|
||||||
|
* lassen sich bessere Ergebnisse erreichen.
|
||||||
|
*/
|
||||||
|
package schule.ngb.zm.ml;
|
||||||
13
src/main/java/schule/ngb/zm/package-info.java
Normal file
13
src/main/java/schule/ngb/zm/package-info.java
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* <h2>Die Zeichenmaschine</h2>
|
||||||
|
* <p>
|
||||||
|
* Die <b>Zeichenmaschine</b> ist eine für den Informatikunterricht entwickelte
|
||||||
|
* Bibliothek, die unter anderem an <a
|
||||||
|
* href="https://processing.org/">Processing</a> angelehnt ist. Die Bibliothek
|
||||||
|
* soll einige der üblichen Anfängerschwierigkeiten mit Java vereinfachen und
|
||||||
|
* für Schülerinnen und Schüler im Unterricht nutzbar machen.
|
||||||
|
* <p>
|
||||||
|
* Eine umfassende Dokumentation ist unter <a
|
||||||
|
* href="https://zeichenmaschine.xyz">zeichenmaschine.xyz</a> verfügbar.
|
||||||
|
*/
|
||||||
|
package schule.ngb.zm;
|
||||||
@@ -4,13 +4,14 @@ import schule.ngb.zm.BasicDrawable;
|
|||||||
import schule.ngb.zm.Fillable;
|
import schule.ngb.zm.Fillable;
|
||||||
import schule.ngb.zm.Options;
|
import schule.ngb.zm.Options;
|
||||||
import schule.ngb.zm.Strokeable;
|
import schule.ngb.zm.Strokeable;
|
||||||
|
import schule.ngb.zm.util.Validator;
|
||||||
|
|
||||||
import java.awt.Graphics2D;
|
import java.awt.Graphics2D;
|
||||||
import java.awt.geom.AffineTransform;
|
import java.awt.geom.AffineTransform;
|
||||||
import java.awt.geom.Point2D;
|
import java.awt.geom.Point2D;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Basisklasse für alle Formen in der Zeichenmaschine.
|
* Dies ist die Basisklasse für alle Formen in der Zeichenmaschine.
|
||||||
* <p>
|
* <p>
|
||||||
* Alle Formen sind als Unterklassen von {@code Shape} implementiert.
|
* Alle Formen sind als Unterklassen von {@code Shape} implementiert.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -19,7 +20,6 @@ import java.awt.geom.Point2D;
|
|||||||
* Parametern initialisiert und einen, der die Werte einer anderen Form
|
* Parametern initialisiert und einen, der die Werte einer anderen Form
|
||||||
* desselben Typs übernimmt. In der Klasse {@link Circle} sind die Konstruktoren
|
* desselben Typs übernimmt. In der Klasse {@link Circle} sind die Konstruktoren
|
||||||
* beispielsweise so implementiert:
|
* beispielsweise so implementiert:
|
||||||
*
|
|
||||||
* <pre><code>
|
* <pre><code>
|
||||||
* public Circle( double x, double y, double radius ) {
|
* public Circle( double x, double y, double radius ) {
|
||||||
* super(x, y);
|
* super(x, y);
|
||||||
@@ -39,42 +39,42 @@ import java.awt.geom.Point2D;
|
|||||||
public abstract class Shape extends BasicDrawable {
|
public abstract class Shape extends BasicDrawable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* x-Koordinate der Form.
|
* Speichert die x-Koordinate der Form.
|
||||||
*/
|
*/
|
||||||
protected double x;
|
protected double x;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* y-Koordinate der Form.
|
* Speichert die y-Koordinate der Form.
|
||||||
*/
|
*/
|
||||||
protected double y;
|
protected double y;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rotation in Grad um den Punkt (x, y).
|
* Speichert die Rotation in Grad um den Punkt (x, y).
|
||||||
*/
|
*/
|
||||||
protected double rotation = 0.0;
|
protected double rotation = 0.0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Skalierungsfaktor.
|
* Speichert den Skalierungsfaktor.
|
||||||
*/
|
*/
|
||||||
protected double scale = 1.0;
|
protected double scale = 1.0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ankerpunkt der Form.
|
* Speichert den Ankerpunkt.
|
||||||
*/
|
*/
|
||||||
protected Options.Direction anchor = Options.Direction.CENTER;
|
protected Options.Direction anchor = Options.Direction.CENTER;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setzt die x- und y-Koordinate der Form auf 0.
|
* Erstellt eine neue Form mit den Koordinaten {@code (0,0)}.
|
||||||
*/
|
*/
|
||||||
public Shape() {
|
public Shape() {
|
||||||
this(0.0, 0.0);
|
this(0.0, 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setzt die x- und y-Koordinate der Form.
|
* Erstellt eine Form mit den angegebenen Koordinaten.
|
||||||
*
|
*
|
||||||
* @param x
|
* @param x Die x-Koordinate.
|
||||||
* @param y
|
* @param y Die y-Koordinate.
|
||||||
*/
|
*/
|
||||||
public Shape( double x, double y ) {
|
public Shape( double x, double y ) {
|
||||||
this.x = x;
|
this.x = x;
|
||||||
@@ -82,7 +82,7 @@ public abstract class Shape extends BasicDrawable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gibt die x-Koordinate der Form zurück.
|
* Liefert die aktuelle x-Koordinate der Form.
|
||||||
*
|
*
|
||||||
* @return Die x-Koordinate.
|
* @return Die x-Koordinate.
|
||||||
*/
|
*/
|
||||||
@@ -100,7 +100,7 @@ public abstract class Shape extends BasicDrawable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gibt die y-Koordinate der Form zurück.
|
* Liefert die aktuelle y-Koordinate der Form.
|
||||||
*
|
*
|
||||||
* @return Die y-Koordinate.
|
* @return Die y-Koordinate.
|
||||||
*/
|
*/
|
||||||
@@ -109,57 +109,44 @@ public abstract class Shape extends BasicDrawable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setzt die y-Koordinate der Form.
|
* Setzt die x-Koordinate der Form.
|
||||||
*
|
*
|
||||||
* @param y Die neue y-Koordinate.
|
* @param y Die neue y-Koordinate der Form.
|
||||||
*/
|
*/
|
||||||
public void setY( double y ) {
|
public void setY( double y ) {
|
||||||
this.y = y;
|
this.y = y;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gibt die Breite dieser Form zurück.
|
* Liefert die aktuelle Breite dieser Form.
|
||||||
* <p>
|
* <p>
|
||||||
* Die Breite einer Form ist immer die Breite ihrer Begrenzung,
|
* Die Breite einer Form ist immer die Breite ihrer Begrenzung,
|
||||||
* <strong>bevor</strong> Drehungen und andere Transformationen auf sie
|
* <strong>bevor</strong> Drehungen und andere Transformationen auf sie
|
||||||
* angewandt wurden.
|
* angewandt wurden.
|
||||||
* <p>
|
* <p>
|
||||||
* Die Begrenzungen der tatsächlich gezeichneten Form kann mit
|
* Die Begrenzungen der tatsächlich gezeichneten Form wird mit
|
||||||
* {@link #getBounds()} abgerufen werden.
|
* {@link #getBounds()} abgerufen.
|
||||||
*
|
*
|
||||||
* @return
|
* @return Die Breite der Form.
|
||||||
*/
|
*/
|
||||||
public abstract double getWidth();
|
public abstract double getWidth();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gibt die Höhe dieser Form zurück.
|
* Liefert die aktuelle Höhe dieser Form.
|
||||||
* <p>
|
* <p>
|
||||||
* Die Höhe einer Form ist immer die Höhe ihrer Begrenzung,
|
* Die Höhe einer Form ist immer die Höhe ihrer Begrenzung,
|
||||||
* <strong>bevor</strong> Drehungen und andere Transformationen auf sie
|
* <strong>bevor</strong> Drehungen und andere Transformationen auf sie
|
||||||
* angewandt wurden.
|
* angewandt wurden.
|
||||||
* <p>
|
* <p>
|
||||||
* Die Begrenzungen der tatsächlich gezeichneten Form kann mit
|
* Die Begrenzungen der tatsächlich gezeichneten Form wird mit
|
||||||
* {@link #getBounds()} abgerufen werden.
|
* {@link #getBounds()} abgerufen.
|
||||||
*
|
*
|
||||||
* @return
|
* @return Die Höhe der Form.
|
||||||
*/
|
*/
|
||||||
public abstract double getHeight();
|
public abstract double getHeight();
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setGradient( schule.ngb.zm.Color from, schule.ngb.zm.Color to, Options.Direction dir ) {
|
|
||||||
Point2D apDir = getAbsAnchorPoint(dir);
|
|
||||||
Point2D apInv = getAbsAnchorPoint(dir.inverse());
|
|
||||||
setGradient(apInv.getX(), apInv.getY(), from, apDir.getX(), apDir.getY(), to);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setGradient( schule.ngb.zm.Color from, schule.ngb.zm.Color to ) {
|
|
||||||
Point2D ap = getAbsAnchorPoint(CENTER);
|
|
||||||
setGradient(ap.getX(), ap.getY(), Math.min(ap.getX(), ap.getY()), from, to);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gibt die Rotation in Grad zurück.
|
* Liefert die Rotation in Grad.
|
||||||
*
|
*
|
||||||
* @return Rotation in Grad.
|
* @return Rotation in Grad.
|
||||||
*/
|
*/
|
||||||
@@ -176,14 +163,25 @@ public abstract class Shape extends BasicDrawable {
|
|||||||
this.rotation += angle % 360;
|
this.rotation += angle % 360;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void rotateTo( double angle ) {
|
/**
|
||||||
this.rotation = angle % 360;
|
* Dreht die Form um den angegebenen Winkel um das angegebene Drehzentrum.
|
||||||
}
|
*
|
||||||
|
* @param center Das Drehzentrum der Drehung.
|
||||||
|
* @param angle Der Drehwinkel.
|
||||||
|
*/
|
||||||
public void rotate( Point2D center, double angle ) {
|
public void rotate( Point2D center, double angle ) {
|
||||||
|
Validator.requireNotNull(center, "center");
|
||||||
rotate(center.getX(), center.getY(), angle);
|
rotate(center.getX(), center.getY(), angle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dreht die Form um den angegebenen Drehwinkel um die angegbenen
|
||||||
|
* Koordinaten als Drehzentrum.
|
||||||
|
*
|
||||||
|
* @param x x-Koordiante des Drehzentrums.
|
||||||
|
* @param y y-Koordiante des Drehzentrums.
|
||||||
|
* @param angle Drehwinkel in Grad.
|
||||||
|
*/
|
||||||
public void rotate( double x, double y, double angle ) {
|
public void rotate( double x, double y, double angle ) {
|
||||||
this.rotation += angle % 360;
|
this.rotation += angle % 360;
|
||||||
|
|
||||||
@@ -198,6 +196,15 @@ public abstract class Shape extends BasicDrawable {
|
|||||||
this.y = y2 + y;
|
this.y = y2 + y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt die Drehung der Form auf den angegebenen Winkel.
|
||||||
|
*
|
||||||
|
* @param angle Drehwinkel in Grad.
|
||||||
|
*/
|
||||||
|
public void rotateTo( double angle ) {
|
||||||
|
this.rotation = angle % 360;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gibt den aktuellen Skalierungsfaktor zurück.
|
* Gibt den aktuellen Skalierungsfaktor zurück.
|
||||||
*
|
*
|
||||||
@@ -207,34 +214,92 @@ public abstract class Shape extends BasicDrawable {
|
|||||||
return scale;
|
return scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt den Skalierungsfaktor auf den angegebenen Faktor.
|
||||||
|
* <p>
|
||||||
|
* Bei einem Faktor größer 0 wird die Form vergrößert, bei einem Faktor
|
||||||
|
* kleiner 0 verkleinert. Bei negativen Werten wird die Form entlang der x-
|
||||||
|
* bzw. y-Achse gespiegelt.
|
||||||
|
* <p>
|
||||||
|
* Das Seitenverhältnis wird immer beibehalten.
|
||||||
|
*
|
||||||
|
* @param factor Der Skalierungsfaktor.
|
||||||
|
*/
|
||||||
public void scale( double factor ) {
|
public void scale( double factor ) {
|
||||||
scale = factor;
|
scale = factor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skaliert die Form um den angegebenen Faktor.
|
||||||
|
* <p>
|
||||||
|
* Bei einem Faktor größer 0 wird die Form vergrößert, bei einem Faktor
|
||||||
|
* kleiner 0 verkleinert. Bei negativen Werten wird die Form entlang der x-
|
||||||
|
* bzw. y-Achse gespiegelt.
|
||||||
|
* <p>
|
||||||
|
* Die Skalierung wird zusätzlich zur aktuellen Skalierung angewandt. Wurde
|
||||||
|
* die Form zuvor um den Faktor 0.5 verkleinert und wird dann um 1.5
|
||||||
|
* vergrößert, dann ist die Form im Anschluss ein Drittel kleiner als zu
|
||||||
|
* Beginn ({@code 0.5 * 1.5 = 0.75}).
|
||||||
|
*
|
||||||
|
* @param factor Der Skalierungsfaktor.
|
||||||
|
*/
|
||||||
public void scaleBy( double factor ) {
|
public void scaleBy( double factor ) {
|
||||||
scale(scale * factor);
|
scale(scale * factor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Liefert den aktuellen Ankerpunkt der Form.
|
||||||
|
*
|
||||||
|
* @return Der Ankerpunkt.
|
||||||
|
*/
|
||||||
public Options.Direction getAnchor() {
|
public Options.Direction getAnchor() {
|
||||||
return anchor;
|
return anchor;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setzt den Ankerpunkt der Form basierend auf der angegebenen
|
* Setzt den Ankerpunkt der Form auf die angegebene Richtung.
|
||||||
* {@link Options.Direction Richtung}.
|
|
||||||
* <p>
|
* <p>
|
||||||
* Für das Setzen des Ankers muss das
|
* Jede Form hat einen Ankerpunkt, von dem aus sie gezeichnet wird. Jede
|
||||||
* {@link #getBounds() begrenzende Rechteck} berechnet werden. Unterklassen
|
* {@link schule.ngb.zm.Options.Direction Richtung} beschreibt einen der
|
||||||
* sollten die Methode überschreiben, wenn der Anker auch direkt gesetzt
|
* Neun Ankerpunkte:
|
||||||
* werden kann.
|
* <pre>
|
||||||
|
* NW────N────NE
|
||||||
|
* │ │
|
||||||
|
* │ │
|
||||||
|
* W C E
|
||||||
|
* │ │
|
||||||
|
* │ │
|
||||||
|
* SW────S────SE
|
||||||
|
* </pre>
|
||||||
|
* <p>
|
||||||
|
* Für den Ankerpunkt {@link #CENTER} wird die Form also ausgehend von den
|
||||||
|
* Koordinaten {@link #x} und {@link #y} um die Hälfte der Breite nach links
|
||||||
|
* und rechts, sowie um die Hälfte der Höhe nach oben und unten gezeichnet.
|
||||||
|
* Fpr den Ankerpunkt {@link #NORTHWEST} dagegen um die gesamte Breite nach
|
||||||
|
* rechts und die Höhe nach unten.
|
||||||
|
* <pre>
|
||||||
|
* setAnchor(CENTER) │ setAnchor(NORTHWEST)
|
||||||
|
* ┌───────────┐ │
|
||||||
|
* │ │ │
|
||||||
|
* │ │ │
|
||||||
|
* │ (x,y) │ │ (x,y)─────────┐
|
||||||
|
* │ │ │ │ │
|
||||||
|
* │ │ │ │ │
|
||||||
|
* └───────────┘ │ │ │
|
||||||
|
* │ │ │
|
||||||
|
* │ │ │
|
||||||
|
* │ └───────────┘
|
||||||
|
* </pre>
|
||||||
|
* <p>
|
||||||
|
* Der Ankerpunkt der Form bestimmt bei Transformationen auch die Position
|
||||||
|
* des Drehzentrums und anderer relativer Koordinaten bezüglich der Form.
|
||||||
*
|
*
|
||||||
* @param anchor
|
* @param anchor Der Ankerpunkt.
|
||||||
*/
|
*/
|
||||||
public void setAnchor( Options.Direction anchor ) {
|
public void setAnchor( Options.Direction anchor ) {
|
||||||
if( anchor != null ) {
|
Validator.requireNotNull(anchor, "anchor");
|
||||||
this.anchor = anchor;
|
this.anchor = anchor;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bestimmt die relativen Koordinaten des angegebenen Ankerpunkts basierend
|
* Bestimmt die relativen Koordinaten des angegebenen Ankerpunkts basierend
|
||||||
@@ -242,7 +307,19 @@ public abstract class Shape extends BasicDrawable {
|
|||||||
* <p>
|
* <p>
|
||||||
* Die Koordinaten des Ankerpunktes werden relativ zur oberen linken Ecke
|
* Die Koordinaten des Ankerpunktes werden relativ zur oberen linken Ecke
|
||||||
* des Rechtecks mit der Breite {@code width} und der Höhe {@code height}
|
* des Rechtecks mit der Breite {@code width} und der Höhe {@code height}
|
||||||
* bestimmt.
|
* bestimmt. Der Ankerpunkt {@link #NORTHWEST} hat daher immer das Ergebnis
|
||||||
|
* {@code (0,0)} und {@link #SOUTHEAST} {@code (width, height)}.
|
||||||
|
* <pre>
|
||||||
|
* (0,0)───(w/2,0)───(w,0)
|
||||||
|
* │ │
|
||||||
|
* │ │
|
||||||
|
* │ │
|
||||||
|
* (0,h/2) (w/2,h/2) (w,h/2)
|
||||||
|
* │ │
|
||||||
|
* │ │
|
||||||
|
* │ │
|
||||||
|
* (0,h)───(w/2,h)───(w,h)
|
||||||
|
* </pre>
|
||||||
*
|
*
|
||||||
* @param width Breite des umschließenden Rechtecks.
|
* @param width Breite des umschließenden Rechtecks.
|
||||||
* @param height Höhe des umschließenden Rechtecks.
|
* @param height Höhe des umschließenden Rechtecks.
|
||||||
@@ -259,11 +336,12 @@ public abstract class Shape extends BasicDrawable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bestimmt den Ankerpunkt der Form relativ zum gesetzten
|
* Bestimmt die Koordinaten des angegebenen Ankers der Form relativ zum
|
||||||
* {@link #setAnchor(Options.Direction) Ankerpunkt}.
|
* aktuellen {@link #setAnchor(Options.Direction) Ankerpunkt}.
|
||||||
*
|
*
|
||||||
* @param anchor Die Richtung des Ankerpunktes.
|
* @param anchor Die Richtung des Ankers.
|
||||||
* @return Der relative Ankerpunkt.
|
* @return Der relative Ankerpunkt.
|
||||||
|
* @see #getAnchorPoint(double, double, Options.Direction)
|
||||||
*/
|
*/
|
||||||
public Point2D.Double getAnchorPoint( Options.Direction anchor ) {
|
public Point2D.Double getAnchorPoint( Options.Direction anchor ) {
|
||||||
double wHalf = getWidth() * .5, hHalf = getHeight() * .5;
|
double wHalf = getWidth() * .5, hHalf = getHeight() * .5;
|
||||||
@@ -276,11 +354,20 @@ public abstract class Shape extends BasicDrawable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ermittelt die absoluten Koordinaten eines angegebenen
|
* Ermittelt die absoluten Koordinaten des angegebenen
|
||||||
* {@link #setAnchor(Options.Direction) Ankers}.
|
* {@link #setAnchor(Options.Direction) Ankers}.
|
||||||
|
* <p>
|
||||||
|
* Die absoluten Koordinaten werden bestimmt durch die Position der Form
|
||||||
|
* {@code (x,y)} plus die
|
||||||
|
* {@link #getAnchorPoint(Options.Direction) relativen Koordinaten} des
|
||||||
|
* Ankers.
|
||||||
*
|
*
|
||||||
* @param anchor Die Richtung des Ankerpunktes.
|
* <b>Wichtig:</b> Die Berechnung berücksichtigt derzeit keine Rotationen
|
||||||
|
* und Transformationen der Form.
|
||||||
|
*
|
||||||
|
* @param anchor Die Richtung des Ankers.
|
||||||
* @return Der absolute Ankerpunkt.
|
* @return Der absolute Ankerpunkt.
|
||||||
|
* @see #getAnchorPoint(double, double, Options.Direction)
|
||||||
*/
|
*/
|
||||||
public Point2D.Double getAbsAnchorPoint( Options.Direction anchor ) {
|
public Point2D.Double getAbsAnchorPoint( Options.Direction anchor ) {
|
||||||
// TODO: Die absoluten Anker müssten eigentlich die Rotation berücksichtigen.
|
// TODO: Die absoluten Anker müssten eigentlich die Rotation berücksichtigen.
|
||||||
@@ -293,20 +380,22 @@ public abstract class Shape extends BasicDrawable {
|
|||||||
/**
|
/**
|
||||||
* Kopiert die Eigenschaften der angegebenen Form in diese.
|
* Kopiert die Eigenschaften der angegebenen Form in diese.
|
||||||
* <p>
|
* <p>
|
||||||
* Unterklassen sollten diese Methode überschreiben, um weitere
|
* Unterklassen überschreiben diese Methode, um weitere Eigenschaften zu
|
||||||
* Eigenschaften zu kopieren (zum Beispiel den Radius eines Kreises).
|
* kopieren (zum Beispiel den Radius eines Kreises). Überschreibende
|
||||||
* Unterklassen sollten immer mit dem Aufruf {@code super.copyFrom(shape)}
|
* Methoden sollten immer mit dem Aufruf {@code super.copyFrom(shape)} die
|
||||||
* die Basiseigenschaften kopieren.
|
* Basiseigenschaften kopieren.
|
||||||
* <p>
|
* <p>
|
||||||
* Die Methode sollte so viele Eigenschaften wie möglich von der anderen
|
* Die Methode kopiert so viele Eigenschaften wie möglich von der
|
||||||
* Form in diese kopieren. Wenn die andere Form einen anderen Typ hat, dann
|
* angegebenen Form in diese. Wenn die andere Form einen anderen Typ hat,
|
||||||
* werden trotzdem die Basiseigenschaften (Konturlinie, Füllung, Position,
|
* dann werden trotzdem die Basiseigenschaften (Konturlinie, Füllung,
|
||||||
* Rotation, Skalierung, Sichtbarkeit und Ankerpunkt) in diese Form kopiert.
|
* Position, Rotation, Skalierung, Sichtbarkeit und Ankerpunkt) in diese
|
||||||
* Implementierende Unterklassen können soweit sinnvoll auch andere Werte
|
* Form kopiert. Soweit sinnvoll übernehmen implementierende Unterklassen
|
||||||
* übernehmen. Eine {@link Ellipse} kann beispielsweise auch die Breite und
|
* auch andere Werte. Eine {@link Ellipse} kopiert beispielsweise auch die
|
||||||
* Höhe eines {@link Rectangle} übernehmen.
|
* Breite und Höhe eines {@link Rectangle}.
|
||||||
|
* <p>
|
||||||
|
* Wird {@code null} übergeben, dann passiert nichts.
|
||||||
*
|
*
|
||||||
* @param shape Die Originalform, von der kopiert werden soll.
|
* @param shape Die Originalform, von der kopiert wird.
|
||||||
*/
|
*/
|
||||||
public void copyFrom( Shape shape ) {
|
public void copyFrom( Shape shape ) {
|
||||||
if( shape != null ) {
|
if( shape != null ) {
|
||||||
@@ -335,9 +424,9 @@ public abstract class Shape extends BasicDrawable {
|
|||||||
* </code></pre>
|
* </code></pre>
|
||||||
* <p>
|
* <p>
|
||||||
* Die Methode kann beliebig umgesetzt werden, um eine 1-zu-1-Kopie dieser
|
* Die Methode kann beliebig umgesetzt werden, um eine 1-zu-1-Kopie dieser
|
||||||
* Form zu erhalten. In der Regel sollte aber jede Form einen Konstruktor
|
* Form zu erhalten. In der Regel besitzt aber jede Form einen Konstruktor,
|
||||||
* besitzen, die alle Werte einer andern Form übernimmt. Die gezeigte
|
* der alle Werte einer andern Form übernimmt. Die gezeigte Implementierung
|
||||||
* Implementierung dürfte daher im Regelfall ausreichend sein.
|
* ist daher im Regelfall ausreichend.
|
||||||
*
|
*
|
||||||
* @return Eine genaue Kopie dieser Form.
|
* @return Eine genaue Kopie dieser Form.
|
||||||
*/
|
*/
|
||||||
@@ -348,8 +437,8 @@ public abstract class Shape extends BasicDrawable {
|
|||||||
* zurück. Intern werden die AWT Shapes benutzt, um sie auf den
|
* zurück. Intern werden die AWT Shapes benutzt, um sie auf den
|
||||||
* {@link Graphics2D Grafikkontext} zu zeichnen.
|
* {@link Graphics2D Grafikkontext} zu zeichnen.
|
||||||
* <p>
|
* <p>
|
||||||
* Wenn diese Form nicht durch eine AWT-Shape dargestellt wird, kann die
|
* Wenn diese Form nicht durch eine AWT-Shape dargestellt wird, liefert die
|
||||||
* Methode {@code null} zurückgeben.
|
* Methode {@code null}.
|
||||||
*
|
*
|
||||||
* @return Eine Java-AWT {@code Shape} die diese Form repräsentiert oder
|
* @return Eine Java-AWT {@code Shape} die diese Form repräsentiert oder
|
||||||
* {@code null}.
|
* {@code null}.
|
||||||
@@ -369,34 +458,85 @@ public abstract class Shape extends BasicDrawable {
|
|||||||
return new Bounds(this);
|
return new Bounds(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verschiebt die Form um die angegebenen Werte entlang der
|
||||||
|
* Koordinatenachsen.
|
||||||
|
*
|
||||||
|
* @param dx Verschiebung entlang der x-Achse.
|
||||||
|
* @param dy Verschiebung entlang der y-Achse.
|
||||||
|
*/
|
||||||
public void move( double dx, double dy ) {
|
public void move( double dx, double dy ) {
|
||||||
x += dx;
|
x += dx;
|
||||||
y += dy;
|
y += dy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bewegt die Form an die angegebenen Koordinaten.
|
||||||
|
*
|
||||||
|
* @param x Die neue x-Koordinate.
|
||||||
|
* @param y Die neue y-Koordinate.
|
||||||
|
*/
|
||||||
public void moveTo( double x, double y ) {
|
public void moveTo( double x, double y ) {
|
||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
this.y = y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bewegt die Form an dieselben Koordinaten wie die angegebene Form.
|
||||||
|
*
|
||||||
|
* @param shape Eine andere Form.
|
||||||
|
*/
|
||||||
public void moveTo( Shape shape ) {
|
public void moveTo( Shape shape ) {
|
||||||
moveTo(shape.getX(), shape.getY());
|
moveTo(shape.getX(), shape.getY());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bewegt die Form zum angegebenen Ankerpunkt der angegebenen Form.
|
||||||
|
*
|
||||||
|
* @param shape Die andere Form.
|
||||||
|
* @param dir Die Richtung des Ankerpunktes.
|
||||||
|
* @see #moveTo(Shape, Options.Direction, double)
|
||||||
|
*/
|
||||||
public void moveTo( Shape shape, Options.Direction dir ) {
|
public void moveTo( Shape shape, Options.Direction dir ) {
|
||||||
moveTo(shape, dir, 0.0);
|
moveTo(shape, dir, 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bewegt den Ankerpunkt dieser Form zu einem Ankerpunkt einer anderen Form.
|
* Bewegt den Ankerpunkt dieser Form zu einem Ankerpunkt einer anderen
|
||||||
* Mit {@code buff} kann ein zusätzlicher Abstand angegeben werden, um den
|
* Form.
|
||||||
* die Form entlang des Ankerpunktes {@code anchor} verschoben werden soll.
|
* <p>
|
||||||
|
* Mit {@code buff} wird ein zusätzlicher Abstand angegeben, um den die Form
|
||||||
|
* entlang des Ankerpunktes {@code anchor} verschoben wird.
|
||||||
|
* <p>
|
||||||
* Ist der Anker zum Beispiel {@code NORTH}, dann wird die Form um
|
* Ist der Anker zum Beispiel {@code NORTH}, dann wird die Form um
|
||||||
* {@code buff} nach oben verschoben.
|
* {@code buff} oberhalb der oberen Kante der zweiten Form verschoben.
|
||||||
|
* <p>
|
||||||
|
* Befinden sich die Formen zuvor in folgender Ausrichtung:
|
||||||
|
* <pre>
|
||||||
|
* ┌─────────┐
|
||||||
|
* │ │
|
||||||
|
* W B │
|
||||||
|
* ┌─────┐ │ │
|
||||||
|
* │ │ └─────────┘
|
||||||
|
* W A │
|
||||||
|
* │ │
|
||||||
|
* └─────┘
|
||||||
|
* </pre>
|
||||||
|
* <p>
|
||||||
|
* bringt sie der Aufruf {@code B.moveTo(A, DOWN, 0)} in diese Ausrichtung:
|
||||||
|
* <pre>
|
||||||
|
* B.moveTo(A, WEST, 0) │ B.moveTo(A, WEST, 10)
|
||||||
|
* │
|
||||||
|
* ┌─────┬───┐ │ ┌┬────┬────┐
|
||||||
|
* │ │ │ │ ││ │ │
|
||||||
|
* │ A B│ │ │ ││ A B │
|
||||||
|
* │ │ │ │ ││ │ │
|
||||||
|
* └─────┴───┘ │ └┴────┴────┘
|
||||||
|
* </pre>
|
||||||
*
|
*
|
||||||
* @param shape
|
* @param shape Die andere Form.
|
||||||
* @param dir
|
* @param dir Die Richtung des Ankerpunktes.
|
||||||
* @param buff
|
* @param buff Der Abstand zum angegebenen Ankerpunkt.
|
||||||
*/
|
*/
|
||||||
public void moveTo( Shape shape, Options.Direction dir, double buff ) {
|
public void moveTo( Shape shape, Options.Direction dir, double buff ) {
|
||||||
Point2D ap = shape.getAbsAnchorPoint(dir);
|
Point2D ap = shape.getAbsAnchorPoint(dir);
|
||||||
@@ -406,15 +546,22 @@ public abstract class Shape extends BasicDrawable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Richtet die Form entlang der angegebenen Richtung am Rand der
|
* Bewegt die Form an den Rand der Zeichenfläche in der angegebenen
|
||||||
* Zeichenfläche aus.
|
* Richtung.
|
||||||
*
|
*
|
||||||
* @param dir Die Richtung der Ausrichtung.
|
* @param dir Die Richtung.
|
||||||
*/
|
*/
|
||||||
public void alignTo( Options.Direction dir ) {
|
public void alignTo( Options.Direction dir ) {
|
||||||
alignTo(dir, 0.0);
|
alignTo(dir, 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bewegt die Form mit dem angegebenen Abstand an den Rand der Zeichenfläche
|
||||||
|
* in der angegebenen Richtung aus.
|
||||||
|
*
|
||||||
|
* @param dir Die Richtung.
|
||||||
|
* @param buff Der Abstand zum Rand.
|
||||||
|
*/
|
||||||
public void alignTo( Options.Direction dir, double buff ) {
|
public void alignTo( Options.Direction dir, double buff ) {
|
||||||
Point2D anchorShape = Shape.getAnchorPoint(canvasWidth, canvasHeight, dir);
|
Point2D anchorShape = Shape.getAnchorPoint(canvasWidth, canvasHeight, dir);
|
||||||
Point2D anchorThis = this.getAbsAnchorPoint(dir);
|
Point2D anchorThis = this.getAbsAnchorPoint(dir);
|
||||||
@@ -423,20 +570,58 @@ public abstract class Shape extends BasicDrawable {
|
|||||||
this.y += Math.abs(dir.y) * (anchorShape.getY() - anchorThis.getY()) + dir.y * buff;
|
this.y += Math.abs(dir.y) * (anchorShape.getY() - anchorThis.getY()) + dir.y * buff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bewegt den Ankerpunkt dieser Form in der angegebenen Richtung zum
|
||||||
|
* Gleichen Ankerpunkt der anderen Form.
|
||||||
|
*
|
||||||
|
* @param shape Die andere Form.
|
||||||
|
* @param dir Die Richtung des Ankerpunktes.
|
||||||
|
* @see #alignTo(Shape, Options.Direction, double)
|
||||||
|
*/
|
||||||
public void alignTo( Shape shape, Options.Direction dir ) {
|
public void alignTo( Shape shape, Options.Direction dir ) {
|
||||||
alignTo(shape, dir, 0.0);
|
alignTo(shape, dir, 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Richtet die Form entlang der angegebenen Richtung an einer anderen Form
|
* Richtet die Form entlang der angegebenen Richtung an einer anderen Form
|
||||||
* aus. Für {@code DOWN} wird beispielsweise die y-Koordinate der unteren
|
* aus.
|
||||||
* Kante dieser Form an der unteren Kante der angegebenen Form {@code shape}
|
* <p>
|
||||||
* ausgerichtet. Die x-Koordinate wird nicht verändert. {@code buff} gibt
|
* {@code buff} gibt einen Abstand ab, um den diese From versetzt
|
||||||
* einen Abstand ab, um den diese From versetzt ausgerichtet werden soll.
|
* ausgerichtet wird.
|
||||||
|
* <p>
|
||||||
|
* Für {@link #DOWN} wird beispielsweise die y-Koordinate der unteren Kante
|
||||||
|
* dieser Form an der unteren Kante von {@code shape} ausgerichtet. Die
|
||||||
|
* x-Koordinate wird in dem Fall nicht verändert.
|
||||||
|
* <p>
|
||||||
|
* Befinden sich die Formen beispielsweise in folgender Position:
|
||||||
|
* <pre>
|
||||||
|
* ┌─────┐
|
||||||
|
* │ │
|
||||||
|
* │ B │
|
||||||
|
* ┌─────┐ │ │
|
||||||
|
* │ │ └──D──┘
|
||||||
|
* │ A │
|
||||||
|
* │ │
|
||||||
|
* └──D──┘
|
||||||
|
* </pre>
|
||||||
|
* <p>
|
||||||
|
* <p>
|
||||||
|
* werden sie durch {@code alignTo} so positioniert:
|
||||||
|
* <pre>
|
||||||
|
* B.alignTo(A, EAST, 0) │ B.alignTo(A, EAST, 10)
|
||||||
|
* │
|
||||||
|
* ┌─────┐ ┌─────┐ │ ┌─────┐
|
||||||
|
* │ │ │ │ │ │ │ ┌─────┐
|
||||||
|
* │ A │ │ B │ │ │ A │ │ │
|
||||||
|
* │ │ │ │ │ │ │ │ B │
|
||||||
|
* └──D──┘ └──D──┘ │ └──D──┘ │ │
|
||||||
|
* │ └──D──┘
|
||||||
|
* │
|
||||||
|
* </pre>
|
||||||
*
|
*
|
||||||
* @param shape
|
* @param shape Die andere Form.
|
||||||
* @param dir
|
* @param dir Die Richtung.
|
||||||
* @param buff
|
* @param buff Der Abstand.
|
||||||
*/
|
*/
|
||||||
public void alignTo( Shape shape, Options.Direction dir, double buff ) {
|
public void alignTo( Shape shape, Options.Direction dir, double buff ) {
|
||||||
Point2D anchorShape = shape.getAbsAnchorPoint(dir);
|
Point2D anchorShape = shape.getAbsAnchorPoint(dir);
|
||||||
@@ -446,16 +631,46 @@ public abstract class Shape extends BasicDrawable {
|
|||||||
this.y += Math.abs(dir.y) * (anchorShape.getY() - anchorThis.getY()) + dir.y * buff;
|
this.y += Math.abs(dir.y) * (anchorShape.getY() - anchorThis.getY()) + dir.y * buff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param shape
|
||||||
|
* @param dir
|
||||||
|
* @see #nextTo(Shape, Options.Direction, double)
|
||||||
|
*/
|
||||||
public void nextTo( Shape shape, Options.Direction dir ) {
|
public void nextTo( Shape shape, Options.Direction dir ) {
|
||||||
nextTo(shape, dir, DEFAULT_BUFFER);
|
nextTo(shape, dir, DEFAULT_BUFFER);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bewegt die Form neben eine andere in Richtung des angegebenen
|
* Bewegt die Form neben eine andere in Richtung des angegebenen
|
||||||
* Ankerpunktes. Im Gegensatz zu
|
* Ankerpunktes.
|
||||||
* {@link #moveTo(Shape, Options.Direction, double)} wird die Breite bzw.
|
* <p>
|
||||||
* Höhe der Formen berücksichtigt und die Formen so platziert, dass keine
|
* Im Gegensatz zu {@link #moveTo(Shape, Options.Direction, double)} wird
|
||||||
* Überlappungen vorhanden sind.
|
* die Breite bzw. Höhe der Formen berücksichtigt und die Formen so
|
||||||
|
* platziert, dass keine Überlappungen vorhanden sind.
|
||||||
|
* <p>
|
||||||
|
* Befinden sich die Formen zuvor in folgender Ausrichtung:
|
||||||
|
* <pre>
|
||||||
|
* ┌─────┐
|
||||||
|
* │ │
|
||||||
|
* W B │
|
||||||
|
* ┌──────┐ │ │
|
||||||
|
* │ │ └─────┘
|
||||||
|
* │ A E
|
||||||
|
* │ │
|
||||||
|
* └──────┘
|
||||||
|
* </pre>
|
||||||
|
* <p>
|
||||||
|
* bringt sie der Aufruf {@code B.nextTo(A, EAST, 0)} in diese Ausrichtung:
|
||||||
|
* <pre>
|
||||||
|
* B.nextTo(A, EAST, 0) │ B.nextTo(A, EAST, 10)
|
||||||
|
* │
|
||||||
|
* ┌─────┬─────┐ │ ┌─────┐ ┌─────┐
|
||||||
|
* │ │ │ │ │ │ │ │
|
||||||
|
* │ A │ B │ │ │ A │ │ B │
|
||||||
|
* │ │ │ │ │ │ │ │
|
||||||
|
* └─────┴─────┘ │ └─────┘ └─────┘
|
||||||
|
* │
|
||||||
|
* </pre>
|
||||||
*
|
*
|
||||||
* @param shape
|
* @param shape
|
||||||
* @param dir
|
* @param dir
|
||||||
@@ -469,6 +684,19 @@ public abstract class Shape extends BasicDrawable {
|
|||||||
this.y += (anchorShape.getY() - anchorThis.getY()) + dir.y * buff;
|
this.y += (anchorShape.getY() - anchorThis.getY()) + dir.y * buff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setGradient( schule.ngb.zm.Color from, schule.ngb.zm.Color to, Options.Direction dir ) {
|
||||||
|
Point2D apDir = getAbsAnchorPoint(dir);
|
||||||
|
Point2D apInv = getAbsAnchorPoint(dir.inverse());
|
||||||
|
setGradient(apInv.getX(), apInv.getY(), from, apDir.getX(), apDir.getY(), to);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setGradient( schule.ngb.zm.Color from, schule.ngb.zm.Color to ) {
|
||||||
|
Point2D ap = getAbsAnchorPoint(CENTER);
|
||||||
|
setGradient(ap.getX(), ap.getY(), Math.min(ap.getX(), ap.getY()), from, to);
|
||||||
|
}
|
||||||
|
|
||||||
/*public void shear( double dx, double dy ) {
|
/*public void shear( double dx, double dy ) {
|
||||||
verzerrung.shear(dx, dy);
|
verzerrung.shear(dx, dy);
|
||||||
}*/
|
}*/
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
/**
|
||||||
|
* Diese Paket enthält Formen, die Diagramme darstellen.
|
||||||
|
*/
|
||||||
|
package schule.ngb.zm.shapes.charts;
|
||||||
13
src/main/java/schule/ngb/zm/shapes/package-info.java
Normal file
13
src/main/java/schule/ngb/zm/shapes/package-info.java
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* Dieses Paket enthält Implementationen der abstrakten
|
||||||
|
* {@link schule.ngb.zm.shapes.Shape} Klasse.
|
||||||
|
*
|
||||||
|
* Jede Unterklasse von {@code Shape} stellt eine konkrete Form wie ein
|
||||||
|
* {@link schule.ngb.zm.shapes.Rectangle Rechteck}, ein
|
||||||
|
* {@link schule.ngb.zm.shapes.Circle Kreis} oder ein
|
||||||
|
* {@link schule.ngb.zm.shapes.Picture Bild} dar.
|
||||||
|
*
|
||||||
|
* Mit {@link schule.ngb.zm.shapes.ShapeGroup} können Formen gruppiert
|
||||||
|
* und gemeinsam transformiert werden.
|
||||||
|
*/
|
||||||
|
package schule.ngb.zm.shapes;
|
||||||
@@ -19,7 +19,7 @@ import java.time.LocalDateTime;
|
|||||||
import java.time.ZoneOffset;
|
import java.time.ZoneOffset;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Function;
|
import java.util.function.*;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@@ -28,21 +28,23 @@ import static java.lang.Math.log;
|
|||||||
import static schule.ngb.zm.Constants.random;
|
import static schule.ngb.zm.Constants.random;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hilfsklasse, um zufällige Beispieldaten zu erzeugen.
|
* Eine Helferklasse, um zufällige Beispieldaten zu erzeugen.
|
||||||
* <p>
|
* <p>
|
||||||
* Die Klasse kann verschiedene Arten realistischer Beispieldaten erzeugen,
|
* Die Klasse kann verschiedene Arten realistischer Beispieldaten erzeugen.
|
||||||
* unter anderem Namen, E-Mail-Adressen, Passwörter oder Platzhalter-Bilder.
|
* Unter anderem Namen, E-Mail-Adressen, Passwörter oder Platzhalter-Bilder.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings( "unused" )
|
@SuppressWarnings( "unused" )
|
||||||
public final class Faker {
|
public final class Faker {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* URL, von der extern generierte Fake-Bilder geladen werden können.
|
||||||
|
* <p>
|
||||||
|
* Die URL wird als Format-String definiert mit zwei {@code %d}
|
||||||
|
* Platzhaltern, die durch die Breite und Höhe des gewünschten Bildes
|
||||||
|
* ersetzt werden.
|
||||||
|
*/
|
||||||
public static final String FAKE_IMG_URL = "https://loremflickr.com/%d/%d";
|
public static final String FAKE_IMG_URL = "https://loremflickr.com/%d/%d";
|
||||||
|
|
||||||
public static void main( String[] args ) {
|
|
||||||
String text = Faker.fakeText(2000, 8);
|
|
||||||
System.out.println(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger Benutzerdaten.
|
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger Benutzerdaten.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -76,8 +78,8 @@ public final class Faker {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger Namen (Vor- und
|
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger Namen im Format
|
||||||
* Nachname).
|
* "Vorname Nachname".
|
||||||
*
|
*
|
||||||
* @param count Anzahl der Beispieldaten.
|
* @param count Anzahl der Beispieldaten.
|
||||||
* @return Ein Array mit den Beispieldaten.
|
* @return Ein Array mit den Beispieldaten.
|
||||||
@@ -133,7 +135,7 @@ public final class Faker {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger
|
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger
|
||||||
* {@code Date}-Objekte.
|
* {@code LocalDate}-Objekte, die ein Datum ohne Uhrzeit beschreiben.
|
||||||
*
|
*
|
||||||
* @param count Anzahl der Beispieldaten.
|
* @param count Anzahl der Beispieldaten.
|
||||||
* @return Ein Array mit den Beispieldaten.
|
* @return Ein Array mit den Beispieldaten.
|
||||||
@@ -151,6 +153,14 @@ public final class Faker {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger
|
||||||
|
* {@code LocalDateTime}-Objekte, die einen Zeitpunkt mit Dateum und Uhrzeit
|
||||||
|
* beschreiben,
|
||||||
|
*
|
||||||
|
* @param count Anzahl der Beispieldaten.
|
||||||
|
* @return Ein Array mit den Beispieldaten.
|
||||||
|
*/
|
||||||
public static LocalDateTime[] fakeDatetimes( int count ) {
|
public static LocalDateTime[] fakeDatetimes( int count ) {
|
||||||
long nowEpoch = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC);
|
long nowEpoch = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC);
|
||||||
long from = LocalDateTime.ofEpochSecond(nowEpoch - 18 * 365, 0, ZoneOffset.UTC).toEpochSecond(ZoneOffset.UTC);
|
long from = LocalDateTime.ofEpochSecond(nowEpoch - 18 * 365, 0, ZoneOffset.UTC).toEpochSecond(ZoneOffset.UTC);
|
||||||
@@ -164,6 +174,17 @@ public final class Faker {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erzeugt einen Blindtext mit der angegebenen Anzahl Worten, aufgeteilt in
|
||||||
|
* die angegebene Anzahl Absätze.
|
||||||
|
* <p>
|
||||||
|
* Abssätze werden duch einen doppelten Zeilenumbruch {@code \n\n}
|
||||||
|
* getrennt.
|
||||||
|
*
|
||||||
|
* @param words Anzahl Wörter im Text insgesamt.
|
||||||
|
* @param paragraphs Anzahl Absätze.
|
||||||
|
* @return Ein zufälliger Blindtext.
|
||||||
|
*/
|
||||||
public static String fakeText( int words, int paragraphs ) {
|
public static String fakeText( int words, int paragraphs ) {
|
||||||
String basetext = "";
|
String basetext = "";
|
||||||
try(
|
try(
|
||||||
@@ -197,6 +218,16 @@ public final class Faker {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erzeugt ein Array mit der angegebenen Anzahl zufällig erzeugter Integer
|
||||||
|
* im angegebenen Bereich.
|
||||||
|
*
|
||||||
|
* @param count Anzahl der Zahlen im Array.
|
||||||
|
* @param min Untere Grenze der Zufallszahlen.
|
||||||
|
* @param max Obere Grenze der Zufallszahlen.
|
||||||
|
* @return Ein Array mit Zufallszahlen.
|
||||||
|
* @see Constants#random(int, int)
|
||||||
|
*/
|
||||||
public static int[] fakeIntArray( int count, int min, int max ) {
|
public static int[] fakeIntArray( int count, int min, int max ) {
|
||||||
int[] arr = new int[count];
|
int[] arr = new int[count];
|
||||||
for( int i = 0; i < count; i++ ) {
|
for( int i = 0; i < count; i++ ) {
|
||||||
@@ -205,14 +236,56 @@ public final class Faker {
|
|||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erzeugt eine Liste mit der angegebenen Anzahl zufällig erzeugter Integer
|
||||||
|
* im angegebenen Bereich.
|
||||||
|
* <p>
|
||||||
|
* Ist {@code list} ein Listenobjekt, werden dei Zahlen an diese angehängt.
|
||||||
|
* Wird {@code null} übergeben, wird eine neue {@link ArrayList} erzeugt.
|
||||||
|
*
|
||||||
|
* @param count Anzahl der erzeugten Zahlen.
|
||||||
|
* @param min Untere Grenze der Zufallszahlen.
|
||||||
|
* @param max Obere Grenze der Zufallszahlen.
|
||||||
|
* @param list Eine Liste, die befüllt werden soll, oder {@code null}.
|
||||||
|
* @return Ein Array mit Zufallszahlen.
|
||||||
|
* @see Constants#random(int, int)
|
||||||
|
*/
|
||||||
public static List<Integer> fakeIntegerList( int count, int min, int max, List<Integer> list ) {
|
public static List<Integer> fakeIntegerList( int count, int min, int max, List<Integer> list ) {
|
||||||
if( list == null ) {
|
List<Integer> result = (list == null) ? new ArrayList<>(count) : list;
|
||||||
list = new ArrayList<>(count);
|
fakeIntegers(count, min, max, result::add);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erzeugt die angegebene Anzahl Zufallszahlen im angegebenen Bereich und
|
||||||
|
* übergibt sie an den angegebenen {@code Consumer}.
|
||||||
|
*
|
||||||
|
* Ein typischer Aufruf, um eine {@code #LinkedList} mit 100 Zufallszahlen
|
||||||
|
* zu erzeugen könnte so aussehen:
|
||||||
|
* <pre><code>
|
||||||
|
* List<Integer> l = new LinkedList<>();
|
||||||
|
* Faker.fakeIntegers(100, 0, 100, l::add);
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* @param count Anzahl der erzeugten Zahlen.
|
||||||
|
* @param min Untere Grenze der Zufallszahlen.
|
||||||
|
* @param max Obere Grenze der Zufallszahlen.
|
||||||
|
* @param con {@code Consumer} für die Zahlen.
|
||||||
|
*/
|
||||||
|
public static void fakeIntegers( int count, int min, int max, Consumer<Integer> con ) {
|
||||||
for( int i = 0; i < count; i++ ) {
|
for( int i = 0; i < count; i++ ) {
|
||||||
list.add(random(min, max));
|
con.accept(random(min, max));
|
||||||
}
|
}
|
||||||
return list;
|
}
|
||||||
|
|
||||||
|
public static <L> L fakeIntegers( int count, int min, int max, Supplier<L> sup, BiConsumer<L, Integer> con ) {
|
||||||
|
L result = sup.get();
|
||||||
|
|
||||||
|
for( int i = 0; i < count; i++ ) {
|
||||||
|
con.accept(result, random(min, max));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings( "unchecked" )
|
@SuppressWarnings( "unchecked" )
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ public final class Log {
|
|||||||
* mindestens herabgesenkt werden sollen.
|
* mindestens herabgesenkt werden sollen.
|
||||||
*/
|
*/
|
||||||
public static void enableGlobalLevel( Level level ) {
|
public static void enableGlobalLevel( Level level ) {
|
||||||
int lvl = Validator.requireNotNull(level).intValue();
|
int lvl = Validator.requireNotNull(level, "level").intValue();
|
||||||
ensureRootLoggerInitialized();
|
ensureRootLoggerInitialized();
|
||||||
|
|
||||||
// Decrease level of root level ConsoleHandlers for output
|
// Decrease level of root level ConsoleHandlers for output
|
||||||
|
|||||||
@@ -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 nut be null." : msg.get());
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final String requireNotEmpty( String str ) {
|
public static final String requireNotEmpty( String str, CharSequence paramName ) {
|
||||||
return requireNotEmpty(str, (Supplier<String>) null);
|
return requireNotEmpty(str, () -> String.format("Parameter <%s> may not be empty string (<%s> provided)", paramName, str));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final String requireNotEmpty( String str, CharSequence msg ) {
|
public static final String requireNotEmpty( String str, CharSequence paramName, CharSequence msg ) {
|
||||||
return requireNotEmpty(str, msg::toString);
|
return requireNotEmpty(str, () -> msg.toString().format(paramName.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final String requireNotEmpty( String str, Supplier<String> msg ) {
|
public static final String requireNotEmpty( String str, Supplier<String> msg ) {
|
||||||
if( str.isEmpty() )
|
if( str.isEmpty() ) {
|
||||||
throw new IllegalArgumentException(msg == null ? String.format("Parameter may not be empty string (<%s> provided)", str) : msg.get());
|
throw new IllegalArgumentException(msg == null ? String.format("Parameter may not be empty string (<%s> provided)", str) : msg.get());
|
||||||
|
}
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final <T> T[] requireNotEmpty( T[] arr, CharSequence paramName ) {
|
||||||
|
return requireNotEmpty(arr, () -> String.format("Parameter <%s> may not be empty", paramName));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final <T> T[] requireNotEmpty( T[] arr, CharSequence paramName, CharSequence msg ) {
|
||||||
|
return requireNotEmpty(arr, () -> msg.toString().format(paramName.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final <T> T[] requireNotEmpty( T[] arr, Supplier<String> msg ) {
|
||||||
|
if( arr.length == 0 )
|
||||||
|
throw new IllegalArgumentException(msg == null ? "Parameter array may not be empty" : msg.get());
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static final int requirePositive( int i ) {
|
public static final int requirePositive( int i ) {
|
||||||
return requirePositive(i, (Supplier<String>) null);
|
return requirePositive(i, (Supplier<String>) null);
|
||||||
}
|
}
|
||||||
@@ -139,21 +159,6 @@ public class Validator {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
public static final <T> T[] requireNotEmpty( T[] arr ) {
|
|
||||||
return requireNotEmpty(arr, (Supplier<String>) null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final <T> T[] requireNotEmpty( T[] arr, CharSequence msg ) {
|
|
||||||
return requireNotEmpty(arr, msg::toString);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final <T> T[] requireNotEmpty( T[] arr, Supplier<String> msg ) {
|
|
||||||
if( arr.length == 0 )
|
|
||||||
throw new IllegalArgumentException(msg == null ? "Parameter array may not be empty" : msg.get());
|
|
||||||
return arr;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final <T> T[] requireSize( T[] arr, int size ) {
|
public static final <T> T[] requireSize( T[] arr, int size ) {
|
||||||
return requireSize(arr, size, (Supplier<String>) null);
|
return requireSize(arr, size, (Supplier<String>) null);
|
||||||
}
|
}
|
||||||
@@ -168,20 +173,6 @@ public class Validator {
|
|||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final <T> T[] requireValid( T[] arr ) {
|
|
||||||
return requireValid(arr, (Supplier<String>) null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final <T> T[] requireValid( T[] arr, CharSequence msg ) {
|
|
||||||
return requireValid(arr, msg::toString);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final <T> T[] requireValid( T[] arr, Supplier<String> msg ) {
|
|
||||||
if( arr == null || arr.length > 0 )
|
|
||||||
throw new IllegalArgumentException(msg == null ? "Parameter array may not be null or empty" : msg.get());
|
|
||||||
return arr;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Validator() {
|
private Validator() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,9 @@ import java.util.function.BiConsumer;
|
|||||||
* dispatcher.dispatchEvent("stop", new MyEvent());
|
* dispatcher.dispatchEvent("stop", new MyEvent());
|
||||||
* }
|
* }
|
||||||
* </code></pre>
|
* </code></pre>
|
||||||
|
* <p>
|
||||||
|
* Siehe {@link schule.ngb.zm.media.AudioListener} und
|
||||||
|
* {@link schule.ngb.zm.media.Music} für ein Beispiel der Verwendung.
|
||||||
*
|
*
|
||||||
* @param <E> Typ der Event-Objekte.
|
* @param <E> Typ der Event-Objekte.
|
||||||
* @param <L> Typ der verwendeten Listener-Schnittstelle.
|
* @param <L> Typ der verwendeten Listener-Schnittstelle.
|
||||||
@@ -73,8 +76,8 @@ public class EventDispatcher<E, L extends Listener<E>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void registerEventType( String eventKey, BiConsumer<E, L> dispatcher ) {
|
public void registerEventType( String eventKey, BiConsumer<E, L> dispatcher ) {
|
||||||
Validator.requireNotNull(eventKey);
|
Validator.requireNotNull(eventKey, "eventKey");
|
||||||
Validator.requireNotNull(dispatcher);
|
Validator.requireNotNull(dispatcher, "dispatcher");
|
||||||
|
|
||||||
if( !eventRegistered(eventKey) ) {
|
if( !eventRegistered(eventKey) ) {
|
||||||
eventRegistry.put(eventKey, dispatcher);
|
eventRegistry.put(eventKey, dispatcher);
|
||||||
@@ -89,7 +92,7 @@ public class EventDispatcher<E, L extends Listener<E>> {
|
|||||||
listeners.remove(listener);
|
listeners.remove(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings( "unused" )
|
||||||
public boolean hasListeners() {
|
public boolean hasListeners() {
|
||||||
return !listeners.isEmpty();
|
return !listeners.isEmpty();
|
||||||
}
|
}
|
||||||
@@ -99,8 +102,8 @@ public class EventDispatcher<E, L extends Listener<E>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void dispatchEvent( String eventKey, final E event ) {
|
public void dispatchEvent( String eventKey, final E event ) {
|
||||||
Validator.requireNotNull(eventKey);
|
Validator.requireNotNull(eventKey, "eventKey");
|
||||||
Validator.requireNotNull(event);
|
Validator.requireNotNull(event, "event");
|
||||||
|
|
||||||
if( eventRegistered(eventKey) ) {
|
if( eventRegistered(eventKey) ) {
|
||||||
final BiConsumer<E, L> dispatcher = eventRegistry.get(eventKey);
|
final BiConsumer<E, L> dispatcher = eventRegistry.get(eventKey);
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
/**
|
||||||
|
* Dieses Paket enthält Helferklassen, die das Listener-Entwurfsmuster
|
||||||
|
* umsetzen.
|
||||||
|
*/
|
||||||
|
package schule.ngb.zm.util.events;
|
||||||
@@ -5,14 +5,21 @@ 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.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Eine Helferklasse 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 Map<String, Font> fontCache = new ConcurrentHashMap<>();
|
||||||
@@ -22,11 +29,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
|
||||||
@@ -53,7 +61,7 @@ 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, "Font source may not be null");
|
||||||
Validator.requireNotEmpty(source, "Font source may not be empty.");
|
Validator.requireNotEmpty(source, "Font source may not be empty.");
|
||||||
|
|
||||||
if( fontCache.containsKey(name) ) {
|
if( fontCache.containsKey(name) ) {
|
||||||
@@ -96,11 +104,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 +122,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 ) {
|
||||||
|
|||||||
@@ -18,6 +18,13 @@ import java.lang.ref.SoftReference;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Eine Helferklasse 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;
|
||||||
|
|||||||
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 Helferklassen, 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 Helferklassen 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 Helferklassen zur Verarbeitung paralleler Aufgaben
|
||||||
|
* innerhalb der Zeichenmaschine.
|
||||||
|
*/
|
||||||
|
package schule.ngb.zm.util.tasks;
|
||||||
@@ -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 {
|
||||||
|
|
||||||
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -11,14 +11,14 @@ class ValidatorTest {
|
|||||||
StringBuilder sb = new StringBuilder("Message");
|
StringBuilder sb = new StringBuilder("Message");
|
||||||
|
|
||||||
Object o1 = new Object();
|
Object o1 = new Object();
|
||||||
assertEquals(o1, Validator.requireNotNull(o1));
|
assertEquals(o1, Validator.requireNotNull(o1, "content"));
|
||||||
assertEquals(o1, Validator.requireNotNull(o1, "Message"));
|
assertEquals(o1, Validator.requireNotNull(o1, "content", "Message"));
|
||||||
assertEquals(o1, Validator.requireNotNull(o1, sb));
|
assertEquals(o1, Validator.requireNotNull(o1, "content", sb));
|
||||||
assertEquals(o1, Validator.requireNotNull(o1, ()->"Message"));
|
assertEquals(o1, Validator.requireNotNull(o1, ()->"Message"));
|
||||||
|
|
||||||
String o2 = null;
|
String o2 = null;
|
||||||
assertThrowsExactly(NullPointerException.class, () -> Validator.requireNotNull(o2));
|
assertThrowsExactly(NullPointerException.class, () -> Validator.requireNotNull(o2, "content"));
|
||||||
assertThrowsExactly(NullPointerException.class, () -> Validator.requireNotNull(o2, "Message"));
|
assertThrowsExactly(NullPointerException.class, () -> Validator.requireNotNull(o2, "content", "Message"));
|
||||||
assertThrowsExactly(NullPointerException.class, () -> Validator.requireNotNull(o2, ()->"Message"));
|
assertThrowsExactly(NullPointerException.class, () -> Validator.requireNotNull(o2, ()->"Message"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,14 +27,14 @@ class ValidatorTest {
|
|||||||
StringBuilder sb = new StringBuilder("Message");
|
StringBuilder sb = new StringBuilder("Message");
|
||||||
|
|
||||||
String s1 = "Content";
|
String s1 = "Content";
|
||||||
assertEquals(s1, Validator.requireNotEmpty(s1));
|
assertEquals(s1, Validator.requireNotEmpty(s1, "content"));
|
||||||
assertEquals(s1, Validator.requireNotEmpty(s1, "Message"));
|
assertEquals(s1, Validator.requireNotEmpty(s1, "content", "Message"));
|
||||||
assertEquals(s1, Validator.requireNotEmpty(s1, sb));
|
assertEquals(s1, Validator.requireNotEmpty(s1, "content", sb));
|
||||||
assertEquals(s1, Validator.requireNotEmpty(s1, ()->"Message"));
|
assertEquals(s1, Validator.requireNotEmpty(s1, ()->"Message"));
|
||||||
|
|
||||||
String s2 = "";
|
String s2 = "";
|
||||||
assertThrowsExactly(IllegalArgumentException.class, () -> Validator.requireNotEmpty(s2));
|
assertThrowsExactly(IllegalArgumentException.class, () -> Validator.requireNotEmpty(s2, "content"));
|
||||||
assertThrowsExactly(IllegalArgumentException.class, () -> Validator.requireNotEmpty(s2, "Message"));
|
assertThrowsExactly(IllegalArgumentException.class, () -> Validator.requireNotEmpty(s2, "content", "Message"));
|
||||||
assertThrowsExactly(IllegalArgumentException.class, () -> Validator.requireNotEmpty(s2, ()->"Message"));
|
assertThrowsExactly(IllegalArgumentException.class, () -> Validator.requireNotEmpty(s2, ()->"Message"));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
262
src/test/java/schule/ngb/zm/util/abi/BinarySearchTree.java
Executable file
262
src/test/java/schule/ngb/zm/util/abi/BinarySearchTree.java
Executable file
@@ -0,0 +1,262 @@
|
|||||||
|
package schule.ngb.zm.util.abi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Materialien zu den zentralen NRW-Abiturpruefungen im Fach Informatik ab 2018
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Generische Klasse BinarySearchTree<ContentType>
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Mithilfe der generischen Klasse BinarySearchTree koennen beliebig viele
|
||||||
|
* Objekte in einem Binaerbaum (binaerer Suchbaum) entsprechend einer
|
||||||
|
* Ordnungsrelation verwaltet werden. <br />
|
||||||
|
* Ein Objekt der Klasse stellt entweder einen leeren binaeren Suchbaum dar oder
|
||||||
|
* verwaltet ein Inhaltsobjekt sowie einen linken und einen rechten Teilbaum,
|
||||||
|
* die ebenfalls Objekte der Klasse BinarySearchTree sind.<br />
|
||||||
|
* Die Klasse der Objekte, die in dem Suchbaum verwaltet werden sollen, muss
|
||||||
|
* das generische Interface ComparableContent implementieren. Dabei muss durch
|
||||||
|
* Ueberschreiben der drei Vergleichsmethoden isLess, isEqual, isGreater (s.
|
||||||
|
* Dokumentation des Interfaces) eine eindeutige Ordnungsrelation festgelegt
|
||||||
|
* sein. <br />
|
||||||
|
* Alle Objekte im linken Teilbaum sind kleiner als das Inhaltsobjekt des
|
||||||
|
* binaeren Suchbaums. Alle Objekte im rechten Teilbaum sind groesser als das
|
||||||
|
* Inhaltsobjekt des binaeren Suchbaums. Diese Bedingung gilt (rekursiv) auch in
|
||||||
|
* beiden Teilbaeumen. <br />
|
||||||
|
* Hinweis: In dieser Version wird die Klasse BinaryTree nicht benutzt.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author Qualitaets- und UnterstuetzungsAgentur - Landesinstitut fuer Schule
|
||||||
|
* @version Generisch_03 2017-11-28
|
||||||
|
*/
|
||||||
|
public class BinarySearchTree<ContentType extends ComparableContent<ContentType>> {
|
||||||
|
|
||||||
|
/* --------- Anfang der privaten inneren Klasse -------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Durch diese innere Klasse kann man dafuer sorgen, dass ein leerer Baum
|
||||||
|
* null ist, ein nicht-leerer Baum jedoch immer eine nicht-null-Wurzel sowie
|
||||||
|
* nicht-null-Teilbaeume hat.
|
||||||
|
*/
|
||||||
|
private class BSTNode<CT extends ComparableContent<CT>> {
|
||||||
|
|
||||||
|
private CT content;
|
||||||
|
private BinarySearchTree<CT> left, right;
|
||||||
|
|
||||||
|
public BSTNode(CT pContent) {
|
||||||
|
// Der Knoten hat einen linken und rechten Teilbaum, die
|
||||||
|
// beide von null verschieden sind. Also hat ein Blatt immer zwei
|
||||||
|
// leere Teilbaeume unter sich.
|
||||||
|
this.content = pContent;
|
||||||
|
left = new BinarySearchTree<CT>();
|
||||||
|
right = new BinarySearchTree<CT>();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------- Ende der privaten inneren Klasse -------------- */
|
||||||
|
|
||||||
|
private BSTNode<ContentType> node;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der Konstruktor erzeugt einen leeren Suchbaum.
|
||||||
|
*/
|
||||||
|
public BinarySearchTree() {
|
||||||
|
this.node = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Diese Anfrage liefert den Wahrheitswert true, wenn der Suchbaum leer ist,
|
||||||
|
* sonst liefert sie den Wert false.
|
||||||
|
*
|
||||||
|
* @return true, wenn der binaere Suchbaum leer ist, sonst false
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return this.node == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Falls der Parameter null ist, geschieht nichts.<br />
|
||||||
|
* Falls ein bezueglich der verwendeten Vergleichsmethode isEqual mit
|
||||||
|
* pContent uebereinstimmendes Objekt im geordneten binaeren Suchbau
|
||||||
|
* enthalten ist, passiert nichts. <br />
|
||||||
|
* Achtung: hier wird davon ausgegangen, dass isEqual genau dann true
|
||||||
|
* liefert, wenn isLess und isGreater false liefern. <br />
|
||||||
|
* Andernfalls (isLess oder isGreater) wird das Objekt pContent entsprechend
|
||||||
|
* der vorgegebenen Ordnungsrelation in den BinarySearchTree eingeordnet.
|
||||||
|
*
|
||||||
|
* @param pContent
|
||||||
|
* einzufuegendes Objekt vom Typ ContentType
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public void insert(ContentType pContent) {
|
||||||
|
if (pContent != null) {
|
||||||
|
if (isEmpty()) {
|
||||||
|
this.node = new BSTNode<ContentType>(pContent);
|
||||||
|
} else if (pContent.isLess(this.node.content)) {
|
||||||
|
this.node.left.insert(pContent);
|
||||||
|
} else if(pContent.isGreater(this.node.content)) {
|
||||||
|
this.node.right.insert(pContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Diese Anfrage liefert den linken Teilbaum des binaeren Suchbaumes. <br />
|
||||||
|
* Wenn er leer ist, wird null zurueckgegeben.
|
||||||
|
*
|
||||||
|
* @return den linken Teilbaum (Objekt vom Typ BinarySearchTree<ContentType>)
|
||||||
|
* bzw. null, wenn der Suchbaum leer ist
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public BinarySearchTree<ContentType> getLeftTree() {
|
||||||
|
if (this.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return this.node.left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Diese Anfrage liefert das Inhaltsobjekt des Suchbaumes. Wenn der Suchbaum
|
||||||
|
* leer ist, wird null zurueckgegeben.
|
||||||
|
*
|
||||||
|
* @return das Inhaltsobjekt vom Typ ContentType bzw. null, wenn der aktuelle
|
||||||
|
* Suchbaum leer ist
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public ContentType getContent() {
|
||||||
|
if (this.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return this.node.content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Diese Anfrage liefert den rechten Teilbaum des binaeren Suchbaumes. <br />
|
||||||
|
* Wenn er leer ist, wird null zurueckgegeben.
|
||||||
|
*
|
||||||
|
* @return den rechten Teilbaum (Objekt vom Typ BinarySearchTree<ContentType>)
|
||||||
|
* bzw. null, wenn der aktuelle Suchbaum leer ist
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public BinarySearchTree<ContentType> getRightTree() {
|
||||||
|
if (this.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return this.node.right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Falls ein bezueglich der verwendeten Vergleichsmethode mit
|
||||||
|
* pContent uebereinstimmendes Objekt im binaeren Suchbaum enthalten
|
||||||
|
* ist, wird dieses entfernt. Falls der Parameter null ist, aendert sich
|
||||||
|
* nichts.
|
||||||
|
*
|
||||||
|
* @param pContent
|
||||||
|
* zu entfernendes Objekt vom Typ ContentType
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public void remove(ContentType pContent) {
|
||||||
|
if (isEmpty() || pContent == null ) {
|
||||||
|
// Abbrechen, da kein Element zum entfernen vorhanden ist.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pContent.isLess(node.content)) {
|
||||||
|
// Element ist im linken Teilbaum zu loeschen.
|
||||||
|
node.left.remove(pContent);
|
||||||
|
} else if (pContent.isGreater(node.content)) {
|
||||||
|
// Element ist im rechten Teilbaum zu loeschen.
|
||||||
|
node.right.remove(pContent);
|
||||||
|
} else {
|
||||||
|
// Element ist gefunden.
|
||||||
|
if (node.left.isEmpty()) {
|
||||||
|
if (node.right.isEmpty()) {
|
||||||
|
// Es gibt keinen Nachfolger.
|
||||||
|
node = null;
|
||||||
|
} else {
|
||||||
|
// Es gibt nur rechts einen Nachfolger.
|
||||||
|
node = getNodeOfRightSuccessor();
|
||||||
|
}
|
||||||
|
} else if (node.right.isEmpty()) {
|
||||||
|
// Es gibt nur links einen Nachfolger.
|
||||||
|
node = getNodeOfLeftSuccessor();
|
||||||
|
} else {
|
||||||
|
// Es gibt links und rechts einen Nachfolger.
|
||||||
|
if (getNodeOfRightSuccessor().left.isEmpty()) {
|
||||||
|
// Der rechte Nachfolger hat keinen linken Nachfolger.
|
||||||
|
node.content = getNodeOfRightSuccessor().content;
|
||||||
|
node.right = getNodeOfRightSuccessor().right;
|
||||||
|
} else {
|
||||||
|
BinarySearchTree<ContentType> previous = node.right
|
||||||
|
.ancestorOfSmallRight();
|
||||||
|
BinarySearchTree<ContentType> smallest = previous.node.left;
|
||||||
|
this.node.content = smallest.node.content;
|
||||||
|
previous.remove(smallest.node.content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Falls ein bezueglich der verwendeten Vergleichsmethode isEqual mit
|
||||||
|
* pContent uebereinstimmendes Objekt im binaeren Suchbaum enthalten ist,
|
||||||
|
* liefert die Anfrage dieses, ansonsten wird null zurueckgegeben. <br />
|
||||||
|
* Falls der Parameter null ist, wird null zurueckgegeben.
|
||||||
|
*
|
||||||
|
* @param pContent
|
||||||
|
* zu suchendes Objekt vom Typ ContentType
|
||||||
|
* @return das gefundene Objekt vom Typ ContentType, bei erfolgloser Suche null
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public ContentType search(ContentType pContent) {
|
||||||
|
if (this.isEmpty() || pContent == null) {
|
||||||
|
// Abbrechen, da es kein Element zu suchen gibt.
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
ContentType content = this.getContent();
|
||||||
|
if (pContent.isLess(content)) {
|
||||||
|
// Element wird im linken Teilbaum gesucht.
|
||||||
|
return this.getLeftTree().search(pContent);
|
||||||
|
} else if (pContent.isGreater(content)) {
|
||||||
|
// Element wird im rechten Teilbaum gesucht.
|
||||||
|
return this.getRightTree().search(pContent);
|
||||||
|
} else if (pContent.isEqual(content)) {
|
||||||
|
// Element wurde gefunden.
|
||||||
|
return content;
|
||||||
|
} else {
|
||||||
|
// Dieser Fall sollte nicht auftreten.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------- Weitere private Methoden -------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Methode liefert denjenigen Baum, dessen linker Nachfolger keinen linken
|
||||||
|
* Nachfolger mehr hat. Es ist also spaeter moeglich, in einem Baum im
|
||||||
|
* rechten Nachfolger den Vorgaenger des linkesten Nachfolgers zu finden.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private BinarySearchTree<ContentType> ancestorOfSmallRight() {
|
||||||
|
if (getNodeOfLeftSuccessor().left.isEmpty()) {
|
||||||
|
return this;
|
||||||
|
} else {
|
||||||
|
return node.left.ancestorOfSmallRight();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BSTNode<ContentType> getNodeOfLeftSuccessor() {
|
||||||
|
return node.left.node;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BSTNode<ContentType> getNodeOfRightSuccessor() {
|
||||||
|
return node.right.node;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
212
src/test/java/schule/ngb/zm/util/abi/BinaryTree.java
Executable file
212
src/test/java/schule/ngb/zm/util/abi/BinaryTree.java
Executable file
@@ -0,0 +1,212 @@
|
|||||||
|
package schule.ngb.zm.util.abi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Materialien zu den zentralen NRW-Abiturpruefungen im Fach Informatik ab 2018
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Generische Klasse BinaryTree<ContentType>
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Mithilfe der generischen Klasse BinaryTree koennen beliebig viele
|
||||||
|
* Inhaltsobjekte vom Typ ContentType in einem Binaerbaum verwaltet werden. Ein
|
||||||
|
* Objekt der Klasse stellt entweder einen leeren Baum dar oder verwaltet ein
|
||||||
|
* Inhaltsobjekt sowie einen linken und einen rechten Teilbaum, die ebenfalls
|
||||||
|
* Objekte der generischen Klasse BinaryTree sind.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author Qualitaets- und UnterstuetzungsAgentur - Landesinstitut fuer Schule
|
||||||
|
* @version Generisch_03 2014-03-01
|
||||||
|
*/
|
||||||
|
public class BinaryTree<ContentType> {
|
||||||
|
|
||||||
|
/* --------- Anfang der privaten inneren Klasse -------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Durch diese innere Klasse kann man dafuer sorgen, dass ein leerer Baum
|
||||||
|
* null ist, ein nicht-leerer Baum jedoch immer eine nicht-null-Wurzel sowie
|
||||||
|
* nicht-null-Teilbaeume, ggf. leere Teilbaeume hat.
|
||||||
|
*/
|
||||||
|
private class BTNode<CT> {
|
||||||
|
|
||||||
|
private CT content;
|
||||||
|
private BinaryTree<CT> left, right;
|
||||||
|
|
||||||
|
public BTNode(CT pContent) {
|
||||||
|
// Der Knoten hat einen linken und einen rechten Teilbaum, die
|
||||||
|
// beide von null verschieden sind. Also hat ein Blatt immer zwei
|
||||||
|
// leere Teilbaeume unter sich.
|
||||||
|
this.content = pContent;
|
||||||
|
left = new BinaryTree<CT>();
|
||||||
|
right = new BinaryTree<CT>();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------- Ende der privaten inneren Klasse -------------- */
|
||||||
|
|
||||||
|
private BTNode<ContentType> node;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nach dem Aufruf des Konstruktors existiert ein leerer Binaerbaum.
|
||||||
|
*/
|
||||||
|
public BinaryTree() {
|
||||||
|
this.node = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wenn der Parameter pContent ungleich null ist, existiert nach dem Aufruf
|
||||||
|
* des Konstruktors der Binaerbaum und hat pContent als Inhaltsobjekt und
|
||||||
|
* zwei leere Teilbaeume. Falls der Parameter null ist, wird ein leerer
|
||||||
|
* Binaerbaum erzeugt.
|
||||||
|
*
|
||||||
|
* @param pContent
|
||||||
|
* das Inhaltsobjekt des Wurzelknotens vom Typ ContentType
|
||||||
|
*/
|
||||||
|
public BinaryTree(ContentType pContent) {
|
||||||
|
if (pContent != null) {
|
||||||
|
this.node = new BTNode<ContentType>(pContent);
|
||||||
|
} else {
|
||||||
|
this.node = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wenn der Parameter pContent ungleich null ist, wird ein Binaerbaum mit
|
||||||
|
* pContent als Inhalt und den beiden Teilbaeume pLeftTree und pRightTree
|
||||||
|
* erzeugt. Sind pLeftTree oder pRightTree gleich null, wird der
|
||||||
|
* entsprechende Teilbaum als leerer Binaerbaum eingefuegt. So kann es also
|
||||||
|
* nie passieren, dass linke oder rechte Teilbaeume null sind. Wenn der
|
||||||
|
* Parameter pContent gleich null ist, wird ein leerer Binaerbaum erzeugt.
|
||||||
|
*
|
||||||
|
* @param pContent
|
||||||
|
* das Inhaltsobjekt des Wurzelknotens vom Typ ContentType
|
||||||
|
* @param pLeftTree
|
||||||
|
* der linke Teilbaum vom Typ BinaryTree<ContentType>
|
||||||
|
* @param pRightTree
|
||||||
|
* der rechte Teilbaum vom Typ BinaryTree<ContentType>
|
||||||
|
*/
|
||||||
|
public BinaryTree(ContentType pContent, BinaryTree<ContentType> pLeftTree, BinaryTree<ContentType> pRightTree) {
|
||||||
|
if (pContent != null) {
|
||||||
|
this.node = new BTNode<ContentType>(pContent);
|
||||||
|
if (pLeftTree != null) {
|
||||||
|
this.node.left = pLeftTree;
|
||||||
|
} else {
|
||||||
|
this.node.left = new BinaryTree<ContentType>();
|
||||||
|
}
|
||||||
|
if (pRightTree != null) {
|
||||||
|
this.node.right = pRightTree;
|
||||||
|
} else {
|
||||||
|
this.node.right = new BinaryTree<ContentType>();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Da der Inhalt null ist, wird ein leerer BinarySearchTree erzeugt.
|
||||||
|
this.node = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Diese Anfrage liefert den Wahrheitswert true, wenn der Binaerbaum leer
|
||||||
|
* ist, sonst liefert sie den Wert false.
|
||||||
|
*
|
||||||
|
* @return true, wenn der Binaerbaum leer ist, sonst false
|
||||||
|
*/
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return this.node == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wenn pContent null ist, geschieht nichts. <br />
|
||||||
|
* Ansonsten: Wenn der Binaerbaum leer ist, wird der Parameter pContent als
|
||||||
|
* Inhaltsobjekt sowie ein leerer linker und rechter Teilbaum eingefuegt.
|
||||||
|
* Ist der Binaerbaum nicht leer, wird das Inhaltsobjekt durch pContent
|
||||||
|
* ersetzt. Die Teilbaeume werden nicht geaendert.
|
||||||
|
*
|
||||||
|
* @param pContent
|
||||||
|
* neues Inhaltsobjekt vom Typ ContentType
|
||||||
|
*/
|
||||||
|
public void setContent(ContentType pContent) {
|
||||||
|
if (pContent != null) {
|
||||||
|
if (this.isEmpty()) {
|
||||||
|
node = new BTNode<ContentType>(pContent);
|
||||||
|
this.node.left = new BinaryTree<ContentType>();
|
||||||
|
this.node.right = new BinaryTree<ContentType>();
|
||||||
|
}
|
||||||
|
this.node.content = pContent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Diese Anfrage liefert das Inhaltsobjekt des Binaerbaums. Wenn der
|
||||||
|
* Binaerbaum leer ist, wird null zurueckgegeben.
|
||||||
|
*
|
||||||
|
* @return das Inhaltsobjekt der Wurzel vom Typ ContentType bzw. null, wenn
|
||||||
|
* der Binaerbaum leer ist
|
||||||
|
*/
|
||||||
|
public ContentType getContent() {
|
||||||
|
if (this.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return this.node.content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Falls der Parameter null ist, geschieht nichts. Wenn der Binaerbaum leer
|
||||||
|
* ist, wird pTree nicht angehaengt. Andernfalls erhaelt der Binaerbaum den
|
||||||
|
* uebergebenen BinaryTree als linken Teilbaum.
|
||||||
|
*
|
||||||
|
* @param pTree
|
||||||
|
* neuer linker Teilbaum vom Typ BinaryTree<ContentType>
|
||||||
|
*/
|
||||||
|
public void setLeftTree(BinaryTree<ContentType> pTree) {
|
||||||
|
if (!this.isEmpty() && pTree != null) {
|
||||||
|
this.node.left = pTree;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Falls der Parameter null ist, geschieht nichts. Wenn der Binaerbaum leer
|
||||||
|
* ist, wird pTree nicht angehaengt. Andernfalls erhaelt der Binaerbaum den
|
||||||
|
* uebergebenen BinaryTree als rechten Teilbaum.
|
||||||
|
*
|
||||||
|
* @param pTree
|
||||||
|
* neuer linker Teilbaum vom Typ BinaryTree<ContentType>
|
||||||
|
*/
|
||||||
|
public void setRightTree(BinaryTree<ContentType> pTree) {
|
||||||
|
if (!this.isEmpty() && pTree != null) {
|
||||||
|
this.node.right = pTree;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Diese Anfrage liefert den linken Teilbaum des Binaerbaumes. Wenn der
|
||||||
|
* Binaerbaum leer ist, wird null zurueckgegeben.
|
||||||
|
*
|
||||||
|
* @return linker Teilbaum vom Typ BinaryTree<ContentType> oder null, wenn
|
||||||
|
* der aktuelle Binaerbaum leer ist
|
||||||
|
*/
|
||||||
|
public BinaryTree<ContentType> getLeftTree() {
|
||||||
|
if (!this.isEmpty()) {
|
||||||
|
return this.node.left;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Diese Anfrage liefert den rechten Teilbaum des Binaerbaumes. Wenn der
|
||||||
|
* Binaerbaum (this) leer ist, wird null zurueckgegeben.
|
||||||
|
*
|
||||||
|
* @return rechter Teilbaum vom Typ BinaryTree<ContentType> oder null, wenn
|
||||||
|
* der aktuelle Binaerbaum (this) leer ist
|
||||||
|
*/
|
||||||
|
public BinaryTree<ContentType> getRightTree() {
|
||||||
|
if (!this.isEmpty()) {
|
||||||
|
return this.node.right;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
163
src/test/java/schule/ngb/zm/util/abi/Client.java
Executable file
163
src/test/java/schule/ngb/zm/util/abi/Client.java
Executable file
@@ -0,0 +1,163 @@
|
|||||||
|
package schule.ngb.zm.util.abi;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.net.Socket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Materialien zu den zentralen NRW-Abiturpruefungen im Fach Informatik ab 2018
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Klasse Client
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Objekte von Unterklassen der abstrakten Klasse Client ermoeglichen
|
||||||
|
* Netzwerkverbindungen zu einem Server mittels TCP/IP-Protokoll. Nach
|
||||||
|
* Verbindungsaufbau koennen Zeichenketten (Strings) zum Server gesendet und von
|
||||||
|
* diesem empfangen werden, wobei der Nachrichtenempfang nebenlaeufig geschieht.
|
||||||
|
* Zur Vereinfachung finden Nachrichtenversand und -empfang zeilenweise statt,
|
||||||
|
* d. h., beim Senden einer Zeichenkette wird ein Zeilentrenner ergaenzt und beim
|
||||||
|
* Empfang wird dieser entfernt. Jede empfangene Nachricht wird einer
|
||||||
|
* Ereignisbehandlungsmethode uebergeben, die in Unterklassen implementiert werden
|
||||||
|
* muss. Es findet nur eine rudimentaere Fehlerbehandlung statt, so dass z.B.
|
||||||
|
* Verbindungsabbrueche nicht zu einem Programmabbruch fuehren. Eine einmal
|
||||||
|
* unterbrochene oder getrennte Verbindung kann nicht reaktiviert werden.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author Qualitaets- und UnterstuetzungsAgentur - Landesinstitut fuer Schule
|
||||||
|
* @version 30.08.2016
|
||||||
|
*/
|
||||||
|
|
||||||
|
public abstract class Client
|
||||||
|
{
|
||||||
|
private MessageHandler messageHandler;
|
||||||
|
|
||||||
|
private class MessageHandler extends Thread
|
||||||
|
{
|
||||||
|
private SocketWrapper socketWrapper;
|
||||||
|
private boolean active;
|
||||||
|
|
||||||
|
private class SocketWrapper
|
||||||
|
{
|
||||||
|
private Socket socket;
|
||||||
|
private BufferedReader fromServer;
|
||||||
|
private PrintWriter toServer;
|
||||||
|
|
||||||
|
public SocketWrapper(String pServerIP, int pServerPort)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
socket = new Socket(pServerIP, pServerPort);
|
||||||
|
toServer = new PrintWriter(socket.getOutputStream(), true);
|
||||||
|
fromServer = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
socket = null;
|
||||||
|
toServer = null;
|
||||||
|
fromServer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String receive()
|
||||||
|
{
|
||||||
|
if(fromServer != null)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return fromServer.readLine();
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
return(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void send(String pMessage)
|
||||||
|
{
|
||||||
|
if(toServer != null)
|
||||||
|
{
|
||||||
|
toServer.println(pMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close()
|
||||||
|
{
|
||||||
|
if(socket != null)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
socket.close();
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Falls eine Verbindung getrennt werden soll, deren Endpunkt
|
||||||
|
* nicht mehr existiert bzw. ihrerseits bereits beendet worden ist,
|
||||||
|
* geschieht nichts.
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private MessageHandler(String pServerIP, int pServerPort)
|
||||||
|
{
|
||||||
|
socketWrapper = new SocketWrapper(pServerIP, pServerPort);
|
||||||
|
start();
|
||||||
|
if(socketWrapper.socket != null)
|
||||||
|
active = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
String message = null;
|
||||||
|
while (active)
|
||||||
|
{
|
||||||
|
message = socketWrapper.receive();
|
||||||
|
if (message != null)
|
||||||
|
processMessage(message);
|
||||||
|
else
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void send(String pMessage)
|
||||||
|
{
|
||||||
|
if(active)
|
||||||
|
socketWrapper.send(pMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void close()
|
||||||
|
{
|
||||||
|
if(active)
|
||||||
|
{
|
||||||
|
active = false;
|
||||||
|
socketWrapper.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Client(String pServerIP, int pServerPort)
|
||||||
|
{
|
||||||
|
messageHandler = new MessageHandler(pServerIP, pServerPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isConnected()
|
||||||
|
{
|
||||||
|
return(messageHandler.active);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void send(String pMessage)
|
||||||
|
{
|
||||||
|
messageHandler.send(pMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close()
|
||||||
|
{
|
||||||
|
messageHandler.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void processMessage(String pMessage);
|
||||||
|
|
||||||
|
}
|
||||||
62
src/test/java/schule/ngb/zm/util/abi/ComparableContent.java
Executable file
62
src/test/java/schule/ngb/zm/util/abi/ComparableContent.java
Executable file
@@ -0,0 +1,62 @@
|
|||||||
|
package schule.ngb.zm.util.abi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Materialien zu den zentralen NRW-Abiturpruefungen im Fach Informatik ab 2018
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Generisches Interface (Schnittstelle) ComparableContent<ContentType>
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* <p>Das generische Interface ComparableContent<ContentType> legt die Methoden
|
||||||
|
* fest, ueber die Objekte verfuegen muessen, die in einen binaeren Suchbaum
|
||||||
|
* (BinarySearchTree) eingefuegt werden sollen. Die Ordnungsrelation wird in
|
||||||
|
* Klassen, die ComparableContent implementieren durch Ueberschreiben der drei
|
||||||
|
* implizit abstrakten Methoden isGreater, isEqual und isLess festgelegt.
|
||||||
|
* </p>
|
||||||
|
* </p>
|
||||||
|
* @author Qualitaets- und UnterstuetzungsAgentur - Landesinstitut fuer Schule
|
||||||
|
* @version Generisch_02 2014-03-01
|
||||||
|
*/
|
||||||
|
public interface ComparableContent<ContentType> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wenn festgestellt wird, dass das Objekt, von dem die Methode aufgerufen
|
||||||
|
* wird, bzgl. der gewuenschten Ordnungsrelation groesser als das Objekt
|
||||||
|
* pContent ist, wird true geliefert. Sonst wird false geliefert.
|
||||||
|
*
|
||||||
|
* @param pContent
|
||||||
|
* das mit dem aufrufenden Objekt zu vergleichende Objekt vom
|
||||||
|
* Typ ContentType
|
||||||
|
* @return true, wenn das aufrufende Objekt groesser ist als das Objekt
|
||||||
|
* pContent, sonst false
|
||||||
|
*/
|
||||||
|
public boolean isGreater(ContentType pContent);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wenn festgestellt wird, dass das Objekt, von dem die Methode aufgerufen
|
||||||
|
* wird, bzgl. der gewuenschten Ordnungsrelation gleich gross wie das Objekt
|
||||||
|
* pContent ist, wird true geliefert. Sonst wird false geliefert.
|
||||||
|
*
|
||||||
|
* @param pContent
|
||||||
|
* das mit dem aufrufenden Objekt zu vergleichende Objekt vom
|
||||||
|
* Typ ContentType
|
||||||
|
* @return true, wenn das aufrufende Objekt gleich gross ist wie das Objekt
|
||||||
|
* pContent, sonst false
|
||||||
|
*/
|
||||||
|
public boolean isEqual(ContentType pContent);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wenn festgestellt wird, dass das Objekt, von dem die Methode aufgerufen
|
||||||
|
* wird, bzgl. der gewuenschten Ordnungsrelation kleiner als das Objekt
|
||||||
|
* pContent ist, wird true geliefert. Sonst wird false geliefert.
|
||||||
|
*
|
||||||
|
* @param pContent
|
||||||
|
* das mit dem aufrufenden Objekt zu vergleichende Objekt vom
|
||||||
|
* Typ ContentType
|
||||||
|
* @return true, wenn das aufrufende Objekt kleiner ist als das Objekt
|
||||||
|
* pContent, sonst false
|
||||||
|
*/
|
||||||
|
public boolean isLess(ContentType pContent);
|
||||||
|
|
||||||
|
}
|
||||||
90
src/test/java/schule/ngb/zm/util/abi/Connection.java
Executable file
90
src/test/java/schule/ngb/zm/util/abi/Connection.java
Executable file
@@ -0,0 +1,90 @@
|
|||||||
|
package schule.ngb.zm.util.abi; /**
|
||||||
|
* <p>
|
||||||
|
* Materialien zu den zentralen NRW-Abiturpruefungen im Fach Informatik ab 2018
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Klasse Connection
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Objekte der Klasse Connection ermoeglichen eine Netzwerkverbindung zu einem
|
||||||
|
* Server mittels TCP/IP-Protokoll. Nach Verbindungsaufbau koennen Zeichenketten
|
||||||
|
* (Strings) zum Server gesendet und von diesem empfangen werden. Zur
|
||||||
|
* Vereinfachung geschieht dies zeilenweise, d. h., beim Senden einer
|
||||||
|
* Zeichenkette wird ein Zeilentrenner ergaenzt und beim Empfang wird dieser
|
||||||
|
* entfernt. Es findet nur eine rudimentaere Fehlerbehandlung statt, so dass z.B.
|
||||||
|
* der Zugriff auf unterbrochene oder bereits getrennte Verbindungen nicht zu
|
||||||
|
* einem Programmabbruch fuehrt. Eine einmal getrennte Verbindung kann nicht
|
||||||
|
* reaktiviert werden.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author Qualitaets- und UnterstuetzungsAgentur - Landesinstitut fuer Schule
|
||||||
|
* @version 30.08.2016
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.net.*;
|
||||||
|
import java.io.*;
|
||||||
|
|
||||||
|
public class Connection
|
||||||
|
{
|
||||||
|
private Socket socket;
|
||||||
|
private BufferedReader fromServer;
|
||||||
|
private PrintWriter toServer;
|
||||||
|
|
||||||
|
public Connection(String pServerIP, int pServerPort)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
socket = new Socket(pServerIP, pServerPort);
|
||||||
|
toServer = new PrintWriter(socket.getOutputStream(), true);
|
||||||
|
fromServer = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
//Erstelle eine nicht-verbundene Instanz von Socket, wenn die avisierte
|
||||||
|
//Verbindung nicht hergestellt werden kann
|
||||||
|
socket = null;
|
||||||
|
toServer = null;
|
||||||
|
fromServer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String receive()
|
||||||
|
{
|
||||||
|
if(fromServer != null)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return fromServer.readLine();
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
return(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void send(String pMessage)
|
||||||
|
{
|
||||||
|
if(toServer != null)
|
||||||
|
{
|
||||||
|
toServer.println(pMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close()
|
||||||
|
{
|
||||||
|
|
||||||
|
if(socket != null && !socket.isClosed())
|
||||||
|
try
|
||||||
|
{
|
||||||
|
socket.close();
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Falls eine Verbindung geschlossen werden soll, deren Endpunkt nicht
|
||||||
|
* mehr existiert bzw. seinerseits bereits geschlossen worden ist oder
|
||||||
|
* die nicht korrekt instanziiert werden konnte (socket == null), geschieht
|
||||||
|
* nichts.
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
151
src/test/java/schule/ngb/zm/util/abi/DatabaseConnector.java
Executable file
151
src/test/java/schule/ngb/zm/util/abi/DatabaseConnector.java
Executable file
@@ -0,0 +1,151 @@
|
|||||||
|
package schule.ngb.zm.util.abi;
|
||||||
|
|
||||||
|
import java.sql.*;
|
||||||
|
import java.sql.Connection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Materialien zu den zentralen NRW-Abiturpruefungen im Fach Informatik ab 2018
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Klasse DatabaseConnector
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Ein Objekt der Klasse DatabaseConnector ermoeglicht die Abfrage und Manipulation
|
||||||
|
* einer SQLite-Datenbank.
|
||||||
|
* Beim Erzeugen des Objekts wird eine Datenbankverbindung aufgebaut, so dass
|
||||||
|
* anschließend SQL-Anweisungen an diese Datenbank gerichtet werden koennen.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author Qualitaets- und UnterstuetzungsAgentur - Landesinstitut fuer Schule
|
||||||
|
* @version 2016-01-24
|
||||||
|
*/
|
||||||
|
public class DatabaseConnector{
|
||||||
|
private Connection connection;
|
||||||
|
private QueryResult currentQueryResult = null;
|
||||||
|
private String message = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ein Objekt vom Typ DatabaseConnector wird erstellt, und eine Verbindung zur Datenbank
|
||||||
|
* wird aufgebaut. Mit den Parametern pIP und pPort werden die IP-Adresse und die
|
||||||
|
* Port-Nummer uebergeben, unter denen die Datenbank mit Namen pDatabase zu erreichen ist.
|
||||||
|
* Mit den Parametern pUsername und pPassword werden Benutzername und Passwort fuer die
|
||||||
|
* Datenbank uebergeben.
|
||||||
|
*/
|
||||||
|
public DatabaseConnector(String pIP, int pPort, String pDatabase, String pUsername, String pPassword){
|
||||||
|
//Eine Impementierung dieser Schnittstelle fuer SQLite ignoriert pID und pPort, da die Datenbank immer lokal ist.
|
||||||
|
//Auch pUsername und pPassword werden nicht verwendet, da SQLite sie nicht unterstuetzt.
|
||||||
|
try {
|
||||||
|
//Laden der Treiberklasse
|
||||||
|
Class.forName("org.sqlite.JDBC");
|
||||||
|
|
||||||
|
//Verbindung herstellen
|
||||||
|
connection = DriverManager.getConnection("jdbc:sqlite:"+pDatabase);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
message = e.getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der Auftrag schickt den im Parameter pSQLStatement enthaltenen SQL-Befehl an die
|
||||||
|
* Datenbank ab.
|
||||||
|
* Handelt es sich bei pSQLStatement um einen SQL-Befehl, der eine Ergebnismenge
|
||||||
|
* liefert, so kann dieses Ergebnis anschließend mit der Methode getCurrentQueryResult
|
||||||
|
* abgerufen werden.
|
||||||
|
*/
|
||||||
|
public void executeStatement(String pSQLStatement){
|
||||||
|
//Altes Ergebnis loeschen
|
||||||
|
currentQueryResult = null;
|
||||||
|
message = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
//Neues Statement erstellen
|
||||||
|
Statement statement = connection.createStatement();
|
||||||
|
|
||||||
|
//SQL Anweisung an die DB schicken.
|
||||||
|
if (statement.execute(pSQLStatement)) { //Fall 1: Es gibt ein Ergebnis
|
||||||
|
|
||||||
|
//Resultset auslesen
|
||||||
|
ResultSet resultset = statement.getResultSet();
|
||||||
|
|
||||||
|
//Spaltenanzahl ermitteln
|
||||||
|
int columnCount = resultset.getMetaData().getColumnCount();
|
||||||
|
|
||||||
|
//Spaltennamen und Spaltentypen in Felder uebertragen
|
||||||
|
String[] resultColumnNames = new String[columnCount];
|
||||||
|
String[] resultColumnTypes = new String[columnCount];
|
||||||
|
for (int i = 0; i < columnCount; i++){
|
||||||
|
resultColumnNames[i] = resultset.getMetaData().getColumnLabel(i+1);
|
||||||
|
resultColumnTypes[i] = resultset.getMetaData().getColumnTypeName(i+1);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Queue fuer die Zeilen der Ergebnistabelle erstellen
|
||||||
|
Queue<String[]> rows = new Queue<String[]>();
|
||||||
|
|
||||||
|
//Daten in Queue uebertragen und Zeilen zaehlen
|
||||||
|
int rowCount = 0;
|
||||||
|
while (resultset.next()){
|
||||||
|
String[] resultrow = new String[columnCount];
|
||||||
|
for (int s = 0; s < columnCount; s++){
|
||||||
|
resultrow[s] = resultset.getString(s+1);
|
||||||
|
}
|
||||||
|
rows.enqueue(resultrow);
|
||||||
|
rowCount = rowCount + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Ergebnisfeld erstellen und Zeilen aus Queue uebertragen
|
||||||
|
String[][] resultData = new String[rowCount][columnCount];
|
||||||
|
int j = 0;
|
||||||
|
while (!rows.isEmpty()){
|
||||||
|
resultData[j] = rows.front();
|
||||||
|
rows.dequeue();
|
||||||
|
j = j + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Statement schließen und Ergebnisobjekt erstellen
|
||||||
|
statement.close();
|
||||||
|
currentQueryResult = new QueryResult(resultData, resultColumnNames, resultColumnTypes);
|
||||||
|
|
||||||
|
} else { //Fall 2: Es gibt kein Ergebnis.
|
||||||
|
//Statement ohne Ergebnisobjekt schliessen
|
||||||
|
statement.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
//Fehlermeldung speichern
|
||||||
|
message = e.getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert das Ergebnis des letzten mit der Methode executeStatement an
|
||||||
|
* die Datenbank geschickten SQL-Befehls als Ob-jekt vom Typ QueryResult zurueck.
|
||||||
|
* Wurde bisher kein SQL-Befehl abgeschickt oder ergab der letzte Aufruf von
|
||||||
|
* executeStatement keine Ergebnismenge (z.B. bei einem INSERT-Befehl oder einem
|
||||||
|
* Syntaxfehler), so wird null geliefert.
|
||||||
|
*/
|
||||||
|
public QueryResult getCurrentQueryResult(){
|
||||||
|
return currentQueryResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert null oder eine Fehlermeldung, die sich jeweils auf die letzte zuvor ausgefuehrte
|
||||||
|
* Datenbankoperation bezieht.
|
||||||
|
*/
|
||||||
|
public String getErrorMessage(){
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Datenbankverbindung wird geschlossen.
|
||||||
|
*/
|
||||||
|
public void close(){
|
||||||
|
try{
|
||||||
|
connection.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
message = e.getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
77
src/test/java/schule/ngb/zm/util/abi/Edge.java
Executable file
77
src/test/java/schule/ngb/zm/util/abi/Edge.java
Executable file
@@ -0,0 +1,77 @@
|
|||||||
|
package schule.ngb.zm.util.abi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Materialien zu den zentralen NRW-Abiturpruefungen im Fach Informatik ab 2018
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Klasse Edge
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Die Klasse Edge stellt eine einzelne, ungerichtete Kante eines Graphen dar.
|
||||||
|
* Beim Erstellen werden die beiden durch sie zu verbindenden Knotenobjekte und eine
|
||||||
|
* Gewichtung als double uebergeben. Beide Knotenobjekte koennen abgefragt werden.
|
||||||
|
* Des Weiteren koennen die Gewichtung und eine Markierung gesetzt und abgefragt werden.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author Qualitaets- und UnterstuetzungsAgentur - Landesinstitut fuer Schule
|
||||||
|
* @version Oktober 2015
|
||||||
|
*/
|
||||||
|
public class Edge{
|
||||||
|
private Vertex[] vertices;
|
||||||
|
private double weight;
|
||||||
|
private boolean mark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ein neues Objekt vom Typ Edge wird erstellt. Die von diesem Objekt
|
||||||
|
* repraesentierte Kante verbindet die Knoten pVertex und pAnotherVertex mit der
|
||||||
|
* Gewichtung pWeight. Ihre Markierung hat den Wert false.
|
||||||
|
*/
|
||||||
|
public Edge( Vertex pVertex, Vertex pAnotherVertex, double pWeight){
|
||||||
|
vertices = new Vertex[2];
|
||||||
|
vertices[0] = pVertex;
|
||||||
|
vertices[1] = pAnotherVertex;
|
||||||
|
weight = pWeight;
|
||||||
|
mark = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage gibt die beiden Knoten, die durch die Kante verbunden werden, als neues Feld vom Typ Vertex zurueck. Das Feld hat
|
||||||
|
* genau zwei Eintraege mit den Indexwerten 0 und 1.
|
||||||
|
*/
|
||||||
|
public Vertex[] getVertices(){
|
||||||
|
Vertex[] result = new Vertex[2];
|
||||||
|
result[0] = vertices[0];
|
||||||
|
result[1] = vertices[1];
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der Auftrag setzt das Gewicht der Kante auf pWeight.
|
||||||
|
*/
|
||||||
|
public void setWeight(double pWeight){
|
||||||
|
weight = pWeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert das Gewicht der Kante als double.
|
||||||
|
*/
|
||||||
|
public double getWeight(){
|
||||||
|
return weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der Auftrag setzt die Markierung der Kante auf den Wert pMark.
|
||||||
|
*/
|
||||||
|
public void setMark(boolean pMark){
|
||||||
|
mark = pMark;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert true, wenn die Markierung der Kante den Wert true hat, ansonsten false.
|
||||||
|
*/
|
||||||
|
public boolean isMarked(){
|
||||||
|
return mark;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
314
src/test/java/schule/ngb/zm/util/abi/Graph.java
Executable file
314
src/test/java/schule/ngb/zm/util/abi/Graph.java
Executable file
@@ -0,0 +1,314 @@
|
|||||||
|
package schule.ngb.zm.util.abi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Materialien zu den zentralen NRW-Abiturpruefungen im Fach Informatik ab 2018
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Klasse Graph
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Die Klasse Graph stellt einen ungerichteten, kantengewichteten Graphen dar. Es koennen
|
||||||
|
* Knoten- und Kantenobjekte hinzugefuegt und entfernt, flache Kopien der Knoten- und Kantenlisten
|
||||||
|
* des Graphen angefragt und Markierungen von Knoten und Kanten gesetzt und ueberprueft werden.
|
||||||
|
* Des Weiteren kann eine Liste der Nachbarn eines bestimmten Knoten, eine Liste der inzidenten
|
||||||
|
* Kanten eines bestimmten Knoten und die Kante von einem bestimmten Knoten zu einem
|
||||||
|
* anderen bestimmten Knoten angefragt werden. Abgesehen davon kann abgefragt werden, welches
|
||||||
|
* Knotenobjekt zu einer bestimmten ID gehoert und ob der Graph leer ist.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author Qualitaets- und UnterstuetzungsAgentur - Landesinstitut fuer Schule
|
||||||
|
* @version Oktober 2015
|
||||||
|
*/
|
||||||
|
public class Graph{
|
||||||
|
private List<Vertex> vertices;
|
||||||
|
private List<Edge> edges;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ein Objekt vom Typ Graph wird erstellt. Der von diesem Objekt
|
||||||
|
* repraesentierte Graph ist leer.
|
||||||
|
*/
|
||||||
|
public Graph(){
|
||||||
|
//Leere Listen fuer Knoten und Kanten erstellen.
|
||||||
|
vertices = new List<Vertex>();
|
||||||
|
edges = new List<Edge>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert eine neue Liste aller Knotenobjekte vom Typ List<Vertex>.
|
||||||
|
*/
|
||||||
|
public List<Vertex> getVertices(){
|
||||||
|
//Eine neue Liste mit allen Vertex-Objekten erstellen.
|
||||||
|
List<Vertex> result = new List<Vertex>();
|
||||||
|
vertices.toFirst();
|
||||||
|
while (vertices.hasAccess()){
|
||||||
|
result.append(vertices.getContent());
|
||||||
|
vertices.next();
|
||||||
|
}
|
||||||
|
//Aktuelles Element zum Anfang bewegen.
|
||||||
|
result.toFirst();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert eine neue Liste aller Kantenobjekte vom Typ List<Edge>.
|
||||||
|
*/
|
||||||
|
public List<Edge> getEdges(){
|
||||||
|
//Eine neue Liste mit allen Edge-Objekten erstellen.
|
||||||
|
List<Edge> result = new List<Edge>();
|
||||||
|
edges.toFirst();
|
||||||
|
while (edges.hasAccess()){
|
||||||
|
result.append(edges.getContent());
|
||||||
|
edges.next();
|
||||||
|
}
|
||||||
|
//Aktuelles Element zum Anfang bewegen.
|
||||||
|
result.toFirst();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert das Knotenobjekt mit pID als ID. Ist ein solchen Knotenobjekt nicht im Graphen enthalten,
|
||||||
|
* wird null zurueckgeliefert.
|
||||||
|
*/
|
||||||
|
public Vertex getVertex( String pID){
|
||||||
|
//Vertex-Objekt mit pID als ID suchen.
|
||||||
|
Vertex result = null;
|
||||||
|
vertices.toFirst();
|
||||||
|
while (vertices.hasAccess() && result == null){
|
||||||
|
if (vertices.getContent().getID().equals(pID)){
|
||||||
|
result = vertices.getContent();
|
||||||
|
}
|
||||||
|
vertices.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
//Objekt zurueckliefern.
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der Auftrag fuegt den Knoten pVertex in den Graphen ein, sofern es noch keinen
|
||||||
|
* Knoten mit demselben ID-Eintrag wie pVertex im Graphen gibt und pVertex eine ID ungleich null hat.
|
||||||
|
* Ansonsten passiert nichts.
|
||||||
|
*/
|
||||||
|
public void addVertex( Vertex pVertex){
|
||||||
|
//Pruefen, ob der Vertex existiert und eine ID hat.
|
||||||
|
if (pVertex != null && pVertex.getID() != null) {
|
||||||
|
//Pruefen, ob nicht schon ein Vertex mit gleicher ID enthalten ist.
|
||||||
|
boolean freeID = true;
|
||||||
|
vertices.toFirst();
|
||||||
|
while (vertices.hasAccess() && freeID){
|
||||||
|
if (vertices.getContent().getID().equals(pVertex.getID())){
|
||||||
|
freeID = false;
|
||||||
|
}
|
||||||
|
vertices.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
//Wenn die ID noch frei ist, den Vertex einfuegen, sonst nichts tun.
|
||||||
|
if (freeID) {
|
||||||
|
vertices.append(pVertex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der Auftrag fuegt die Kante pEdge in den Graphen ein, sofern beide durch die Kante verbundenen Knoten
|
||||||
|
* im Graphen enthalten sind, nicht identisch sind und noch keine Kante zwischen den Knoten existiert. Ansonsten passiert nichts.
|
||||||
|
*/
|
||||||
|
public void addEdge(Edge pEdge){
|
||||||
|
//Pruefen, ob pEdge exisitert.
|
||||||
|
if (pEdge != null){
|
||||||
|
Vertex[] vertexPair = pEdge.getVertices();
|
||||||
|
|
||||||
|
//Einfuegekriterien pruefen.
|
||||||
|
if (vertexPair[0] != null && vertexPair[1] != null &&
|
||||||
|
this.getVertex(vertexPair[0].getID()) == vertexPair[0] &&
|
||||||
|
this.getVertex(vertexPair[1].getID()) == vertexPair[1] &&
|
||||||
|
this.getEdge(vertexPair[0], vertexPair[1]) == null &&
|
||||||
|
vertexPair[0] != vertexPair[1]){
|
||||||
|
//Kante einfuegen.
|
||||||
|
edges.append(pEdge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der Auftrag entfernt den Knoten pVertex aus dem Graphen und loescht alle Kanten, die mit ihm inzident sind.
|
||||||
|
* Ist der Knoten pVertex nicht im Graphen enthalten, passiert nichts.
|
||||||
|
*/
|
||||||
|
public void removeVertex( Vertex pVertex){
|
||||||
|
//Inzidente Kanten entfernen.
|
||||||
|
edges.toFirst();
|
||||||
|
while (edges.hasAccess()){
|
||||||
|
Vertex[] akt = edges.getContent().getVertices();
|
||||||
|
if (akt[0] == pVertex || akt[1] == pVertex){
|
||||||
|
edges.remove();
|
||||||
|
} else {
|
||||||
|
edges.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Knoten entfernen
|
||||||
|
vertices.toFirst();
|
||||||
|
while (vertices.hasAccess() && vertices.getContent()!= pVertex){
|
||||||
|
vertices.next();
|
||||||
|
}
|
||||||
|
if (vertices.hasAccess()){
|
||||||
|
vertices.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der Auftrag entfernt die Kante pEdge aus dem Graphen. Ist die Kante pEdge nicht
|
||||||
|
* im Graphen enthalten, passiert nichts.
|
||||||
|
*/
|
||||||
|
public void removeEdge(Edge pEdge){
|
||||||
|
//Kante aus Kantenliste des Graphen entfernen.
|
||||||
|
edges.toFirst();
|
||||||
|
while (edges.hasAccess()){
|
||||||
|
if (edges.getContent() == pEdge){
|
||||||
|
edges.remove();
|
||||||
|
} else {
|
||||||
|
edges.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der Auftrag setzt die Markierungen aller Knoten des Graphen auf pMark.
|
||||||
|
*/
|
||||||
|
public void setAllVertexMarks(boolean pMark){
|
||||||
|
vertices.toFirst();
|
||||||
|
while (vertices.hasAccess()){
|
||||||
|
vertices.getContent().setMark(pMark);
|
||||||
|
vertices.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der Auftrag setzt die Markierungen aller Kanten des Graphen auf pMark.
|
||||||
|
*/
|
||||||
|
public void setAllEdgeMarks(boolean pMark){
|
||||||
|
edges.toFirst();
|
||||||
|
while (edges.hasAccess()){
|
||||||
|
edges.getContent().setMark(pMark);
|
||||||
|
edges.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert true, wenn alle Knoten des Graphen mit true markiert sind, ansonsten false.
|
||||||
|
*/
|
||||||
|
public boolean allVerticesMarked(){
|
||||||
|
boolean result = true;
|
||||||
|
vertices.toFirst();
|
||||||
|
while (vertices.hasAccess()){
|
||||||
|
if (!vertices.getContent().isMarked()){
|
||||||
|
result = false;
|
||||||
|
}
|
||||||
|
vertices.next();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert true, wenn alle Kanten des Graphen mit true markiert sind, ansonsten false.
|
||||||
|
*/
|
||||||
|
public boolean allEdgesMarked(){
|
||||||
|
boolean result = true;
|
||||||
|
edges.toFirst();
|
||||||
|
while (edges.hasAccess()){
|
||||||
|
if (!edges.getContent().isMarked()){
|
||||||
|
result = false;
|
||||||
|
}
|
||||||
|
edges.next();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert alle Nachbarn des Knotens pVertex als neue Liste vom Typ List<Vertex>. Hat der Knoten
|
||||||
|
* pVertex keine Nachbarn in diesem Graphen oder ist gar nicht in diesem Graphen enthalten, so
|
||||||
|
* wird eine leere Liste zurueckgeliefert.
|
||||||
|
*/
|
||||||
|
public List<Vertex> getNeighbours( Vertex pVertex){
|
||||||
|
List<Vertex> result = new List<Vertex>();
|
||||||
|
|
||||||
|
//Alle Kanten durchlaufen.
|
||||||
|
edges.toFirst();
|
||||||
|
while (edges.hasAccess()){
|
||||||
|
|
||||||
|
//Wenn ein Knoten der Kante pVertex ist, den anderen als Nachbarn in die Ergebnisliste einfuegen.
|
||||||
|
Vertex[] vertexPair = edges.getContent().getVertices();
|
||||||
|
if (vertexPair[0] == pVertex) {
|
||||||
|
result.append(vertexPair[1]);
|
||||||
|
} else {
|
||||||
|
if (vertexPair[1] == pVertex){
|
||||||
|
result.append(vertexPair[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
edges.next();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert eine neue Liste alle inzidenten Kanten zum Knoten pVertex. Hat der Knoten
|
||||||
|
* pVertex keine inzidenten Kanten in diesem Graphen oder ist gar nicht in diesem Graphen enthalten, so
|
||||||
|
* wird eine leere Liste zurueckgeliefert.
|
||||||
|
*/
|
||||||
|
public List<Edge> getEdges( Vertex pVertex){
|
||||||
|
List<Edge> result = new List<Edge>();
|
||||||
|
|
||||||
|
//Alle Kanten durchlaufen.
|
||||||
|
edges.toFirst();
|
||||||
|
while (edges.hasAccess()){
|
||||||
|
|
||||||
|
//Wenn ein Knoten der Kante pVertex ist, dann Kante als inzidente Kante in die Ergebnisliste einfuegen.
|
||||||
|
Vertex[] vertexPair = edges.getContent().getVertices();
|
||||||
|
if (vertexPair[0] == pVertex) {
|
||||||
|
result.append(edges.getContent());
|
||||||
|
} else{
|
||||||
|
if (vertexPair[1] == pVertex){
|
||||||
|
result.append(edges.getContent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
edges.next();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert die Kante, welche die Knoten pVertex und pAnotherVertex verbindet,
|
||||||
|
* als Objekt vom Typ Edge. Ist der Knoten pVertex oder der Knoten pAnotherVertex nicht
|
||||||
|
* im Graphen enthalten oder gibt es keine Kante, die beide Knoten verbindet, so wird null
|
||||||
|
* zurueckgeliefert.
|
||||||
|
*/
|
||||||
|
public Edge getEdge( Vertex pVertex, Vertex pAnotherVertex){
|
||||||
|
Edge result = null;
|
||||||
|
|
||||||
|
//Kanten durchsuchen, solange keine passende gefunden wurde.
|
||||||
|
edges.toFirst();
|
||||||
|
while (edges.hasAccess() && result == null){
|
||||||
|
|
||||||
|
//Pruefen, ob die Kante pVertex und pAnotherVertex verbindet.
|
||||||
|
Vertex[] vertexPair = edges.getContent().getVertices();
|
||||||
|
if ((vertexPair[0] == pVertex && vertexPair[1] == pAnotherVertex) ||
|
||||||
|
(vertexPair[0] == pAnotherVertex && vertexPair[1] == pVertex)) {
|
||||||
|
//Kante als Ergebnis merken.
|
||||||
|
result = edges.getContent();
|
||||||
|
}
|
||||||
|
edges.next();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert true, wenn der Graph keine Knoten enthaelt, ansonsten false.
|
||||||
|
*/
|
||||||
|
public boolean isEmpty(){
|
||||||
|
return vertices.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
347
src/test/java/schule/ngb/zm/util/abi/List.java
Executable file
347
src/test/java/schule/ngb/zm/util/abi/List.java
Executable file
@@ -0,0 +1,347 @@
|
|||||||
|
package schule.ngb.zm.util.abi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Materialien zu den zentralen NRW-Abiturpruefungen im Fach Informatik ab 2018
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Generische Klasse List<ContentType>
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Objekt der generischen Klasse List verwalten beliebig viele linear
|
||||||
|
* angeordnete Objekte vom Typ ContentType. Auf hoechstens ein Listenobjekt,
|
||||||
|
* aktuellesObjekt genannt, kann jeweils zugegriffen werden.<br />
|
||||||
|
* Wenn eine Liste leer ist, vollstaendig durchlaufen wurde oder das aktuelle
|
||||||
|
* Objekt am Ende der Liste geloescht wurde, gibt es kein aktuelles Objekt.<br />
|
||||||
|
* Das erste oder das letzte Objekt einer Liste koennen durch einen Auftrag zum
|
||||||
|
* aktuellen Objekt gemacht werden. Ausserdem kann das dem aktuellen Objekt
|
||||||
|
* folgende Listenobjekt zum neuen aktuellen Objekt werden. <br />
|
||||||
|
* Das aktuelle Objekt kann gelesen, veraendert oder geloescht werden. Ausserdem
|
||||||
|
* kann vor dem aktuellen Objekt ein Listenobjekt eingefuegt werden.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author Qualitaets- und UnterstuetzungsAgentur - Landesinstitut fuer Schule
|
||||||
|
* @version Generisch_06 2015-10-25
|
||||||
|
*/
|
||||||
|
public class List<ContentType> {
|
||||||
|
|
||||||
|
/* --------- Anfang der privaten inneren Klasse -------------- */
|
||||||
|
|
||||||
|
private class ListNode {
|
||||||
|
|
||||||
|
private ContentType contentObject;
|
||||||
|
private ListNode next;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ein neues Objekt wird erschaffen. Der Verweis ist leer.
|
||||||
|
*
|
||||||
|
* @param pContent das Inhaltsobjekt vom Typ ContentType
|
||||||
|
*/
|
||||||
|
private ListNode(ContentType pContent) {
|
||||||
|
contentObject = pContent;
|
||||||
|
next = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der Inhalt des Knotens wird zurueckgeliefert.
|
||||||
|
*
|
||||||
|
* @return das Inhaltsobjekt des Knotens
|
||||||
|
*/
|
||||||
|
public ContentType getContentObject() {
|
||||||
|
return contentObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der Inhalt dieses Kontens wird gesetzt.
|
||||||
|
*
|
||||||
|
* @param pContent das Inhaltsobjekt vom Typ ContentType
|
||||||
|
*/
|
||||||
|
public void setContentObject(ContentType pContent) {
|
||||||
|
contentObject = pContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der Nachfolgeknoten wird zurueckgeliefert.
|
||||||
|
*
|
||||||
|
* @return das Objekt, auf das der aktuelle Verweis zeigt
|
||||||
|
*/
|
||||||
|
public ListNode getNextNode() {
|
||||||
|
return this.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der Verweis wird auf das Objekt, das als Parameter uebergeben
|
||||||
|
* wird, gesetzt.
|
||||||
|
*
|
||||||
|
* @param pNext der Nachfolger des Knotens
|
||||||
|
*/
|
||||||
|
public void setNextNode(ListNode pNext) {
|
||||||
|
this.next = pNext;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------- Ende der privaten inneren Klasse -------------- */
|
||||||
|
|
||||||
|
// erstes Element der Liste
|
||||||
|
ListNode first;
|
||||||
|
|
||||||
|
// letztes Element der Liste
|
||||||
|
ListNode last;
|
||||||
|
|
||||||
|
// aktuelles Element der Liste
|
||||||
|
ListNode current;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Eine leere Liste wird erzeugt.
|
||||||
|
*/
|
||||||
|
public List() {
|
||||||
|
first = null;
|
||||||
|
last = null;
|
||||||
|
current = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert den Wert true, wenn die Liste keine Objekte enthaelt,
|
||||||
|
* sonst liefert sie den Wert false.
|
||||||
|
*
|
||||||
|
* @return true, wenn die Liste leer ist, sonst false
|
||||||
|
*/
|
||||||
|
public boolean isEmpty() {
|
||||||
|
// Die Liste ist leer, wenn es kein erstes Element gibt.
|
||||||
|
return first == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert den Wert true, wenn es ein aktuelles Objekt gibt,
|
||||||
|
* sonst liefert sie den Wert false.
|
||||||
|
*
|
||||||
|
* @return true, falls Zugriff moeglich, sonst false
|
||||||
|
*/
|
||||||
|
public boolean hasAccess() {
|
||||||
|
// Es gibt keinen Zugriff, wenn current auf kein Element verweist.
|
||||||
|
return current != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Falls die Liste nicht leer ist, es ein aktuelles Objekt gibt und dieses
|
||||||
|
* nicht das letzte Objekt der Liste ist, wird das dem aktuellen Objekt in
|
||||||
|
* der Liste folgende Objekt zum aktuellen Objekt, andernfalls gibt es nach
|
||||||
|
* Ausfuehrung des Auftrags kein aktuelles Objekt, d.h. hasAccess() liefert
|
||||||
|
* den Wert false.
|
||||||
|
*/
|
||||||
|
public void next() {
|
||||||
|
if (this.hasAccess()) {
|
||||||
|
current = current.getNextNode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Falls die Liste nicht leer ist, wird das erste Objekt der Liste aktuelles
|
||||||
|
* Objekt. Ist die Liste leer, geschieht nichts.
|
||||||
|
*/
|
||||||
|
public void toFirst() {
|
||||||
|
if (!isEmpty()) {
|
||||||
|
current = first;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Falls die Liste nicht leer ist, wird das letzte Objekt der Liste
|
||||||
|
* aktuelles Objekt. Ist die Liste leer, geschieht nichts.
|
||||||
|
*/
|
||||||
|
public void toLast() {
|
||||||
|
if (!isEmpty()) {
|
||||||
|
current = last;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Falls es ein aktuelles Objekt gibt (hasAccess() == true), wird das
|
||||||
|
* aktuelle Objekt zurueckgegeben, andernfalls (hasAccess() == false) gibt
|
||||||
|
* die Anfrage den Wert null zurueck.
|
||||||
|
*
|
||||||
|
* @return das aktuelle Objekt (vom Typ ContentType) oder null, wenn es
|
||||||
|
* kein aktuelles Objekt gibt
|
||||||
|
*/
|
||||||
|
public ContentType getContent() {
|
||||||
|
if (this.hasAccess()) {
|
||||||
|
return current.getContentObject();
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Falls es ein aktuelles Objekt gibt (hasAccess() == true) und pContent
|
||||||
|
* ungleich null ist, wird das aktuelle Objekt durch pContent ersetzt. Sonst
|
||||||
|
* geschieht nichts.
|
||||||
|
*
|
||||||
|
* @param pContent
|
||||||
|
* das zu schreibende Objekt vom Typ ContentType
|
||||||
|
*/
|
||||||
|
public void setContent(ContentType pContent) {
|
||||||
|
// Nichts tun, wenn es keinen Inhalt oder kein aktuelles Element gibt.
|
||||||
|
if (pContent != null && this.hasAccess()) {
|
||||||
|
current.setContentObject(pContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Falls es ein aktuelles Objekt gibt (hasAccess() == true), wird ein neues
|
||||||
|
* Objekt vor dem aktuellen Objekt in die Liste eingefuegt. Das aktuelle
|
||||||
|
* Objekt bleibt unveraendert. <br />
|
||||||
|
* Wenn die Liste leer ist, wird pContent in die Liste eingefuegt und es
|
||||||
|
* gibt weiterhin kein aktuelles Objekt (hasAccess() == false). <br />
|
||||||
|
* Falls es kein aktuelles Objekt gibt (hasAccess() == false) und die Liste
|
||||||
|
* nicht leer ist oder pContent gleich null ist, geschieht nichts.
|
||||||
|
*
|
||||||
|
* @param pContent
|
||||||
|
* das einzufuegende Objekt vom Typ ContentType
|
||||||
|
*/
|
||||||
|
public void insert(ContentType pContent) {
|
||||||
|
if (pContent != null) { // Nichts tun, wenn es keinen Inhalt gibt.
|
||||||
|
if (this.hasAccess()) { // Fall: Es gibt ein aktuelles Element.
|
||||||
|
|
||||||
|
// Neuen Knoten erstellen.
|
||||||
|
ListNode newNode = new ListNode(pContent);
|
||||||
|
|
||||||
|
if (current != first) { // Fall: Nicht an erster Stelle einfuegen.
|
||||||
|
ListNode previous = this.getPrevious(current);
|
||||||
|
newNode.setNextNode(previous.getNextNode());
|
||||||
|
previous.setNextNode(newNode);
|
||||||
|
} else { // Fall: An erster Stelle einfuegen.
|
||||||
|
newNode.setNextNode(first);
|
||||||
|
first = newNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else { //Fall: Es gibt kein aktuelles Element.
|
||||||
|
|
||||||
|
if (this.isEmpty()) { // Fall: In leere Liste einfuegen.
|
||||||
|
|
||||||
|
// Neuen Knoten erstellen.
|
||||||
|
ListNode newNode = new ListNode(pContent);
|
||||||
|
|
||||||
|
first = newNode;
|
||||||
|
last = newNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Falls pContent gleich null ist, geschieht nichts.<br />
|
||||||
|
* Ansonsten wird ein neues Objekt pContent am Ende der Liste eingefuegt.
|
||||||
|
* Das aktuelle Objekt bleibt unveraendert. <br />
|
||||||
|
* Wenn die Liste leer ist, wird das Objekt pContent in die Liste eingefuegt
|
||||||
|
* und es gibt weiterhin kein aktuelles Objekt (hasAccess() == false).
|
||||||
|
*
|
||||||
|
* @param pContent
|
||||||
|
* das anzuhaengende Objekt vom Typ ContentType
|
||||||
|
*/
|
||||||
|
public void append(ContentType pContent) {
|
||||||
|
if (pContent != null) { // Nichts tun, wenn es keine Inhalt gibt.
|
||||||
|
|
||||||
|
if (this.isEmpty()) { // Fall: An leere Liste anfuegen.
|
||||||
|
this.insert(pContent);
|
||||||
|
} else { // Fall: An nicht-leere Liste anfuegen.
|
||||||
|
|
||||||
|
// Neuen Knoten erstellen.
|
||||||
|
ListNode newNode = new ListNode(pContent);
|
||||||
|
|
||||||
|
last.setNextNode(newNode);
|
||||||
|
last = newNode; // Letzten Knoten aktualisieren.
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Falls es sich bei der Liste und pList um dasselbe Objekt handelt,
|
||||||
|
* pList null oder eine leere Liste ist, geschieht nichts.<br />
|
||||||
|
* Ansonsten wird die Liste pList an die aktuelle Liste angehaengt.
|
||||||
|
* Anschliessend wird pList eine leere Liste. Das aktuelle Objekt bleibt
|
||||||
|
* unveraendert. Insbesondere bleibt hasAccess identisch.
|
||||||
|
*
|
||||||
|
* @param pList
|
||||||
|
* die am Ende anzuhaengende Liste vom Typ List<ContentType>
|
||||||
|
*/
|
||||||
|
public void concat(List<ContentType> pList) {
|
||||||
|
if (pList != this && pList != null && !pList.isEmpty()) { // Nichts tun,
|
||||||
|
// wenn pList und this identisch, pList leer oder nicht existent.
|
||||||
|
|
||||||
|
if (this.isEmpty()) { // Fall: An leere Liste anfuegen.
|
||||||
|
this.first = pList.first;
|
||||||
|
this.last = pList.last;
|
||||||
|
} else { // Fall: An nicht-leere Liste anfuegen.
|
||||||
|
this.last.setNextNode(pList.first);
|
||||||
|
this.last = pList.last;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Liste pList loeschen.
|
||||||
|
pList.first = null;
|
||||||
|
pList.last = null;
|
||||||
|
pList.current = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wenn die Liste leer ist oder es kein aktuelles Objekt gibt (hasAccess()
|
||||||
|
* == false), geschieht nichts.<br />
|
||||||
|
* Falls es ein aktuelles Objekt gibt (hasAccess() == true), wird das
|
||||||
|
* aktuelle Objekt geloescht und das Objekt hinter dem geloeschten Objekt
|
||||||
|
* wird zum aktuellen Objekt. <br />
|
||||||
|
* Wird das Objekt, das am Ende der Liste steht, geloescht, gibt es kein
|
||||||
|
* aktuelles Objekt mehr.
|
||||||
|
*/
|
||||||
|
public void remove() {
|
||||||
|
// Nichts tun, wenn es kein aktuelle Element gibt oder die Liste leer ist.
|
||||||
|
if (this.hasAccess() && !this.isEmpty()) {
|
||||||
|
|
||||||
|
if (current == first) {
|
||||||
|
first = first.getNextNode();
|
||||||
|
} else {
|
||||||
|
ListNode previous = this.getPrevious(current);
|
||||||
|
if (current == last) {
|
||||||
|
last = previous;
|
||||||
|
}
|
||||||
|
previous.setNextNode(current.getNextNode());
|
||||||
|
}
|
||||||
|
|
||||||
|
ListNode temp = current.getNextNode();
|
||||||
|
current.setContentObject(null);
|
||||||
|
current.setNextNode(null);
|
||||||
|
current = temp;
|
||||||
|
|
||||||
|
//Beim loeschen des letzten Elements last auf null setzen.
|
||||||
|
if (this.isEmpty()) {
|
||||||
|
last = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Liefert den Vorgaengerknoten des Knotens pNode. Ist die Liste leer, pNode
|
||||||
|
* == null, pNode nicht in der Liste oder pNode der erste Knoten der Liste,
|
||||||
|
* wird null zurueckgegeben.
|
||||||
|
*
|
||||||
|
* @param pNode
|
||||||
|
* der Knoten, dessen Vorgaenger zurueckgegeben werden soll
|
||||||
|
* @return der Vorgaenger des Knotens pNode oder null, falls die Liste leer ist,
|
||||||
|
* pNode == null ist, pNode nicht in der Liste ist oder pNode der erste Knoten
|
||||||
|
* der Liste ist
|
||||||
|
*/
|
||||||
|
private ListNode getPrevious(ListNode pNode) {
|
||||||
|
if (pNode != null && pNode != first && !this.isEmpty()) {
|
||||||
|
ListNode temp = first;
|
||||||
|
while (temp != null && temp.getNextNode() != pNode) {
|
||||||
|
temp = temp.getNextNode();
|
||||||
|
}
|
||||||
|
return temp;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
78
src/test/java/schule/ngb/zm/util/abi/QueryResult.java
Executable file
78
src/test/java/schule/ngb/zm/util/abi/QueryResult.java
Executable file
@@ -0,0 +1,78 @@
|
|||||||
|
package schule.ngb.zm.util.abi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Materialien zu den zentralen NRW-Abiturpruefungen im Fach Informatik ab 2018
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Klasse QueryResult
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Ein Objekt der Klasse QueryResult stellt die Ergebnistabelle einer Datenbankanfrage mit Hilfe
|
||||||
|
* der Klasse DatabaseConnector dar. Objekte dieser Klasse werden nur von der Klasse DatabaseConnector erstellt.
|
||||||
|
* Die Klasse verfuegt ueber keinen oeffentlichen Konstruktor.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author Qualitaets- und UnterstuetzungsAgentur - Landesinstitut fuer Schule
|
||||||
|
* @version 2015-01-31
|
||||||
|
*/
|
||||||
|
public class QueryResult{
|
||||||
|
private String[][] data;
|
||||||
|
private String[] columnNames;
|
||||||
|
private String[] columnTypes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Paketinterner Konstruktor.
|
||||||
|
*/
|
||||||
|
QueryResult(String[][] pData, String[] pColumnNames, String[] pColumnTypes){
|
||||||
|
data = pData;
|
||||||
|
columnNames = pColumnNames;
|
||||||
|
columnTypes = pColumnTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert die Eintraege der Ergebnistabelle als zweidimensionales Feld
|
||||||
|
* vom Typ String. Der erste Index des Feldes stellt die Zeile und der zweite die
|
||||||
|
* Spalte dar (d.h. Object[zeile][spalte]).
|
||||||
|
*/
|
||||||
|
public String[][] getData(){
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert die Bezeichner der Spalten der Ergebnistabelle als Feld vom
|
||||||
|
* Typ String zurueck.
|
||||||
|
*/
|
||||||
|
public String[] getColumnNames(){
|
||||||
|
return columnNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert die Typenbezeichnung der Spalten der Ergebnistabelle als Feld
|
||||||
|
* vom Typ String zurueck. Die Bezeichnungen entsprechen den Angaben in der MySQL-Datenbank.
|
||||||
|
*/
|
||||||
|
public String[] getColumnTypes(){
|
||||||
|
return columnTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert die Anzahl der Zeilen der Ergebnistabelle als Integer.
|
||||||
|
*/
|
||||||
|
public int getRowCount(){
|
||||||
|
if (data != null )
|
||||||
|
return data.length;
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert die Anzahl der Spalten der Ergebnistabelle als Integer.
|
||||||
|
*/
|
||||||
|
public int getColumnCount(){
|
||||||
|
if (data != null && data.length > 0 && data[0] != null)
|
||||||
|
return data[0].length;
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
144
src/test/java/schule/ngb/zm/util/abi/Queue.java
Executable file
144
src/test/java/schule/ngb/zm/util/abi/Queue.java
Executable file
@@ -0,0 +1,144 @@
|
|||||||
|
package schule.ngb.zm.util.abi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Materialien zu den zentralen NRW-Abiturpruefungen im Fach Informatik ab 2018
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Generische Klasse Queue<ContentType>
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Objekte der generischen Klasse Queue (Warteschlange) verwalten beliebige
|
||||||
|
* Objekte vom Typ ContentType nach dem First-In-First-Out-Prinzip, d.h., das
|
||||||
|
* zuerst abgelegte Objekt wird als erstes wieder entnommen. Alle Methoden haben
|
||||||
|
* eine konstante Laufzeit, unabhaengig von der Anzahl der verwalteten Objekte.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author Qualitaets- und UnterstuetzungsAgentur - Landesinstitut fuer Schule
|
||||||
|
* @version Generisch_02 2014-02-21
|
||||||
|
*/
|
||||||
|
public class Queue<ContentType> {
|
||||||
|
|
||||||
|
/* --------- Anfang der privaten inneren Klasse -------------- */
|
||||||
|
|
||||||
|
private class QueueNode {
|
||||||
|
|
||||||
|
private ContentType content = null;
|
||||||
|
private QueueNode nextNode = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ein neues Objekt vom Typ QueueNode<ContentType> wird erschaffen.
|
||||||
|
* Der Inhalt wird per Parameter gesetzt. Der Verweis ist leer.
|
||||||
|
*
|
||||||
|
* @param pContent das Inhaltselement des Knotens vom Typ ContentType
|
||||||
|
*/
|
||||||
|
public QueueNode(ContentType pContent) {
|
||||||
|
content = pContent;
|
||||||
|
nextNode = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der Verweis wird auf das Objekt, das als Parameter uebergeben wird,
|
||||||
|
* gesetzt.
|
||||||
|
*
|
||||||
|
* @param pNext der Nachfolger des Knotens
|
||||||
|
*/
|
||||||
|
public void setNext(QueueNode pNext) {
|
||||||
|
nextNode = pNext;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Liefert das naechste Element des aktuellen Knotens.
|
||||||
|
*
|
||||||
|
* @return das Objekt vom Typ QueueNode, auf das der aktuelle Verweis zeigt
|
||||||
|
*/
|
||||||
|
public QueueNode getNext() {
|
||||||
|
return nextNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Liefert das Inhaltsobjekt des Knotens vom Typ ContentType.
|
||||||
|
*
|
||||||
|
* @return das Inhaltsobjekt des Knotens
|
||||||
|
*/
|
||||||
|
public ContentType getContent() {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------- Ende der privaten inneren Klasse -------------- */
|
||||||
|
|
||||||
|
private QueueNode head;
|
||||||
|
private QueueNode tail;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Eine leere Schlange wird erzeugt.
|
||||||
|
* Objekte, die in dieser Schlange verwaltet werden, muessen vom Typ
|
||||||
|
* ContentType sein.
|
||||||
|
*/
|
||||||
|
public Queue() {
|
||||||
|
head = null;
|
||||||
|
tail = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert den Wert true, wenn die Schlange keine Objekte enthaelt,
|
||||||
|
* sonst liefert sie den Wert false.
|
||||||
|
*
|
||||||
|
* @return true, falls die Schlange leer ist, sonst false
|
||||||
|
*/
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return head == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Das Objekt pContentType wird an die Schlange angehaengt.
|
||||||
|
* Falls pContentType gleich null ist, bleibt die Schlange unveraendert.
|
||||||
|
*
|
||||||
|
* @param pContent
|
||||||
|
* das anzuhaengende Objekt vom Typ ContentType
|
||||||
|
*/
|
||||||
|
public void enqueue(ContentType pContent) {
|
||||||
|
if (pContent != null) {
|
||||||
|
QueueNode newNode = new QueueNode(pContent);
|
||||||
|
if (this.isEmpty()) {
|
||||||
|
head = newNode;
|
||||||
|
tail = newNode;
|
||||||
|
} else {
|
||||||
|
tail.setNext(newNode);
|
||||||
|
tail = newNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Das erste Objekt wird aus der Schlange entfernt.
|
||||||
|
* Falls die Schlange leer ist, wird sie nicht veraendert.
|
||||||
|
*/
|
||||||
|
public void dequeue() {
|
||||||
|
if (!this.isEmpty()) {
|
||||||
|
head = head.getNext();
|
||||||
|
if (this.isEmpty()) {
|
||||||
|
head = null;
|
||||||
|
tail = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert das erste Objekt der Schlange.
|
||||||
|
* Die Schlange bleibt unveraendert.
|
||||||
|
* Falls die Schlange leer ist, wird null zurueckgegeben.
|
||||||
|
*
|
||||||
|
* @return das erste Objekt der Schlange vom Typ ContentType oder null,
|
||||||
|
* falls die Schlange leer ist
|
||||||
|
*/
|
||||||
|
public ContentType front() {
|
||||||
|
if (this.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return head.getContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
359
src/test/java/schule/ngb/zm/util/abi/Server.java
Executable file
359
src/test/java/schule/ngb/zm/util/abi/Server.java
Executable file
@@ -0,0 +1,359 @@
|
|||||||
|
package schule.ngb.zm.util.abi; /**
|
||||||
|
* <p>
|
||||||
|
* Materialien zu den zentralen NRW-Abiturpruefungen im Fach Informatik ab 2018
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Klasse Server
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Objekte von Unterklassen der abstrakten Klasse Server ermoeglichen das
|
||||||
|
* Anbieten von Serverdiensten, so dass Clients Verbindungen zum Server mittels
|
||||||
|
* TCP/IP-Protokoll aufbauen koennen. Zur Vereinfachung finden Nachrichtenversand
|
||||||
|
* und -empfang zeilenweise statt, d. h., beim Senden einer Zeichenkette wird ein
|
||||||
|
* Zeilentrenner ergaenzt und beim Empfang wird dieser entfernt.
|
||||||
|
* Verbindungsannahme, Nachrichtenempfang und Verbindungsende geschehen
|
||||||
|
* nebenlaeufig. Auf diese Ereignisse muss durch Ueberschreiben der entsprechenden
|
||||||
|
* Ereignisbehandlungsmethoden reagiert werden. Es findet nur eine rudimentaere
|
||||||
|
* Fehlerbehandlung statt, so dass z.B. Verbindungsabbrueche nicht zu einem
|
||||||
|
* Programmabbruch fuehren. Einmal unterbrochene oder getrennte Verbindungen
|
||||||
|
* koennen nicht reaktiviert werden.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author Qualitaets- und UnterstuetzungsAgentur - Landesinstitut fuer Schule
|
||||||
|
* @version 30.08.2016
|
||||||
|
*/
|
||||||
|
import java.net.*;
|
||||||
|
import java.io.*;
|
||||||
|
|
||||||
|
public abstract class Server
|
||||||
|
{
|
||||||
|
private NewConnectionHandler connectionHandler;
|
||||||
|
private List<ClientMessageHandler> messageHandlers;
|
||||||
|
|
||||||
|
private class NewConnectionHandler extends Thread
|
||||||
|
{
|
||||||
|
private ServerSocket serverSocket;
|
||||||
|
private boolean active;
|
||||||
|
|
||||||
|
public NewConnectionHandler(int pPort)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
serverSocket = new ServerSocket(pPort);
|
||||||
|
start();
|
||||||
|
active = true;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
serverSocket = null;
|
||||||
|
active = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
while (active)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//Warten auf Verbdinungsversuch durch Client:
|
||||||
|
Socket clientSocket = serverSocket.accept();
|
||||||
|
// Eingehende Nachrichten vom neu verbundenen Client werden
|
||||||
|
// in einem eigenen Thread empfangen:
|
||||||
|
addNewClientMessageHandler(clientSocket);
|
||||||
|
processNewConnection(clientSocket.getInetAddress().getHostAddress(),clientSocket.getPort());
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Kann keine Verbindung zum anfragenden Client aufgebaut werden,
|
||||||
|
* geschieht nichts.
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close()
|
||||||
|
{
|
||||||
|
active = false;
|
||||||
|
if(serverSocket != null)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
serverSocket.close();
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Befindet sich der ServerSocket im accept()-Wartezustand oder wurde
|
||||||
|
* er bereits geschlossen, geschieht nichts.
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ClientMessageHandler extends Thread
|
||||||
|
{
|
||||||
|
private ClientSocketWrapper socketWrapper;
|
||||||
|
private boolean active;
|
||||||
|
|
||||||
|
private class ClientSocketWrapper
|
||||||
|
{
|
||||||
|
private Socket clientSocket;
|
||||||
|
private BufferedReader fromClient;
|
||||||
|
private PrintWriter toClient;
|
||||||
|
|
||||||
|
public ClientSocketWrapper(Socket pSocket)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
clientSocket = pSocket;
|
||||||
|
toClient = new PrintWriter(clientSocket.getOutputStream(), true);
|
||||||
|
fromClient = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
clientSocket = null;
|
||||||
|
toClient = null;
|
||||||
|
fromClient = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String receive()
|
||||||
|
{
|
||||||
|
if(fromClient != null)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return fromClient.readLine();
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
return(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void send(String pMessage)
|
||||||
|
{
|
||||||
|
if(toClient != null)
|
||||||
|
{
|
||||||
|
toClient.println(pMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClientIP()
|
||||||
|
{
|
||||||
|
if(clientSocket != null)
|
||||||
|
return(clientSocket.getInetAddress().getHostAddress());
|
||||||
|
else
|
||||||
|
return(null); //Gemaess Java-API Rueckgabe bei nicht-verbundenen Sockets
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getClientPort()
|
||||||
|
{
|
||||||
|
if(clientSocket != null)
|
||||||
|
return(clientSocket.getPort());
|
||||||
|
else
|
||||||
|
return(0); //Gemaess Java-API Rueckgabe bei nicht-verbundenen Sockets
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close()
|
||||||
|
{
|
||||||
|
if(clientSocket != null)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
clientSocket.close();
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Falls eine Verbindung getrennt werden soll, deren Endpunkt
|
||||||
|
* nicht mehr existiert bzw. ihrerseits bereits beendet worden ist,
|
||||||
|
* geschieht nichts.
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ClientMessageHandler(Socket pClientSocket)
|
||||||
|
{
|
||||||
|
socketWrapper = new ClientSocketWrapper(pClientSocket);
|
||||||
|
if(pClientSocket!=null)
|
||||||
|
{
|
||||||
|
start();
|
||||||
|
active = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
active = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
String message = null;
|
||||||
|
while (active)
|
||||||
|
{
|
||||||
|
message = socketWrapper.receive();
|
||||||
|
if (message != null)
|
||||||
|
processMessage(socketWrapper.getClientIP(), socketWrapper.getClientPort(), message);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ClientMessageHandler aMessageHandler = findClientMessageHandler(socketWrapper.getClientIP(), socketWrapper.getClientPort());
|
||||||
|
if (aMessageHandler != null)
|
||||||
|
{
|
||||||
|
aMessageHandler.close();
|
||||||
|
removeClientMessageHandler(aMessageHandler);
|
||||||
|
processClosingConnection(socketWrapper.getClientIP(), socketWrapper.getClientPort());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void send(String pMessage)
|
||||||
|
{
|
||||||
|
if(active)
|
||||||
|
socketWrapper.send(pMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close()
|
||||||
|
{
|
||||||
|
if(active)
|
||||||
|
{
|
||||||
|
active=false;
|
||||||
|
socketWrapper.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClientIP()
|
||||||
|
{
|
||||||
|
return(socketWrapper.getClientIP());
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getClientPort()
|
||||||
|
{
|
||||||
|
return(socketWrapper.getClientPort());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public Server(int pPort)
|
||||||
|
{
|
||||||
|
connectionHandler = new NewConnectionHandler(pPort);
|
||||||
|
messageHandlers = new List<ClientMessageHandler>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isOpen()
|
||||||
|
{
|
||||||
|
return(connectionHandler.active);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isConnectedTo(String pClientIP, int pClientPort)
|
||||||
|
{
|
||||||
|
ClientMessageHandler aMessageHandler = findClientMessageHandler(pClientIP, pClientPort);
|
||||||
|
if (aMessageHandler != null)
|
||||||
|
return(aMessageHandler.active);
|
||||||
|
else
|
||||||
|
return(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void send(String pClientIP, int pClientPort, String pMessage)
|
||||||
|
{
|
||||||
|
ClientMessageHandler aMessageHandler = this.findClientMessageHandler(pClientIP, pClientPort);
|
||||||
|
if (aMessageHandler != null)
|
||||||
|
aMessageHandler.send(pMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendToAll(String pMessage)
|
||||||
|
{
|
||||||
|
synchronized(messageHandlers)
|
||||||
|
{
|
||||||
|
messageHandlers.toFirst();
|
||||||
|
while (messageHandlers.hasAccess())
|
||||||
|
{
|
||||||
|
messageHandlers.getContent().send(pMessage);
|
||||||
|
messageHandlers.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void closeConnection(String pClientIP, int pClientPort)
|
||||||
|
{
|
||||||
|
ClientMessageHandler aMessageHandler = findClientMessageHandler(pClientIP, pClientPort);
|
||||||
|
if (aMessageHandler != null)
|
||||||
|
{
|
||||||
|
processClosingConnection(pClientIP, pClientPort);
|
||||||
|
aMessageHandler.close();
|
||||||
|
removeClientMessageHandler(aMessageHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close()
|
||||||
|
{
|
||||||
|
connectionHandler.close();
|
||||||
|
|
||||||
|
synchronized(messageHandlers)
|
||||||
|
{
|
||||||
|
ClientMessageHandler aMessageHandler;
|
||||||
|
messageHandlers.toFirst();
|
||||||
|
while (messageHandlers.hasAccess())
|
||||||
|
{
|
||||||
|
aMessageHandler = messageHandlers.getContent();
|
||||||
|
processClosingConnection(aMessageHandler.getClientIP(), aMessageHandler.getClientPort());
|
||||||
|
aMessageHandler.close();
|
||||||
|
messageHandlers.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
public abstract void processNewConnection(String pClientIP, int pClientPort);
|
||||||
|
|
||||||
|
public abstract void processMessage(String pClientIP, int pClientPort, String pMessage);
|
||||||
|
|
||||||
|
public abstract void processClosingConnection(String pClientIP, int pClientPort);
|
||||||
|
|
||||||
|
private void addNewClientMessageHandler(Socket pClientSocket)
|
||||||
|
{
|
||||||
|
synchronized(messageHandlers)
|
||||||
|
{
|
||||||
|
messageHandlers.append(new ClientMessageHandler(pClientSocket));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeClientMessageHandler(ClientMessageHandler pClientMessageHandler)
|
||||||
|
{
|
||||||
|
synchronized(messageHandlers)
|
||||||
|
{
|
||||||
|
messageHandlers.toFirst();
|
||||||
|
while (messageHandlers.hasAccess())
|
||||||
|
{
|
||||||
|
if (pClientMessageHandler == messageHandlers.getContent())
|
||||||
|
{
|
||||||
|
messageHandlers.remove();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
messageHandlers.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ClientMessageHandler findClientMessageHandler(String pClientIP, int pClientPort)
|
||||||
|
{
|
||||||
|
synchronized(messageHandlers)
|
||||||
|
{
|
||||||
|
ClientMessageHandler aMessageHandler;
|
||||||
|
messageHandlers.toFirst();
|
||||||
|
|
||||||
|
while (messageHandlers.hasAccess())
|
||||||
|
{
|
||||||
|
aMessageHandler = messageHandlers.getContent();
|
||||||
|
if (aMessageHandler.getClientIP().equals(pClientIP) && aMessageHandler.getClientPort() == pClientPort)
|
||||||
|
return (aMessageHandler);
|
||||||
|
messageHandlers.next();
|
||||||
|
}
|
||||||
|
return (null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
128
src/test/java/schule/ngb/zm/util/abi/Stack.java
Executable file
128
src/test/java/schule/ngb/zm/util/abi/Stack.java
Executable file
@@ -0,0 +1,128 @@
|
|||||||
|
package schule.ngb.zm.util.abi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Materialien zu den zentralen NRW-Abiturpruefungen im Fach Informatik ab 2018
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Generische Klasse Stack<ContentType>
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Objekte der generischen Klasse Stack (Keller, Stapel) verwalten beliebige
|
||||||
|
* Objekte vom Typ ContentType nach dem Last-In-First-Out-Prinzip, d.h., das
|
||||||
|
* zuletzt abgelegte Objekt wird als erstes wieder entnommen. Alle Methoden
|
||||||
|
* haben eine konstante Laufzeit, unabhaengig von der Anzahl der verwalteten
|
||||||
|
* Objekte.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author Qualitaets- und UnterstuetzungsAgentur - Landesinstitut fuer Schule
|
||||||
|
* @version Generisch_02 2014-02-21
|
||||||
|
*/
|
||||||
|
public class Stack<ContentType> {
|
||||||
|
|
||||||
|
/* --------- Anfang der privaten inneren Klasse -------------- */
|
||||||
|
|
||||||
|
private class StackNode {
|
||||||
|
|
||||||
|
private ContentType content = null;
|
||||||
|
private StackNode nextNode = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ein neues Objekt vom Typ StackNode<ContentType> wird erschaffen. <br />
|
||||||
|
* Der Inhalt wird per Parameter gesetzt. Der Verweis ist leer.
|
||||||
|
*
|
||||||
|
* @param pContent der Inhalt des Knotens
|
||||||
|
*/
|
||||||
|
public StackNode(ContentType pContent) {
|
||||||
|
content = pContent;
|
||||||
|
nextNode = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der Verweis wird auf das Objekt, das als Parameter uebergeben wird,
|
||||||
|
* gesetzt.
|
||||||
|
*
|
||||||
|
* @param pNext der Nachfolger des Knotens
|
||||||
|
*/
|
||||||
|
public void setNext(StackNode pNext) {
|
||||||
|
nextNode = pNext;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return das Objekt, auf das der aktuelle Verweis zeigt
|
||||||
|
*/
|
||||||
|
public StackNode getNext() {
|
||||||
|
return nextNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return das Inhaltsobjekt vom Typ ContentType
|
||||||
|
*/
|
||||||
|
public ContentType getContent() {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------- Ende der privaten inneren Klasse -------------- */
|
||||||
|
|
||||||
|
private StackNode head;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ein leerer Stapel wird erzeugt. Objekte, die in diesem Stapel verwaltet
|
||||||
|
* werden, muessen vom Typ ContentType sein.
|
||||||
|
*/
|
||||||
|
public Stack() {
|
||||||
|
head = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert den Wert true, wenn der Stapel keine Objekte
|
||||||
|
* enthaelt, sonst liefert sie den Wert false.
|
||||||
|
*
|
||||||
|
* @return true, falls der Stapel leer ist, sonst false
|
||||||
|
*/
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return (head == null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Das Objekt pContentType wird oben auf den Stapel gelegt. Falls
|
||||||
|
* pContentType gleich null ist, bleibt der Stapel unveraendert.
|
||||||
|
*
|
||||||
|
* @param pContent
|
||||||
|
* das einzufuegende Objekt vom Typ ContentType
|
||||||
|
*/
|
||||||
|
public void push(ContentType pContent) {
|
||||||
|
if (pContent != null) {
|
||||||
|
StackNode node = new StackNode(pContent);
|
||||||
|
node.setNext(head);
|
||||||
|
head = node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Das zuletzt eingefuegte Objekt wird von dem Stapel entfernt. Falls der
|
||||||
|
* Stapel leer ist, bleibt er unveraendert.
|
||||||
|
*/
|
||||||
|
public void pop() {
|
||||||
|
if (!isEmpty()) {
|
||||||
|
head = head.getNext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert das oberste Stapelobjekt. Der Stapel bleibt
|
||||||
|
* unveraendert. Falls der Stapel leer ist, wird null zurueckgegeben.
|
||||||
|
*
|
||||||
|
* @return das oberste Stackelement vom Typ ContentType oder null, falls
|
||||||
|
* der Stack leer ist
|
||||||
|
*/
|
||||||
|
public ContentType top() {
|
||||||
|
if (!this.isEmpty()) {
|
||||||
|
return head.getContent();
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
53
src/test/java/schule/ngb/zm/util/abi/Vertex.java
Executable file
53
src/test/java/schule/ngb/zm/util/abi/Vertex.java
Executable file
@@ -0,0 +1,53 @@
|
|||||||
|
package schule.ngb.zm.util.abi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Materialien zu den zentralen NRW-Abiturpruefungen im Fach Informatik ab 2018
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Klasse Vertex
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Die Klasse Vertex stellt einen einzelnen Knoten eines Graphen dar. Jedes Objekt
|
||||||
|
* dieser Klasse verfuegt ueber eine im Graphen eindeutige ID als String und kann diese
|
||||||
|
* ID zurueckliefern. Darueber hinaus kann eine Markierung gesetzt und abgefragt werden.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author Qualitaets- und UnterstuetzungsAgentur - Landesinstitut fuer Schule
|
||||||
|
* @version Oktober 2015
|
||||||
|
*/
|
||||||
|
public class Vertex{
|
||||||
|
//Einmalige ID des Knotens und Markierung
|
||||||
|
private String id;
|
||||||
|
private boolean mark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ein neues Objekt vom Typ Vertex wird erstellt. Seine Markierung hat den Wert false.
|
||||||
|
*/
|
||||||
|
public Vertex(String pID){
|
||||||
|
id = pID;
|
||||||
|
mark = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert die ID des Knotens als String.
|
||||||
|
*/
|
||||||
|
public String getID(){
|
||||||
|
return new String(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Der Auftrag setzt die Markierung des Knotens auf den Wert pMark.
|
||||||
|
*/
|
||||||
|
public void setMark(boolean pMark){
|
||||||
|
mark = pMark;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Anfrage liefert true, wenn die Markierung des Knotens den Wert true hat, ansonsten false.
|
||||||
|
*/
|
||||||
|
public boolean isMarked(){
|
||||||
|
return mark;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user