16 Commits

Author SHA1 Message Date
f0a3c65552 Gradle Task buildAll hinzugefügt 2022-12-11 21:50:10 +01:00
13cad69e1d Abi-NRW Klassen zum Testen eingefügt
Die Klassen werden von der Qualitäts- und UnterstützungsAgentur - Landesinstitut für Schule herausgegeben.
2022-12-11 17:32:21 +01:00
bd718ba27d Changelog 2022-12-11 16:34:45 +01:00
1895378978 Name der mp3spi jar angepasst 2022-12-11 16:34:41 +01:00
2f845bdcd9 Überschirft in overview.html angepasst 2022-12-11 16:34:30 +01:00
788ed888e9 Dokumentatione 2022-12-11 13:35:30 +01:00
ce3ffee4da Testklassen auf letzte Änderungen angepasst 2022-12-11 13:35:21 +01:00
74c85e0f61 Shape2DLayer ins Test-Paket verschoben 2022-12-11 13:35:03 +01:00
c7b2a520c4 Overview-Datei für Javadoc ergänzt 2022-12-11 13:34:40 +01:00
c5c046521b Doku und Formatierungen 2022-12-11 13:34:21 +01:00
9834b9c389 package-info.java ergänzt 2022-12-11 13:33:55 +01:00
eaaca6b90f Validator Methods erwarten nun einen Parameter
Die Methoden in `Validator` erwarten nun als zweiten Parameter den Namen des Parameters, der geprüft wird. Dadurch sollen die Methoden hilfreicher werden, indem geneuere Fehlermeldungen generiert werden können.
2022-12-11 13:33:19 +01:00
1275af55f3 Dokumentation random(int) 2022-12-10 14:43:59 +01:00
632038030e main Methode entfernt 2022-12-10 13:40:15 +01:00
cec17f0d7c choice Methoden wiederhergestellt 2022-12-10 13:33:00 +01:00
b29532bf6e Dokumentation 2022-12-10 13:32:45 +01:00
47 changed files with 3123 additions and 201 deletions

View File

@@ -6,6 +6,18 @@ und diese Projekt folgt [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
## [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
### Added

View File

@@ -33,16 +33,36 @@ dependencies {
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 {
useJUnitPlatform()
}
tasks.register('jarMP3SPI', Jar) {
archiveClassifier = 'all'
archiveClassifier = 'mp3spi'
duplicatesStrategy = 'exclude'
archivesBaseName = 'zeichenmaschine-mp3spi'
// archivesBaseName = 'zeichenmaschine-mp3spi'
from {
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
}
with jar
}
task buildAll {
dependsOn 'jar'
dependsOn 'jarMP3SPI'
dependsOn 'sourcesJar'
dependsOn 'javadocJar'
}

View File

@@ -0,0 +1,9 @@
<HTML>
<BODY>
Die Zeichenmaschine
<p>
<h1>Die Zeichenmaschine</h1>
@author J. Neugebauer
</BODY>
</HTML>

View File

@@ -1,9 +1,8 @@
package schule.ngb.zm;
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.io.ImageLoader;
import java.awt.Cursor;
import java.awt.Font;
@@ -24,7 +23,7 @@ import java.util.function.DoubleUnaryOperator;
* aktuell gehalten werden (beispielsweise {@link #runtime}).
* <p>
* 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.
* <pre><code>
* class MyClass extends Constants {
@@ -1256,7 +1255,7 @@ public class Constants {
public static final double distance( double fromX, double fromY, double toX, double toY ) {
double diffX = toX - fromX;
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.
* @return Eine Zufallszahl.
@@ -1332,7 +1332,7 @@ public class Constants {
/**
* Erzeugt eine ganze Pseudozufallsganzzahl zwischen {@code min} und
* {@code max}.
* {@code max} (einschließlich der Grenzen).
*
* @param min Untere Grenze.
* @param max Obere Grenze.
@@ -1395,9 +1395,9 @@ public class Constants {
* @param values Ein Array mit Werten, die zur Auswahl stehen.
* @return Ein zufälliges Element aus dem Array.
*/
/*public static final int choice( int... values ) {
public static final int choice( int... values ) {
return values[random(0, values.length - 1)];
}*/
}
/**
* 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.
* @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)];
}*/
}
/**
* Wählt ein zufälliges Element aus dem Array aus.
@@ -1416,6 +1416,7 @@ public class Constants {
* @param <T> Datentyp des Elements.
* @return Ein zufälliges Element aus dem Array.
*/
@SafeVarargs
public static final <T> T choice( T... values ) {
return values[random(0, values.length - 1)];
}

View File

@@ -201,6 +201,8 @@ public interface Strokeable extends Drawable {
* Kontureigenschaften zu erstellen. Der aktuelle {@code Stroke} wird
* zwischengespeichert.
*
* @param strokeType
* @param strokeWeight
* @return Ein {@code Stroke} mit den passenden Kontureigenschaften.
*/
static Stroke createStroke( Options.StrokeType strokeType, double strokeWeight ) {

View File

@@ -156,7 +156,7 @@ public class Zeichenfenster extends JFrame {
* @param displayDevice Das Anzeigegerät für das Fenster.
*/
public Zeichenfenster( Zeichenleinwand canvas, String title, GraphicsDevice displayDevice ) {
super(Validator.requireNotNull(displayDevice).getDefaultConfiguration());
super(Validator.requireNotNull(displayDevice, "displayDevice").getDefaultConfiguration());
this.displayDevice = displayDevice;
Validator.requireNotNull(canvas, "Every Zeichenfenster needs a Zeichenleinwand, but got <null>.");

View File

@@ -24,7 +24,7 @@ public abstract class Animation<T> extends Constants implements Updatable {
public Animation( DoubleUnaryOperator easing ) {
this.runtime = Constants.DEFAULT_ANIM_RUNTIME;
this.easing = Validator.requireNotNull(easing);
this.easing = Validator.requireNotNull(easing, "easing");
}
public Animation( int runtime ) {
@@ -34,7 +34,7 @@ public abstract class Animation<T> extends Constants implements Updatable {
public Animation( int runtime, DoubleUnaryOperator easing ) {
this.runtime = runtime;
this.easing = Validator.requireNotNull(easing);
this.easing = Validator.requireNotNull(easing, "easing");
}
public int getRuntime() {

View File

@@ -10,7 +10,7 @@ public class AnimationFacade<S> extends Animation<S> {
public AnimationFacade( Animation<S> anim, int runtime, DoubleUnaryOperator easing ) {
super(runtime, easing);
this.anim = Validator.requireNotNull(anim);
this.anim = Validator.requireNotNull(anim, "anim");
}
@Override

View File

@@ -9,7 +9,6 @@ public class AnimationGroup<T> extends Animation<T> {
List<Animation<T>> anims;
private boolean overrideEasing = false;
private int overrideRuntime = -1;

View File

@@ -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 ) {
Validator.requireNotNull(target);
Validator.requireNotNull(propSetter);
Validator.requireNotNull(target, "target");
Validator.requireNotNull(propSetter, "propSetter");
return play(target, runtime, easing, ( e ) -> propSetter.accept(Constants.interpolate(from, to, e)));
}

View 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;

View 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;

View File

@@ -84,7 +84,7 @@ public class Music implements Audio {
* @see ResourceStreamProvider#getResourceURL(String)
*/
public Music( String audioSource ) {
Validator.requireNotNull(audioSource);
Validator.requireNotNull(audioSource, "audioSource");
this.audioSource = audioSource;
}

View File

@@ -76,7 +76,7 @@ public class Sound implements Audio {
* @see ResourceStreamProvider#getResourceURL(String)
*/
public Sound( String source ) {
Validator.requireNotNull(source);
Validator.requireNotNull(source, "source");
this.audioSource = source;
}

View 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;

View 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;

View 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;

View File

@@ -4,13 +4,14 @@ import schule.ngb.zm.BasicDrawable;
import schule.ngb.zm.Fillable;
import schule.ngb.zm.Options;
import schule.ngb.zm.Strokeable;
import schule.ngb.zm.util.Validator;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
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>
* Alle Formen sind als Unterklassen von {@code Shape} implementiert.
* <p>
@@ -19,7 +20,6 @@ import java.awt.geom.Point2D;
* Parametern initialisiert und einen, der die Werte einer anderen Form
* desselben Typs übernimmt. In der Klasse {@link Circle} sind die Konstruktoren
* beispielsweise so implementiert:
*
* <pre><code>
* public Circle( double x, double y, double radius ) {
* super(x, y);
@@ -39,42 +39,42 @@ import java.awt.geom.Point2D;
public abstract class Shape extends BasicDrawable {
/**
* x-Koordinate der Form.
* Speichert die x-Koordinate der Form.
*/
protected double x;
/**
* y-Koordinate der Form.
* Speichert die y-Koordinate der Form.
*/
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;
/**
* Skalierungsfaktor.
* Speichert den Skalierungsfaktor.
*/
protected double scale = 1.0;
/**
* Ankerpunkt der Form.
* Speichert den Ankerpunkt.
*/
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() {
this(0.0, 0.0);
}
/**
* Setzt die x- und y-Koordinate der Form.
* Erstellt eine Form mit den angegebenen Koordinaten.
*
* @param x
* @param y
* @param x Die x-Koordinate.
* @param y Die y-Koordinate.
*/
public Shape( double x, double y ) {
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.
*/
@@ -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.
*/
@@ -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 ) {
this.y = y;
}
/**
* Gibt die Breite dieser Form zurück.
* Liefert die aktuelle Breite dieser Form.
* <p>
* Die Breite einer Form ist immer die Breite ihrer Begrenzung,
* <strong>bevor</strong> Drehungen und andere Transformationen auf sie
* angewandt wurden.
* <p>
* Die Begrenzungen der tatsächlich gezeichneten Form kann mit
* {@link #getBounds()} abgerufen werden.
* Die Begrenzungen der tatsächlich gezeichneten Form wird mit
* {@link #getBounds()} abgerufen.
*
* @return
* @return Die Breite der Form.
*/
public abstract double getWidth();
/**
* Gibt die Höhe dieser Form zurück.
* Liefert die aktuelle Höhe dieser Form.
* <p>
* Die Höhe einer Form ist immer die Höhe ihrer Begrenzung,
* <strong>bevor</strong> Drehungen und andere Transformationen auf sie
* angewandt wurden.
* <p>
* Die Begrenzungen der tatsächlich gezeichneten Form kann mit
* {@link #getBounds()} abgerufen werden.
* Die Begrenzungen der tatsächlich gezeichneten Form wird mit
* {@link #getBounds()} abgerufen.
*
* @return
* @return Die Höhe der Form.
*/
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.
*/
@@ -176,14 +163,25 @@ public abstract class Shape extends BasicDrawable {
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 ) {
Validator.requireNotNull(center, "center");
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 ) {
this.rotation += angle % 360;
@@ -198,6 +196,15 @@ public abstract class Shape extends BasicDrawable {
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.
*
@@ -207,34 +214,92 @@ public abstract class Shape extends BasicDrawable {
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 ) {
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 ) {
scale(scale * factor);
}
/**
* Liefert den aktuellen Ankerpunkt der Form.
*
* @return Der Ankerpunkt.
*/
public Options.Direction getAnchor() {
return anchor;
}
/**
* Setzt den Ankerpunkt der Form basierend auf der angegebenen
* {@link Options.Direction Richtung}.
* Setzt den Ankerpunkt der Form auf die angegebene Richtung.
* <p>
* Für das Setzen des Ankers muss das
* {@link #getBounds() begrenzende Rechteck} berechnet werden. Unterklassen
* sollten die Methode überschreiben, wenn der Anker auch direkt gesetzt
* werden kann.
* Jede Form hat einen Ankerpunkt, von dem aus sie gezeichnet wird. Jede
* {@link schule.ngb.zm.Options.Direction Richtung} beschreibt einen der
* Neun Ankerpunkte:
* <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 ) {
if( anchor != null ) {
Validator.requireNotNull(anchor, "anchor");
this.anchor = anchor;
}
}
/**
* Bestimmt die relativen Koordinaten des angegebenen Ankerpunkts basierend
@@ -242,7 +307,19 @@ public abstract class Shape extends BasicDrawable {
* <p>
* Die Koordinaten des Ankerpunktes werden relativ zur oberen linken Ecke
* 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 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
* {@link #setAnchor(Options.Direction) Ankerpunkt}.
* Bestimmt die Koordinaten des angegebenen Ankers der Form relativ zum
* aktuellen {@link #setAnchor(Options.Direction) Ankerpunkt}.
*
* @param anchor Die Richtung des Ankerpunktes.
* @param anchor Die Richtung des Ankers.
* @return Der relative Ankerpunkt.
* @see #getAnchorPoint(double, double, Options.Direction)
*/
public Point2D.Double getAnchorPoint( Options.Direction anchor ) {
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}.
* <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.
* @see #getAnchorPoint(double, double, Options.Direction)
*/
public Point2D.Double getAbsAnchorPoint( Options.Direction anchor ) {
// 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.
* <p>
* Unterklassen sollten diese Methode überschreiben, um weitere
* Eigenschaften zu kopieren (zum Beispiel den Radius eines Kreises).
* Unterklassen sollten immer mit dem Aufruf {@code super.copyFrom(shape)}
* die Basiseigenschaften kopieren.
* Unterklassen überschreiben diese Methode, um weitere Eigenschaften zu
* kopieren (zum Beispiel den Radius eines Kreises). Überschreibende
* Methoden sollten immer mit dem Aufruf {@code super.copyFrom(shape)} die
* Basiseigenschaften kopieren.
* <p>
* Die Methode sollte so viele Eigenschaften wie möglich von der anderen
* Form in diese kopieren. Wenn die andere Form einen anderen Typ hat, dann
* werden trotzdem die Basiseigenschaften (Konturlinie, Füllung, Position,
* Rotation, Skalierung, Sichtbarkeit und Ankerpunkt) in diese Form kopiert.
* Implementierende Unterklassen können soweit sinnvoll auch andere Werte
* übernehmen. Eine {@link Ellipse} kann beispielsweise auch die Breite und
* Höhe eines {@link Rectangle} übernehmen.
* Die Methode kopiert so viele Eigenschaften wie möglich von der
* angegebenen Form in diese. Wenn die andere Form einen anderen Typ hat,
* dann werden trotzdem die Basiseigenschaften (Konturlinie, Füllung,
* Position, Rotation, Skalierung, Sichtbarkeit und Ankerpunkt) in diese
* Form kopiert. Soweit sinnvoll übernehmen implementierende Unterklassen
* auch andere Werte. Eine {@link Ellipse} kopiert beispielsweise auch die
* 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 ) {
if( shape != null ) {
@@ -335,9 +424,9 @@ public abstract class Shape extends BasicDrawable {
* </code></pre>
* <p>
* 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
* besitzen, die alle Werte einer andern Form übernimmt. Die gezeigte
* Implementierung dürfte daher im Regelfall ausreichend sein.
* Form zu erhalten. In der Regel besitzt aber jede Form einen Konstruktor,
* der alle Werte einer andern Form übernimmt. Die gezeigte Implementierung
* ist daher im Regelfall ausreichend.
*
* @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
* {@link Graphics2D Grafikkontext} zu zeichnen.
* <p>
* Wenn diese Form nicht durch eine AWT-Shape dargestellt wird, kann die
* Methode {@code null} zurückgeben.
* Wenn diese Form nicht durch eine AWT-Shape dargestellt wird, liefert die
* Methode {@code null}.
*
* @return Eine Java-AWT {@code Shape} die diese Form repräsentiert oder
* {@code null}.
@@ -369,34 +458,85 @@ public abstract class Shape extends BasicDrawable {
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 ) {
x += dx;
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 ) {
this.x = x;
this.y = y;
}
/**
* Bewegt die Form an dieselben Koordinaten wie die angegebene Form.
*
* @param shape Eine andere Form.
*/
public void moveTo( Shape shape ) {
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 ) {
moveTo(shape, dir, 0.0);
}
/**
* Bewegt den Ankerpunkt dieser Form zu einem Ankerpunkt einer anderen Form.
* Mit {@code buff} kann ein zusätzlicher Abstand angegeben werden, um den
* die Form entlang des Ankerpunktes {@code anchor} verschoben werden soll.
* Bewegt den Ankerpunkt dieser Form zu einem Ankerpunkt einer anderen
* Form.
* <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
* {@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 dir
* @param buff
* @param shape Die andere Form.
* @param dir Die Richtung des Ankerpunktes.
* @param buff Der Abstand zum angegebenen Ankerpunkt.
*/
public void moveTo( Shape shape, Options.Direction dir, double buff ) {
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
* Zeichenfläche aus.
* Bewegt die Form an den Rand der Zeichenfläche in der angegebenen
* Richtung.
*
* @param dir Die Richtung der Ausrichtung.
* @param dir Die Richtung.
*/
public void alignTo( Options.Direction dir ) {
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 ) {
Point2D anchorShape = Shape.getAnchorPoint(canvasWidth, canvasHeight, 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;
}
/**
* 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 ) {
alignTo(shape, dir, 0.0);
}
/**
* Richtet die Form entlang der angegebenen Richtung an einer anderen Form
* aus. Für {@code DOWN} wird beispielsweise die y-Koordinate der unteren
* Kante dieser Form an der unteren Kante der angegebenen Form {@code shape}
* ausgerichtet. Die x-Koordinate wird nicht verändert. {@code buff} gibt
* einen Abstand ab, um den diese From versetzt ausgerichtet werden soll.
* aus.
* <p>
* {@code buff} gibt einen Abstand ab, um den diese From versetzt
* 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 dir
* @param buff
* @param shape Die andere Form.
* @param dir Die Richtung.
* @param buff Der Abstand.
*/
public void alignTo( Shape shape, Options.Direction dir, double buff ) {
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;
}
/**
* @param shape
* @param dir
* @see #nextTo(Shape, Options.Direction, double)
*/
public void nextTo( Shape shape, Options.Direction dir ) {
nextTo(shape, dir, DEFAULT_BUFFER);
}
/**
* Bewegt die Form neben eine andere in Richtung des angegebenen
* Ankerpunktes. Im Gegensatz zu
* {@link #moveTo(Shape, Options.Direction, double)} wird die Breite bzw.
* Höhe der Formen berücksichtigt und die Formen so platziert, dass keine
* Überlappungen vorhanden sind.
* Ankerpunktes.
* <p>
* Im Gegensatz zu {@link #moveTo(Shape, Options.Direction, double)} wird
* 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 dir
@@ -469,6 +684,19 @@ public abstract class Shape extends BasicDrawable {
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 ) {
verzerrung.shear(dx, dy);
}*/

View File

@@ -0,0 +1,4 @@
/**
* Diese Paket enthält Formen, die Diagramme darstellen.
*/
package schule.ngb.zm.shapes.charts;

View 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;

View File

@@ -19,7 +19,7 @@ import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.function.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -28,21 +28,23 @@ import static java.lang.Math.log;
import static schule.ngb.zm.Constants.random;
/**
* Hilfsklasse, um zufällige Beispieldaten zu erzeugen.
* Eine Helferklasse, um zufällige Beispieldaten zu erzeugen.
* <p>
* Die Klasse kann verschiedene Arten realistischer Beispieldaten erzeugen,
* unter anderem Namen, E-Mail-Adressen, Passwörter oder Platzhalter-Bilder.
* Die Klasse kann verschiedene Arten realistischer Beispieldaten erzeugen.
* Unter anderem Namen, E-Mail-Adressen, Passwörter oder Platzhalter-Bilder.
*/
@SuppressWarnings( "unused" )
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 void main( String[] args ) {
String text = Faker.fakeText(2000, 8);
System.out.println(text);
}
/**
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger Benutzerdaten.
* <p>
@@ -76,8 +78,8 @@ public final class Faker {
/**
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger Namen (Vor- und
* Nachname).
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger Namen im Format
* "Vorname Nachname".
*
* @param count Anzahl der Beispieldaten.
* @return Ein Array mit den Beispieldaten.
@@ -133,7 +135,7 @@ public final class Faker {
/**
* 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.
* @return Ein Array mit den Beispieldaten.
@@ -151,6 +153,14 @@ public final class Faker {
return result;
}
/**
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger
* {@code LocalDateTime}-Objekte, die einen Zeitpunkt mit Dateum und Uhrzeit
* beschreiben,
*
* @param count Anzahl der Beispieldaten.
* @return Ein Array mit den Beispieldaten.
*/
public static LocalDateTime[] fakeDatetimes( int count ) {
long nowEpoch = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC);
long from = LocalDateTime.ofEpochSecond(nowEpoch - 18 * 365, 0, ZoneOffset.UTC).toEpochSecond(ZoneOffset.UTC);
@@ -164,6 +174,17 @@ public final class Faker {
return result;
}
/**
* Erzeugt einen Blindtext mit der angegebenen Anzahl Worten, aufgeteilt in
* die angegebene Anzahl Absätze.
* <p>
* Abssätze werden duch einen doppelten Zeilenumbruch {@code \n\n}
* getrennt.
*
* @param words Anzahl Wörter im Text insgesamt.
* @param paragraphs Anzahl Absätze.
* @return Ein zufälliger Blindtext.
*/
public static String fakeText( int words, int paragraphs ) {
String basetext = "";
try(
@@ -197,6 +218,16 @@ public final class Faker {
return result;
}
/**
* Erzeugt ein Array mit der angegebenen Anzahl zufällig erzeugter Integer
* im angegebenen Bereich.
*
* @param count Anzahl der Zahlen im Array.
* @param min Untere Grenze der Zufallszahlen.
* @param max Obere Grenze der Zufallszahlen.
* @return Ein Array mit Zufallszahlen.
* @see Constants#random(int, int)
*/
public static int[] fakeIntArray( int count, int min, int max ) {
int[] arr = new int[count];
for( int i = 0; i < count; i++ ) {
@@ -205,14 +236,56 @@ public final class Faker {
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 ) {
if( list == null ) {
list = new ArrayList<>(count);
List<Integer> result = (list == null) ? new ArrayList<>(count) : list;
fakeIntegers(count, min, max, result::add);
return result;
}
/**
* Erzeugt die angegebene Anzahl Zufallszahlen im angegebenen Bereich und
* übergibt sie an den angegebenen {@code Consumer}.
*
* Ein typischer Aufruf, um eine {@code #LinkedList} mit 100 Zufallszahlen
* zu erzeugen könnte so aussehen:
* <pre><code>
* List&lt;Integer&gt; l = new LinkedList&lt;&gt;();
* 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++ ) {
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" )

View File

@@ -77,7 +77,7 @@ public final class Log {
* mindestens herabgesenkt werden sollen.
*/
public static void enableGlobalLevel( Level level ) {
int lvl = Validator.requireNotNull(level).intValue();
int lvl = Validator.requireNotNull(level, "level").intValue();
ensureRootLoggerInitialized();
// Decrease level of root level ConsoleHandlers for output

View File

@@ -6,35 +6,55 @@ import java.util.function.Supplier;
/**
* Statische Methoden, um Methodenparameter auf Gültigkeit zu prüfen.
*/
@SuppressWarnings( "UnusedReturnValue" )
@SuppressWarnings( {"unused", "UnusedReturnValue"} )
public class Validator {
public static final <T> T requireNotNull( T obj ) {
return Objects.requireNonNull(obj);
public static final <T> T requireNotNull( T obj, CharSequence paramName ) {
return requireNotNull(obj, () -> "Parameter <%s> may not be null.".format(paramName.toString()));
}
public static final <T> T requireNotNull( T obj, CharSequence msg ) {
return Objects.requireNonNull(obj, msg::toString);
public static final <T> T requireNotNull( T obj, CharSequence paramName, CharSequence msg ) {
return requireNotNull(obj, () -> msg.toString().format(paramName.toString()));
}
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 ) {
return requireNotEmpty(str, (Supplier<String>) null);
public static final String requireNotEmpty( String str, CharSequence paramName ) {
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 ) {
return requireNotEmpty(str, msg::toString);
public static final String requireNotEmpty( String str, CharSequence paramName, CharSequence msg ) {
return requireNotEmpty(str, () -> msg.toString().format(paramName.toString()));
}
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());
}
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 ) {
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 ) {
return requireSize(arr, size, (Supplier<String>) null);
}
@@ -168,20 +173,6 @@ public class Validator {
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() {
}

View File

@@ -57,6 +57,9 @@ import java.util.function.BiConsumer;
* dispatcher.dispatchEvent("stop", new MyEvent());
* }
* </code></pre>
* <p>
* Siehe {@link schule.ngb.zm.media.AudioListener} und
* {@link schule.ngb.zm.media.Music} für ein Beispiel der Verwendung.
*
* @param <E> Typ der Event-Objekte.
* @param <L> Typ der verwendeten Listener-Schnittstelle.
@@ -73,8 +76,8 @@ public class EventDispatcher<E, L extends Listener<E>> {
}
public void registerEventType( String eventKey, BiConsumer<E, L> dispatcher ) {
Validator.requireNotNull(eventKey);
Validator.requireNotNull(dispatcher);
Validator.requireNotNull(eventKey, "eventKey");
Validator.requireNotNull(dispatcher, "dispatcher");
if( !eventRegistered(eventKey) ) {
eventRegistry.put(eventKey, dispatcher);
@@ -89,7 +92,7 @@ public class EventDispatcher<E, L extends Listener<E>> {
listeners.remove(listener);
}
@SuppressWarnings("unused")
@SuppressWarnings( "unused" )
public boolean hasListeners() {
return !listeners.isEmpty();
}
@@ -99,8 +102,8 @@ public class EventDispatcher<E, L extends Listener<E>> {
}
public void dispatchEvent( String eventKey, final E event ) {
Validator.requireNotNull(eventKey);
Validator.requireNotNull(event);
Validator.requireNotNull(eventKey, "eventKey");
Validator.requireNotNull(event, "event");
if( eventRegistered(eventKey) ) {
final BiConsumer<E, L> dispatcher = eventRegistry.get(eventKey);

View File

@@ -0,0 +1,5 @@
/**
* Dieses Paket enthält Helferklassen, die das Listener-Entwurfsmuster
* umsetzen.
*/
package schule.ngb.zm.util.events;

View File

@@ -5,14 +5,21 @@ import schule.ngb.zm.util.Validator;
import java.awt.Font;
import java.awt.FontFormatException;
import java.awt.Image;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.util.Map;
import java.util.Objects;
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 {
private static final Map<String, Font> fontCache = new ConcurrentHashMap<>();
@@ -22,11 +29,12 @@ public class FontLoader {
* <p>
* Die Methode kann eine Liste von Schriften bekommen und probiert diese
* nacheinander zu laden. Die erste Schrift, die Fehlerfrei geladen werden
* kann, wird zurückgegeben. Kann keine der Schriften geladen werden, ist das
* Ergebnis {@code null}.
* kann, wird zurückgegeben. Kann keine der Schriften geladen werden, ist
* das Ergebnis {@code null}.
* <p>
* Die gefundene Schrift wird unter ihrem Dateinamen in den Schriftenspeicher
* geladen und kann danach in der Zeichenmaschine benutzt werden.
* Die gefundene Schrift wird unter ihrem Dateinamen in den
* Schriftenspeicher geladen und kann danach in der Zeichenmaschine benutzt
* werden.
* <p>
* Eine Datei mit dem Namen "fonts/Font-Name.ttf" würde mit dem Namen
* "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 ) {
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.");
if( fontCache.containsKey(name) ) {
@@ -96,11 +104,12 @@ public class FontLoader {
* <p>
* Die Methode kann eine Liste von Schriften bekommen und probiert diese
* nacheinander zu laden. Die erste Schrift, die Fehlerfrei geladen werden
* kann, wird zurückgegeben. Kann keine der Schriften geladen werden, ist das
* Ergebnis {@code null}.
* kann, wird zurückgegeben. Kann keine der Schriften geladen werden, ist
* das Ergebnis {@code null}.
* <p>
* Die gefundene Schrift wird unter ihrem Dateinamen in den Schriftenspeicher
* geladen und kann danach in der Zeichenmaschine benutzt werden.
* Die gefundene Schrift wird unter ihrem Dateinamen in den
* Schriftenspeicher geladen und kann danach in der Zeichenmaschine benutzt
* werden.
* <p>
* Eine Datei mit dem Namen "fonts/Font-Name.ttf" würde mit dem Namen
* "Font-Name" geladen und kann danach zum Beispiel in einem
@@ -113,7 +122,7 @@ public class FontLoader {
* @see #loadFont(String, String)
*/
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?
Font font = loadFont(name, fontSource);
if( font != null ) {

View File

@@ -18,6 +18,13 @@ import java.lang.ref.SoftReference;
import java.util.Map;
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 static boolean caching = true;

View File

@@ -0,0 +1,5 @@
/**
* Dieses Paket enthält Helferklassen, um Ressourcen aus verschiedenen Quellen
* zu laden.
*/
package schule.ngb.zm.util.io;

View File

@@ -0,0 +1,4 @@
/**
* Dieses Paket enthält Helferklassen für verschiedene Einsatzzwecke.
*/
package schule.ngb.zm.util;

View File

@@ -0,0 +1,5 @@
/**
* Dieses Paket enthält Helferklassen zur Verarbeitung paralleler Aufgaben
* innerhalb der Zeichenmaschine.
*/
package schule.ngb.zm.util.tasks;

View File

@@ -10,6 +10,11 @@ import java.awt.Shape;
import java.awt.Stroke;
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" )
public final class Shape2DLayer extends Layer {

View 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());
}
}

View File

@@ -11,14 +11,14 @@ class ValidatorTest {
StringBuilder sb = new StringBuilder("Message");
Object o1 = new Object();
assertEquals(o1, Validator.requireNotNull(o1));
assertEquals(o1, Validator.requireNotNull(o1, "Message"));
assertEquals(o1, Validator.requireNotNull(o1, sb));
assertEquals(o1, Validator.requireNotNull(o1, "content"));
assertEquals(o1, Validator.requireNotNull(o1, "content", "Message"));
assertEquals(o1, Validator.requireNotNull(o1, "content", sb));
assertEquals(o1, Validator.requireNotNull(o1, ()->"Message"));
String o2 = null;
assertThrowsExactly(NullPointerException.class, () -> Validator.requireNotNull(o2));
assertThrowsExactly(NullPointerException.class, () -> Validator.requireNotNull(o2, "Message"));
assertThrowsExactly(NullPointerException.class, () -> Validator.requireNotNull(o2, "content"));
assertThrowsExactly(NullPointerException.class, () -> Validator.requireNotNull(o2, "content", "Message"));
assertThrowsExactly(NullPointerException.class, () -> Validator.requireNotNull(o2, ()->"Message"));
}
@@ -27,14 +27,14 @@ class ValidatorTest {
StringBuilder sb = new StringBuilder("Message");
String s1 = "Content";
assertEquals(s1, Validator.requireNotEmpty(s1));
assertEquals(s1, Validator.requireNotEmpty(s1, "Message"));
assertEquals(s1, Validator.requireNotEmpty(s1, sb));
assertEquals(s1, Validator.requireNotEmpty(s1, "content"));
assertEquals(s1, Validator.requireNotEmpty(s1, "content", "Message"));
assertEquals(s1, Validator.requireNotEmpty(s1, "content", sb));
assertEquals(s1, Validator.requireNotEmpty(s1, ()->"Message"));
String s2 = "";
assertThrowsExactly(IllegalArgumentException.class, () -> Validator.requireNotEmpty(s2));
assertThrowsExactly(IllegalArgumentException.class, () -> Validator.requireNotEmpty(s2, "Message"));
assertThrowsExactly(IllegalArgumentException.class, () -> Validator.requireNotEmpty(s2, "content"));
assertThrowsExactly(IllegalArgumentException.class, () -> Validator.requireNotEmpty(s2, "content", "Message"));
assertThrowsExactly(IllegalArgumentException.class, () -> Validator.requireNotEmpty(s2, ()->"Message"));
}

View 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;
}
}

View 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;
}
}
}

View 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);
}

View 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);
}

View 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.
*/
}
}
}

View 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();
}
}
}

View 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;
}
}

View 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();
}
}

View 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;
}
}
}

View 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;
}
}

View 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();
}
}
}

View 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);
}
}
}

View 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;
}
}
}

View 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;
}
}