11 Commits

Author SHA1 Message Date
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
32 changed files with 662 additions and 199 deletions

View File

@@ -33,6 +33,19 @@ dependencies {
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
} }
javadoc {
options {
encoding = "UTF-8"
overview = "src/main/java/overview.html"
// title = "Die Zeichenmaschine"
// options.links 'https://docs.oracle.com/javase/8/docs/api/'
// options.links 'https://docs.oracle.com/javaee/7/api'
options.links 'https://docs.oracle.com/en/java/javase/11/docs/api'
}
options.addStringOption("charset", "UTF-8")
}
test { test {
useJUnitPlatform() useJUnitPlatform()
} }

View File

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

View File

@@ -1,9 +1,8 @@
package schule.ngb.zm; package schule.ngb.zm;
import schule.ngb.zm.anim.Easing; import schule.ngb.zm.anim.Easing;
import schule.ngb.zm.util.Validator;
import schule.ngb.zm.util.io.ImageLoader;
import schule.ngb.zm.util.Noise; import schule.ngb.zm.util.Noise;
import schule.ngb.zm.util.io.ImageLoader;
import java.awt.Cursor; import java.awt.Cursor;
import java.awt.Font; import java.awt.Font;
@@ -24,7 +23,7 @@ import java.util.function.DoubleUnaryOperator;
* aktuell gehalten werden (beispielsweise {@link #runtime}). * aktuell gehalten werden (beispielsweise {@link #runtime}).
* <p> * <p>
* Für die Implementierung eigener Klassen ist es oft hilfreich von * Für die Implementierung eigener Klassen ist es oft hilfreich von
* {@code Constants} zu erben, um die Methoden und Konstanten einfach im * {@code Constants} zu erben, um die Methoden und Konstanten einfacher im
* Programm nutzen zu können. * Programm nutzen zu können.
* <pre><code> * <pre><code>
* class MyClass extends Constants { * class MyClass extends Constants {
@@ -1256,7 +1255,7 @@ public class Constants {
public static final double distance( double fromX, double fromY, double toX, double toY ) { public static final double distance( double fromX, double fromY, double toX, double toY ) {
double diffX = toX - fromX; double diffX = toX - fromX;
double diffY = toY - fromY; double diffY = toY - fromY;
return sqrt(diffX*diffX + diffY*diffY); return sqrt(diffX * diffX + diffY * diffY);
} }
/** /**
@@ -1321,7 +1320,8 @@ public class Constants {
} }
/** /**
* Erzeugt eine ganze Pseudozufallszahl zwischen {@code 0} und {@code max}. * Erzeugt eine ganze Pseudozufallszahl zwischen {@code 0} und {@code max}
* (einschließlich der Grenzen).
* *
* @param max Obere Grenze. * @param max Obere Grenze.
* @return Eine Zufallszahl. * @return Eine Zufallszahl.
@@ -1332,7 +1332,7 @@ public class Constants {
/** /**
* Erzeugt eine ganze Pseudozufallsganzzahl zwischen {@code min} und * Erzeugt eine ganze Pseudozufallsganzzahl zwischen {@code min} und
* {@code max}. * {@code max} (einschließlich der Grenzen).
* *
* @param min Untere Grenze. * @param min Untere Grenze.
* @param max Obere Grenze. * @param max Obere Grenze.
@@ -1395,9 +1395,9 @@ public class Constants {
* @param values Ein Array mit Werten, die zur Auswahl stehen. * @param values Ein Array mit Werten, die zur Auswahl stehen.
* @return Ein zufälliges Element aus dem Array. * @return Ein zufälliges Element aus dem Array.
*/ */
/*public static final int choice( int... values ) { public static final int choice( int... values ) {
return values[random(0, values.length - 1)]; return values[random(0, values.length - 1)];
}*/ }
/** /**
* Wählt ein zufälliges Element aus dem Array aus. * Wählt ein zufälliges Element aus dem Array aus.
@@ -1405,9 +1405,9 @@ public class Constants {
* @param values Ein Array mit Werten, die zur Auswahl stehen. * @param values Ein Array mit Werten, die zur Auswahl stehen.
* @return Ein zufälliges Element aus dem Array. * @return Ein zufälliges Element aus dem Array.
*/ */
/*public static final double choice( double... values ) { public static final double choice( double... values ) {
return values[random(0, values.length - 1)]; return values[random(0, values.length - 1)];
}*/ }
/** /**
* Wählt ein zufälliges Element aus dem Array aus. * Wählt ein zufälliges Element aus dem Array aus.
@@ -1416,6 +1416,7 @@ public class Constants {
* @param <T> Datentyp des Elements. * @param <T> Datentyp des Elements.
* @return Ein zufälliges Element aus dem Array. * @return Ein zufälliges Element aus dem Array.
*/ */
@SafeVarargs
public static final <T> T choice( T... values ) { public static final <T> T choice( T... values ) {
return values[random(0, values.length - 1)]; return values[random(0, values.length - 1)];
} }

View File

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

View File

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

View File

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

View File

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

View File

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

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 ) { public static final <T> Future<T> animateProperty( T target, final double from, final double to, int runtime, DoubleUnaryOperator easing, DoubleConsumer propSetter ) {
Validator.requireNotNull(target); Validator.requireNotNull(target, "target");
Validator.requireNotNull(propSetter); Validator.requireNotNull(propSetter, "propSetter");
return play(target, runtime, easing, ( e ) -> propSetter.accept(Constants.interpolate(from, to, e))); return play(target, runtime, easing, ( e ) -> propSetter.accept(Constants.interpolate(from, to, e)));
} }

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) * @see ResourceStreamProvider#getResourceURL(String)
*/ */
public Music( String audioSource ) { public Music( String audioSource ) {
Validator.requireNotNull(audioSource); Validator.requireNotNull(audioSource, "audioSource");
this.audioSource = audioSource; this.audioSource = audioSource;
} }

View File

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

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.Fillable;
import schule.ngb.zm.Options; import schule.ngb.zm.Options;
import schule.ngb.zm.Strokeable; import schule.ngb.zm.Strokeable;
import schule.ngb.zm.util.Validator;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.geom.AffineTransform; import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D; import java.awt.geom.Point2D;
/** /**
* Basisklasse für alle Formen in der Zeichenmaschine. * Dies ist die Basisklasse für alle Formen in der Zeichenmaschine.
* <p> * <p>
* Alle Formen sind als Unterklassen von {@code Shape} implementiert. * Alle Formen sind als Unterklassen von {@code Shape} implementiert.
* <p> * <p>
@@ -19,7 +20,6 @@ import java.awt.geom.Point2D;
* Parametern initialisiert und einen, der die Werte einer anderen Form * Parametern initialisiert und einen, der die Werte einer anderen Form
* desselben Typs übernimmt. In der Klasse {@link Circle} sind die Konstruktoren * desselben Typs übernimmt. In der Klasse {@link Circle} sind die Konstruktoren
* beispielsweise so implementiert: * beispielsweise so implementiert:
*
* <pre><code> * <pre><code>
* public Circle( double x, double y, double radius ) { * public Circle( double x, double y, double radius ) {
* super(x, y); * super(x, y);
@@ -39,42 +39,42 @@ import java.awt.geom.Point2D;
public abstract class Shape extends BasicDrawable { public abstract class Shape extends BasicDrawable {
/** /**
* x-Koordinate der Form. * Speichert die x-Koordinate der Form.
*/ */
protected double x; protected double x;
/** /**
* y-Koordinate der Form. * Speichert die y-Koordinate der Form.
*/ */
protected double y; protected double y;
/** /**
* Rotation in Grad um den Punkt (x, y). * Speichert die Rotation in Grad um den Punkt (x, y).
*/ */
protected double rotation = 0.0; protected double rotation = 0.0;
/** /**
* Skalierungsfaktor. * Speichert den Skalierungsfaktor.
*/ */
protected double scale = 1.0; protected double scale = 1.0;
/** /**
* Ankerpunkt der Form. * Speichert den Ankerpunkt.
*/ */
protected Options.Direction anchor = Options.Direction.CENTER; protected Options.Direction anchor = Options.Direction.CENTER;
/** /**
* Setzt die x- und y-Koordinate der Form auf 0. * Erstellt eine neue Form mit den Koordinaten {@code (0,0)}.
*/ */
public Shape() { public Shape() {
this(0.0, 0.0); this(0.0, 0.0);
} }
/** /**
* Setzt die x- und y-Koordinate der Form. * Erstellt eine Form mit den angegebenen Koordinaten.
* *
* @param x * @param x Die x-Koordinate.
* @param y * @param y Die y-Koordinate.
*/ */
public Shape( double x, double y ) { public Shape( double x, double y ) {
this.x = x; this.x = x;
@@ -82,7 +82,7 @@ public abstract class Shape extends BasicDrawable {
} }
/** /**
* Gibt die x-Koordinate der Form zurück. * Liefert die aktuelle x-Koordinate der Form.
* *
* @return Die x-Koordinate. * @return Die x-Koordinate.
*/ */
@@ -100,7 +100,7 @@ public abstract class Shape extends BasicDrawable {
} }
/** /**
* Gibt die y-Koordinate der Form zurück. * Liefert die aktuelle y-Koordinate der Form.
* *
* @return Die y-Koordinate. * @return Die y-Koordinate.
*/ */
@@ -109,57 +109,44 @@ public abstract class Shape extends BasicDrawable {
} }
/** /**
* Setzt die y-Koordinate der Form. * Setzt die x-Koordinate der Form.
* *
* @param y Die neue y-Koordinate. * @param y Die neue y-Koordinate der Form.
*/ */
public void setY( double y ) { public void setY( double y ) {
this.y = y; this.y = y;
} }
/** /**
* Gibt die Breite dieser Form zurück. * Liefert die aktuelle Breite dieser Form.
* <p> * <p>
* Die Breite einer Form ist immer die Breite ihrer Begrenzung, * Die Breite einer Form ist immer die Breite ihrer Begrenzung,
* <strong>bevor</strong> Drehungen und andere Transformationen auf sie * <strong>bevor</strong> Drehungen und andere Transformationen auf sie
* angewandt wurden. * angewandt wurden.
* <p> * <p>
* Die Begrenzungen der tatsächlich gezeichneten Form kann mit * Die Begrenzungen der tatsächlich gezeichneten Form wird mit
* {@link #getBounds()} abgerufen werden. * {@link #getBounds()} abgerufen.
* *
* @return * @return Die Breite der Form.
*/ */
public abstract double getWidth(); public abstract double getWidth();
/** /**
* Gibt die Höhe dieser Form zurück. * Liefert die aktuelle Höhe dieser Form.
* <p> * <p>
* Die Höhe einer Form ist immer die Höhe ihrer Begrenzung, * Die Höhe einer Form ist immer die Höhe ihrer Begrenzung,
* <strong>bevor</strong> Drehungen und andere Transformationen auf sie * <strong>bevor</strong> Drehungen und andere Transformationen auf sie
* angewandt wurden. * angewandt wurden.
* <p> * <p>
* Die Begrenzungen der tatsächlich gezeichneten Form kann mit * Die Begrenzungen der tatsächlich gezeichneten Form wird mit
* {@link #getBounds()} abgerufen werden. * {@link #getBounds()} abgerufen.
* *
* @return * @return Die Höhe der Form.
*/ */
public abstract double getHeight(); public abstract double getHeight();
@Override
public void setGradient( schule.ngb.zm.Color from, schule.ngb.zm.Color to, Options.Direction dir ) {
Point2D apDir = getAbsAnchorPoint(dir);
Point2D apInv = getAbsAnchorPoint(dir.inverse());
setGradient(apInv.getX(), apInv.getY(), from, apDir.getX(), apDir.getY(), to);
}
@Override
public void setGradient( schule.ngb.zm.Color from, schule.ngb.zm.Color to ) {
Point2D ap = getAbsAnchorPoint(CENTER);
setGradient(ap.getX(), ap.getY(), Math.min(ap.getX(), ap.getY()), from, to);
}
/** /**
* Gibt die Rotation in Grad zurück. * Liefert die Rotation in Grad.
* *
* @return Rotation in Grad. * @return Rotation in Grad.
*/ */
@@ -176,14 +163,25 @@ public abstract class Shape extends BasicDrawable {
this.rotation += angle % 360; this.rotation += angle % 360;
} }
public void rotateTo( double angle ) { /**
this.rotation = angle % 360; * Dreht die Form um den angegebenen Winkel um das angegebene Drehzentrum.
} *
* @param center Das Drehzentrum der Drehung.
* @param angle Der Drehwinkel.
*/
public void rotate( Point2D center, double angle ) { public void rotate( Point2D center, double angle ) {
Validator.requireNotNull(center, "center");
rotate(center.getX(), center.getY(), angle); rotate(center.getX(), center.getY(), angle);
} }
/**
* Dreht die Form um den angegebenen Drehwinkel um die angegbenen
* Koordinaten als Drehzentrum.
*
* @param x x-Koordiante des Drehzentrums.
* @param y y-Koordiante des Drehzentrums.
* @param angle Drehwinkel in Grad.
*/
public void rotate( double x, double y, double angle ) { public void rotate( double x, double y, double angle ) {
this.rotation += angle % 360; this.rotation += angle % 360;
@@ -198,6 +196,15 @@ public abstract class Shape extends BasicDrawable {
this.y = y2 + y; this.y = y2 + y;
} }
/**
* Setzt die Drehung der Form auf den angegebenen Winkel.
*
* @param angle Drehwinkel in Grad.
*/
public void rotateTo( double angle ) {
this.rotation = angle % 360;
}
/** /**
* Gibt den aktuellen Skalierungsfaktor zurück. * Gibt den aktuellen Skalierungsfaktor zurück.
* *
@@ -207,34 +214,92 @@ public abstract class Shape extends BasicDrawable {
return scale; return scale;
} }
/**
* Setzt den Skalierungsfaktor auf den angegebenen Faktor.
* <p>
* Bei einem Faktor größer 0 wird die Form vergrößert, bei einem Faktor
* kleiner 0 verkleinert. Bei negativen Werten wird die Form entlang der x-
* bzw. y-Achse gespiegelt.
* <p>
* Das Seitenverhältnis wird immer beibehalten.
*
* @param factor Der Skalierungsfaktor.
*/
public void scale( double factor ) { public void scale( double factor ) {
scale = factor; scale = factor;
} }
/**
* Skaliert die Form um den angegebenen Faktor.
* <p>
* Bei einem Faktor größer 0 wird die Form vergrößert, bei einem Faktor
* kleiner 0 verkleinert. Bei negativen Werten wird die Form entlang der x-
* bzw. y-Achse gespiegelt.
* <p>
* Die Skalierung wird zusätzlich zur aktuellen Skalierung angewandt. Wurde
* die Form zuvor um den Faktor 0.5 verkleinert und wird dann um 1.5
* vergrößert, dann ist die Form im Anschluss ein Drittel kleiner als zu
* Beginn ({@code 0.5 * 1.5 = 0.75}).
*
* @param factor Der Skalierungsfaktor.
*/
public void scaleBy( double factor ) { public void scaleBy( double factor ) {
scale(scale * factor); scale(scale * factor);
} }
/**
* Liefert den aktuellen Ankerpunkt der Form.
*
* @return Der Ankerpunkt.
*/
public Options.Direction getAnchor() { public Options.Direction getAnchor() {
return anchor; return anchor;
} }
/** /**
* Setzt den Ankerpunkt der Form basierend auf der angegebenen * Setzt den Ankerpunkt der Form auf die angegebene Richtung.
* {@link Options.Direction Richtung}.
* <p> * <p>
* Für das Setzen des Ankers muss das * Jede Form hat einen Ankerpunkt, von dem aus sie gezeichnet wird. Jede
* {@link #getBounds() begrenzende Rechteck} berechnet werden. Unterklassen * {@link schule.ngb.zm.Options.Direction Richtung} beschreibt einen der
* sollten die Methode überschreiben, wenn der Anker auch direkt gesetzt * Neun Ankerpunkte:
* werden kann. * <pre>
* NW────N────NE
* │ │
* │ │
* W C E
* │ │
* │ │
* SW────S────SE
* </pre>
* <p>
* Für den Ankerpunkt {@link #CENTER} wird die Form also ausgehend von den
* Koordinaten {@link #x} und {@link #y} um die Hälfte der Breite nach links
* und rechts, sowie um die Hälfte der Höhe nach oben und unten gezeichnet.
* Fpr den Ankerpunkt {@link #NORTHWEST} dagegen um die gesamte Breite nach
* rechts und die Höhe nach unten.
* <pre>
* setAnchor(CENTER) │ setAnchor(NORTHWEST)
* ┌───────────┐ │
* │ │ │
* │ │ │
* │ (x,y) │ │ (x,y)─────────┐
* │ │ │ │ │
* │ │ │ │ │
* └───────────┘ │ │ │
* │ │ │
* │ │ │
* │ └───────────┘
* </pre>
* <p>
* Der Ankerpunkt der Form bestimmt bei Transformationen auch die Position
* des Drehzentrums und anderer relativer Koordinaten bezüglich der Form.
* *
* @param anchor * @param anchor Der Ankerpunkt.
*/ */
public void setAnchor( Options.Direction anchor ) { public void setAnchor( Options.Direction anchor ) {
if( anchor != null ) { Validator.requireNotNull(anchor, "anchor");
this.anchor = anchor; this.anchor = anchor;
} }
}
/** /**
* Bestimmt die relativen Koordinaten des angegebenen Ankerpunkts basierend * Bestimmt die relativen Koordinaten des angegebenen Ankerpunkts basierend
@@ -242,7 +307,19 @@ public abstract class Shape extends BasicDrawable {
* <p> * <p>
* Die Koordinaten des Ankerpunktes werden relativ zur oberen linken Ecke * Die Koordinaten des Ankerpunktes werden relativ zur oberen linken Ecke
* des Rechtecks mit der Breite {@code width} und der Höhe {@code height} * des Rechtecks mit der Breite {@code width} und der Höhe {@code height}
* bestimmt. * bestimmt. Der Ankerpunkt {@link #NORTHWEST} hat daher immer das Ergebnis
* {@code (0,0)} und {@link #SOUTHEAST} {@code (width, height)}.
* <pre>
* (0,0)───(w/2,0)───(w,0)
* │ │
* │ │
* │ │
* (0,h/2) (w/2,h/2) (w,h/2)
* │ │
* │ │
* │ │
* (0,h)───(w/2,h)───(w,h)
* </pre>
* *
* @param width Breite des umschließenden Rechtecks. * @param width Breite des umschließenden Rechtecks.
* @param height Höhe des umschließenden Rechtecks. * @param height Höhe des umschließenden Rechtecks.
@@ -259,11 +336,12 @@ public abstract class Shape extends BasicDrawable {
} }
/** /**
* Bestimmt den Ankerpunkt der Form relativ zum gesetzten * Bestimmt die Koordinaten des angegebenen Ankers der Form relativ zum
* {@link #setAnchor(Options.Direction) Ankerpunkt}. * aktuellen {@link #setAnchor(Options.Direction) Ankerpunkt}.
* *
* @param anchor Die Richtung des Ankerpunktes. * @param anchor Die Richtung des Ankers.
* @return Der relative Ankerpunkt. * @return Der relative Ankerpunkt.
* @see #getAnchorPoint(double, double, Options.Direction)
*/ */
public Point2D.Double getAnchorPoint( Options.Direction anchor ) { public Point2D.Double getAnchorPoint( Options.Direction anchor ) {
double wHalf = getWidth() * .5, hHalf = getHeight() * .5; double wHalf = getWidth() * .5, hHalf = getHeight() * .5;
@@ -276,11 +354,20 @@ public abstract class Shape extends BasicDrawable {
} }
/** /**
* Ermittelt die absoluten Koordinaten eines angegebenen * Ermittelt die absoluten Koordinaten des angegebenen
* {@link #setAnchor(Options.Direction) Ankers}. * {@link #setAnchor(Options.Direction) Ankers}.
* <p>
* Die absoluten Koordinaten werden bestimmt durch die Position der Form
* {@code (x,y)} plus die
* {@link #getAnchorPoint(Options.Direction) relativen Koordinaten} des
* Ankers.
* *
* @param anchor Die Richtung des Ankerpunktes. * <b>Wichtig:</b> Die Berechnung berücksichtigt derzeit keine Rotationen
* und Transformationen der Form.
*
* @param anchor Die Richtung des Ankers.
* @return Der absolute Ankerpunkt. * @return Der absolute Ankerpunkt.
* @see #getAnchorPoint(double, double, Options.Direction)
*/ */
public Point2D.Double getAbsAnchorPoint( Options.Direction anchor ) { public Point2D.Double getAbsAnchorPoint( Options.Direction anchor ) {
// TODO: Die absoluten Anker müssten eigentlich die Rotation berücksichtigen. // TODO: Die absoluten Anker müssten eigentlich die Rotation berücksichtigen.
@@ -293,20 +380,22 @@ public abstract class Shape extends BasicDrawable {
/** /**
* Kopiert die Eigenschaften der angegebenen Form in diese. * Kopiert die Eigenschaften der angegebenen Form in diese.
* <p> * <p>
* Unterklassen sollten diese Methode überschreiben, um weitere * Unterklassen überschreiben diese Methode, um weitere Eigenschaften zu
* Eigenschaften zu kopieren (zum Beispiel den Radius eines Kreises). * kopieren (zum Beispiel den Radius eines Kreises). Überschreibende
* Unterklassen sollten immer mit dem Aufruf {@code super.copyFrom(shape)} * Methoden sollten immer mit dem Aufruf {@code super.copyFrom(shape)} die
* die Basiseigenschaften kopieren. * Basiseigenschaften kopieren.
* <p> * <p>
* Die Methode sollte so viele Eigenschaften wie möglich von der anderen * Die Methode kopiert so viele Eigenschaften wie möglich von der
* Form in diese kopieren. Wenn die andere Form einen anderen Typ hat, dann * angegebenen Form in diese. Wenn die andere Form einen anderen Typ hat,
* werden trotzdem die Basiseigenschaften (Konturlinie, Füllung, Position, * dann werden trotzdem die Basiseigenschaften (Konturlinie, Füllung,
* Rotation, Skalierung, Sichtbarkeit und Ankerpunkt) in diese Form kopiert. * Position, Rotation, Skalierung, Sichtbarkeit und Ankerpunkt) in diese
* Implementierende Unterklassen können soweit sinnvoll auch andere Werte * Form kopiert. Soweit sinnvoll übernehmen implementierende Unterklassen
* übernehmen. Eine {@link Ellipse} kann beispielsweise auch die Breite und * auch andere Werte. Eine {@link Ellipse} kopiert beispielsweise auch die
* Höhe eines {@link Rectangle} übernehmen. * Breite und Höhe eines {@link Rectangle}.
* <p>
* Wird {@code null} übergeben, dann passiert nichts.
* *
* @param shape Die Originalform, von der kopiert werden soll. * @param shape Die Originalform, von der kopiert wird.
*/ */
public void copyFrom( Shape shape ) { public void copyFrom( Shape shape ) {
if( shape != null ) { if( shape != null ) {
@@ -335,9 +424,9 @@ public abstract class Shape extends BasicDrawable {
* </code></pre> * </code></pre>
* <p> * <p>
* Die Methode kann beliebig umgesetzt werden, um eine 1-zu-1-Kopie dieser * Die Methode kann beliebig umgesetzt werden, um eine 1-zu-1-Kopie dieser
* Form zu erhalten. In der Regel sollte aber jede Form einen Konstruktor * Form zu erhalten. In der Regel besitzt aber jede Form einen Konstruktor,
* besitzen, die alle Werte einer andern Form übernimmt. Die gezeigte * der alle Werte einer andern Form übernimmt. Die gezeigte Implementierung
* Implementierung dürfte daher im Regelfall ausreichend sein. * ist daher im Regelfall ausreichend.
* *
* @return Eine genaue Kopie dieser Form. * @return Eine genaue Kopie dieser Form.
*/ */
@@ -348,8 +437,8 @@ public abstract class Shape extends BasicDrawable {
* zurück. Intern werden die AWT Shapes benutzt, um sie auf den * zurück. Intern werden die AWT Shapes benutzt, um sie auf den
* {@link Graphics2D Grafikkontext} zu zeichnen. * {@link Graphics2D Grafikkontext} zu zeichnen.
* <p> * <p>
* Wenn diese Form nicht durch eine AWT-Shape dargestellt wird, kann die * Wenn diese Form nicht durch eine AWT-Shape dargestellt wird, liefert die
* Methode {@code null} zurückgeben. * Methode {@code null}.
* *
* @return Eine Java-AWT {@code Shape} die diese Form repräsentiert oder * @return Eine Java-AWT {@code Shape} die diese Form repräsentiert oder
* {@code null}. * {@code null}.
@@ -369,34 +458,85 @@ public abstract class Shape extends BasicDrawable {
return new Bounds(this); return new Bounds(this);
} }
/**
* Verschiebt die Form um die angegebenen Werte entlang der
* Koordinatenachsen.
*
* @param dx Verschiebung entlang der x-Achse.
* @param dy Verschiebung entlang der y-Achse.
*/
public void move( double dx, double dy ) { public void move( double dx, double dy ) {
x += dx; x += dx;
y += dy; y += dy;
} }
/**
* Bewegt die Form an die angegebenen Koordinaten.
*
* @param x Die neue x-Koordinate.
* @param y Die neue y-Koordinate.
*/
public void moveTo( double x, double y ) { public void moveTo( double x, double y ) {
this.x = x; this.x = x;
this.y = y; this.y = y;
} }
/**
* Bewegt die Form an dieselben Koordinaten wie die angegebene Form.
*
* @param shape Eine andere Form.
*/
public void moveTo( Shape shape ) { public void moveTo( Shape shape ) {
moveTo(shape.getX(), shape.getY()); moveTo(shape.getX(), shape.getY());
} }
/**
* Bewegt die Form zum angegebenen Ankerpunkt der angegebenen Form.
*
* @param shape Die andere Form.
* @param dir Die Richtung des Ankerpunktes.
* @see #moveTo(Shape, Options.Direction, double)
*/
public void moveTo( Shape shape, Options.Direction dir ) { public void moveTo( Shape shape, Options.Direction dir ) {
moveTo(shape, dir, 0.0); moveTo(shape, dir, 0.0);
} }
/** /**
* Bewegt den Ankerpunkt dieser Form zu einem Ankerpunkt einer anderen Form. * Bewegt den Ankerpunkt dieser Form zu einem Ankerpunkt einer anderen
* Mit {@code buff} kann ein zusätzlicher Abstand angegeben werden, um den * Form.
* die Form entlang des Ankerpunktes {@code anchor} verschoben werden soll. * <p>
* Mit {@code buff} wird ein zusätzlicher Abstand angegeben, um den die Form
* entlang des Ankerpunktes {@code anchor} verschoben wird.
* <p>
* Ist der Anker zum Beispiel {@code NORTH}, dann wird die Form um * Ist der Anker zum Beispiel {@code NORTH}, dann wird die Form um
* {@code buff} nach oben verschoben. * {@code buff} oberhalb der oberen Kante der zweiten Form verschoben.
* <p>
* Befinden sich die Formen zuvor in folgender Ausrichtung:
* <pre>
* ┌─────────┐
* │ │
* W B │
* ┌─────┐ │ │
* │ │ └─────────┘
* W A │
* │ │
* └─────┘
* </pre>
* <p>
* bringt sie der Aufruf {@code B.moveTo(A, DOWN, 0)} in diese Ausrichtung:
* <pre>
* B.moveTo(A, WEST, 0) │ B.moveTo(A, WEST, 10)
* │
* ┌─────┬───┐ │ ┌┬────┬────┐
* │ │ │ │ ││ │ │
* │ A B│ │ │ ││ A B │
* │ │ │ │ ││ │ │
* └─────┴───┘ │ └┴────┴────┘
* </pre>
* *
* @param shape * @param shape Die andere Form.
* @param dir * @param dir Die Richtung des Ankerpunktes.
* @param buff * @param buff Der Abstand zum angegebenen Ankerpunkt.
*/ */
public void moveTo( Shape shape, Options.Direction dir, double buff ) { public void moveTo( Shape shape, Options.Direction dir, double buff ) {
Point2D ap = shape.getAbsAnchorPoint(dir); Point2D ap = shape.getAbsAnchorPoint(dir);
@@ -406,15 +546,22 @@ public abstract class Shape extends BasicDrawable {
} }
/** /**
* Richtet die Form entlang der angegebenen Richtung am Rand der * Bewegt die Form an den Rand der Zeichenfläche in der angegebenen
* Zeichenfläche aus. * Richtung.
* *
* @param dir Die Richtung der Ausrichtung. * @param dir Die Richtung.
*/ */
public void alignTo( Options.Direction dir ) { public void alignTo( Options.Direction dir ) {
alignTo(dir, 0.0); alignTo(dir, 0.0);
} }
/**
* Bewegt die Form mit dem angegebenen Abstand an den Rand der Zeichenfläche
* in der angegebenen Richtung aus.
*
* @param dir Die Richtung.
* @param buff Der Abstand zum Rand.
*/
public void alignTo( Options.Direction dir, double buff ) { public void alignTo( Options.Direction dir, double buff ) {
Point2D anchorShape = Shape.getAnchorPoint(canvasWidth, canvasHeight, dir); Point2D anchorShape = Shape.getAnchorPoint(canvasWidth, canvasHeight, dir);
Point2D anchorThis = this.getAbsAnchorPoint(dir); Point2D anchorThis = this.getAbsAnchorPoint(dir);
@@ -423,20 +570,58 @@ public abstract class Shape extends BasicDrawable {
this.y += Math.abs(dir.y) * (anchorShape.getY() - anchorThis.getY()) + dir.y * buff; this.y += Math.abs(dir.y) * (anchorShape.getY() - anchorThis.getY()) + dir.y * buff;
} }
/**
* Bewegt den Ankerpunkt dieser Form in der angegebenen Richtung zum
* Gleichen Ankerpunkt der anderen Form.
*
* @param shape Die andere Form.
* @param dir Die Richtung des Ankerpunktes.
* @see #alignTo(Shape, Options.Direction, double)
*/
public void alignTo( Shape shape, Options.Direction dir ) { public void alignTo( Shape shape, Options.Direction dir ) {
alignTo(shape, dir, 0.0); alignTo(shape, dir, 0.0);
} }
/** /**
* Richtet die Form entlang der angegebenen Richtung an einer anderen Form * Richtet die Form entlang der angegebenen Richtung an einer anderen Form
* aus. Für {@code DOWN} wird beispielsweise die y-Koordinate der unteren * aus.
* Kante dieser Form an der unteren Kante der angegebenen Form {@code shape} * <p>
* ausgerichtet. Die x-Koordinate wird nicht verändert. {@code buff} gibt * {@code buff} gibt einen Abstand ab, um den diese From versetzt
* einen Abstand ab, um den diese From versetzt ausgerichtet werden soll. * ausgerichtet wird.
* <p>
* Für {@link #DOWN} wird beispielsweise die y-Koordinate der unteren Kante
* dieser Form an der unteren Kante von {@code shape} ausgerichtet. Die
* x-Koordinate wird in dem Fall nicht verändert.
* <p>
* Befinden sich die Formen beispielsweise in folgender Position:
* <pre>
* ┌─────┐
* │ │
* │ B │
* ┌─────┐ │ │
* │ │ └──D──┘
* │ A │
* │ │
* └──D──┘
* </pre>
* <p>
* <p>
* werden sie durch {@code alignTo} so positioniert:
* <pre>
* B.alignTo(A, EAST, 0) │ B.alignTo(A, EAST, 10)
* │
* ┌─────┐ ┌─────┐ │ ┌─────┐
* │ │ │ │ │ │ │ ┌─────┐
* │ A │ │ B │ │ │ A │ │ │
* │ │ │ │ │ │ │ │ B │
* └──D──┘ └──D──┘ │ └──D──┘ │ │
* │ └──D──┘
* │
* </pre>
* *
* @param shape * @param shape Die andere Form.
* @param dir * @param dir Die Richtung.
* @param buff * @param buff Der Abstand.
*/ */
public void alignTo( Shape shape, Options.Direction dir, double buff ) { public void alignTo( Shape shape, Options.Direction dir, double buff ) {
Point2D anchorShape = shape.getAbsAnchorPoint(dir); Point2D anchorShape = shape.getAbsAnchorPoint(dir);
@@ -446,16 +631,46 @@ public abstract class Shape extends BasicDrawable {
this.y += Math.abs(dir.y) * (anchorShape.getY() - anchorThis.getY()) + dir.y * buff; this.y += Math.abs(dir.y) * (anchorShape.getY() - anchorThis.getY()) + dir.y * buff;
} }
/**
* @param shape
* @param dir
* @see #nextTo(Shape, Options.Direction, double)
*/
public void nextTo( Shape shape, Options.Direction dir ) { public void nextTo( Shape shape, Options.Direction dir ) {
nextTo(shape, dir, DEFAULT_BUFFER); nextTo(shape, dir, DEFAULT_BUFFER);
} }
/** /**
* Bewegt die Form neben eine andere in Richtung des angegebenen * Bewegt die Form neben eine andere in Richtung des angegebenen
* Ankerpunktes. Im Gegensatz zu * Ankerpunktes.
* {@link #moveTo(Shape, Options.Direction, double)} wird die Breite bzw. * <p>
* Höhe der Formen berücksichtigt und die Formen so platziert, dass keine * Im Gegensatz zu {@link #moveTo(Shape, Options.Direction, double)} wird
* Überlappungen vorhanden sind. * die Breite bzw. Höhe der Formen berücksichtigt und die Formen so
* platziert, dass keine Überlappungen vorhanden sind.
* <p>
* Befinden sich die Formen zuvor in folgender Ausrichtung:
* <pre>
* ┌─────┐
* │ │
* W B │
* ┌──────┐ │ │
* │ │ └─────┘
* │ A E
* │ │
* └──────┘
* </pre>
* <p>
* bringt sie der Aufruf {@code B.nextTo(A, EAST, 0)} in diese Ausrichtung:
* <pre>
* B.nextTo(A, EAST, 0) │ B.nextTo(A, EAST, 10)
* │
* ┌─────┬─────┐ │ ┌─────┐ ┌─────┐
* │ │ │ │ │ │ │ │
* │ A │ B │ │ │ A │ │ B │
* │ │ │ │ │ │ │ │
* └─────┴─────┘ │ └─────┘ └─────┘
* │
* </pre>
* *
* @param shape * @param shape
* @param dir * @param dir
@@ -469,6 +684,19 @@ public abstract class Shape extends BasicDrawable {
this.y += (anchorShape.getY() - anchorThis.getY()) + dir.y * buff; this.y += (anchorShape.getY() - anchorThis.getY()) + dir.y * buff;
} }
@Override
public void setGradient( schule.ngb.zm.Color from, schule.ngb.zm.Color to, Options.Direction dir ) {
Point2D apDir = getAbsAnchorPoint(dir);
Point2D apInv = getAbsAnchorPoint(dir.inverse());
setGradient(apInv.getX(), apInv.getY(), from, apDir.getX(), apDir.getY(), to);
}
@Override
public void setGradient( schule.ngb.zm.Color from, schule.ngb.zm.Color to ) {
Point2D ap = getAbsAnchorPoint(CENTER);
setGradient(ap.getX(), ap.getY(), Math.min(ap.getX(), ap.getY()), from, to);
}
/*public void shear( double dx, double dy ) { /*public void shear( double dx, double dy ) {
verzerrung.shear(dx, dy); verzerrung.shear(dx, dy);
}*/ }*/

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.time.ZoneOffset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.function.Function; import java.util.function.*;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@@ -28,21 +28,23 @@ import static java.lang.Math.log;
import static schule.ngb.zm.Constants.random; import static schule.ngb.zm.Constants.random;
/** /**
* Hilfsklasse, um zufällige Beispieldaten zu erzeugen. * Eine Helferklasse, um zufällige Beispieldaten zu erzeugen.
* <p> * <p>
* Die Klasse kann verschiedene Arten realistischer Beispieldaten erzeugen, * Die Klasse kann verschiedene Arten realistischer Beispieldaten erzeugen.
* unter anderem Namen, E-Mail-Adressen, Passwörter oder Platzhalter-Bilder. * Unter anderem Namen, E-Mail-Adressen, Passwörter oder Platzhalter-Bilder.
*/ */
@SuppressWarnings( "unused" ) @SuppressWarnings( "unused" )
public final class Faker { public final class Faker {
/**
* URL, von der extern generierte Fake-Bilder geladen werden können.
* <p>
* Die URL wird als Format-String definiert mit zwei {@code %d}
* Platzhaltern, die durch die Breite und Höhe des gewünschten Bildes
* ersetzt werden.
*/
public static final String FAKE_IMG_URL = "https://loremflickr.com/%d/%d"; public static final String FAKE_IMG_URL = "https://loremflickr.com/%d/%d";
public static void main( String[] args ) {
String text = Faker.fakeText(2000, 8);
System.out.println(text);
}
/** /**
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger Benutzerdaten. * Erzeugt ein Array mit den angegebenen Anzahl zufälliger Benutzerdaten.
* <p> * <p>
@@ -76,8 +78,8 @@ public final class Faker {
/** /**
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger Namen (Vor- und * Erzeugt ein Array mit den angegebenen Anzahl zufälliger Namen im Format
* Nachname). * "Vorname Nachname".
* *
* @param count Anzahl der Beispieldaten. * @param count Anzahl der Beispieldaten.
* @return Ein Array mit den Beispieldaten. * @return Ein Array mit den Beispieldaten.
@@ -133,7 +135,7 @@ public final class Faker {
/** /**
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger * Erzeugt ein Array mit den angegebenen Anzahl zufälliger
* {@code Date}-Objekte. * {@code LocalDate}-Objekte, die ein Datum ohne Uhrzeit beschreiben.
* *
* @param count Anzahl der Beispieldaten. * @param count Anzahl der Beispieldaten.
* @return Ein Array mit den Beispieldaten. * @return Ein Array mit den Beispieldaten.
@@ -151,6 +153,14 @@ public final class Faker {
return result; return result;
} }
/**
* Erzeugt ein Array mit den angegebenen Anzahl zufälliger
* {@code LocalDateTime}-Objekte, die einen Zeitpunkt mit Dateum und Uhrzeit
* beschreiben,
*
* @param count Anzahl der Beispieldaten.
* @return Ein Array mit den Beispieldaten.
*/
public static LocalDateTime[] fakeDatetimes( int count ) { public static LocalDateTime[] fakeDatetimes( int count ) {
long nowEpoch = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC); long nowEpoch = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC);
long from = LocalDateTime.ofEpochSecond(nowEpoch - 18 * 365, 0, ZoneOffset.UTC).toEpochSecond(ZoneOffset.UTC); long from = LocalDateTime.ofEpochSecond(nowEpoch - 18 * 365, 0, ZoneOffset.UTC).toEpochSecond(ZoneOffset.UTC);
@@ -164,6 +174,17 @@ public final class Faker {
return result; return result;
} }
/**
* Erzeugt einen Blindtext mit der angegebenen Anzahl Worten, aufgeteilt in
* die angegebene Anzahl Absätze.
* <p>
* Abssätze werden duch einen doppelten Zeilenumbruch {@code \n\n}
* getrennt.
*
* @param words Anzahl Wörter im Text insgesamt.
* @param paragraphs Anzahl Absätze.
* @return Ein zufälliger Blindtext.
*/
public static String fakeText( int words, int paragraphs ) { public static String fakeText( int words, int paragraphs ) {
String basetext = ""; String basetext = "";
try( try(
@@ -197,6 +218,16 @@ public final class Faker {
return result; return result;
} }
/**
* Erzeugt ein Array mit der angegebenen Anzahl zufällig erzeugter Integer
* im angegebenen Bereich.
*
* @param count Anzahl der Zahlen im Array.
* @param min Untere Grenze der Zufallszahlen.
* @param max Obere Grenze der Zufallszahlen.
* @return Ein Array mit Zufallszahlen.
* @see Constants#random(int, int)
*/
public static int[] fakeIntArray( int count, int min, int max ) { public static int[] fakeIntArray( int count, int min, int max ) {
int[] arr = new int[count]; int[] arr = new int[count];
for( int i = 0; i < count; i++ ) { for( int i = 0; i < count; i++ ) {
@@ -205,14 +236,56 @@ public final class Faker {
return arr; return arr;
} }
/**
* Erzeugt eine Liste mit der angegebenen Anzahl zufällig erzeugter Integer
* im angegebenen Bereich.
* <p>
* Ist {@code list} ein Listenobjekt, werden dei Zahlen an diese angehängt.
* Wird {@code null} übergeben, wird eine neue {@link ArrayList} erzeugt.
*
* @param count Anzahl der erzeugten Zahlen.
* @param min Untere Grenze der Zufallszahlen.
* @param max Obere Grenze der Zufallszahlen.
* @param list Eine Liste, die befüllt werden soll, oder {@code null}.
* @return Ein Array mit Zufallszahlen.
* @see Constants#random(int, int)
*/
public static List<Integer> fakeIntegerList( int count, int min, int max, List<Integer> list ) { public static List<Integer> fakeIntegerList( int count, int min, int max, List<Integer> list ) {
if( list == null ) { List<Integer> result = (list == null) ? new ArrayList<>(count) : list;
list = new ArrayList<>(count); fakeIntegers(count, min, max, result::add);
return result;
} }
/**
* Erzeugt die angegebene Anzahl Zufallszahlen im angegebenen Bereich und
* übergibt sie an den angegebenen {@code Consumer}.
*
* Ein typischer Aufruf, um eine {@code #LinkedList} mit 100 Zufallszahlen
* zu erzeugen könnte so aussehen:
* <pre><code>
* List&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++ ) { for( int i = 0; i < count; i++ ) {
list.add(random(min, max)); con.accept(random(min, max));
} }
return list; }
public static <L> L fakeIntegers( int count, int min, int max, Supplier<L> sup, BiConsumer<L, Integer> con ) {
L result = sup.get();
for( int i = 0; i < count; i++ ) {
con.accept(result, random(min, max));
}
return result;
} }
@SuppressWarnings( "unchecked" ) @SuppressWarnings( "unchecked" )

View File

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

View File

@@ -6,35 +6,55 @@ import java.util.function.Supplier;
/** /**
* Statische Methoden, um Methodenparameter auf Gültigkeit zu prüfen. * Statische Methoden, um Methodenparameter auf Gültigkeit zu prüfen.
*/ */
@SuppressWarnings( "UnusedReturnValue" ) @SuppressWarnings( {"unused", "UnusedReturnValue"} )
public class Validator { public class Validator {
public static final <T> T requireNotNull( T obj ) { public static final <T> T requireNotNull( T obj, CharSequence paramName ) {
return Objects.requireNonNull(obj); return requireNotNull(obj, () -> "Parameter <%s> may not be null.".format(paramName.toString()));
} }
public static final <T> T requireNotNull( T obj, CharSequence msg ) { public static final <T> T requireNotNull( T obj, CharSequence paramName, CharSequence msg ) {
return Objects.requireNonNull(obj, msg::toString); return requireNotNull(obj, () -> msg.toString().format(paramName.toString()));
} }
public static final <T> T requireNotNull( T obj, Supplier<String> msg ) { public static final <T> T requireNotNull( T obj, Supplier<String> msg ) {
return Objects.requireNonNull(obj, msg); if( obj == null ) {
throw new NullPointerException(msg == null ? "Parameter may nut be null." : msg.get());
}
return obj;
} }
public static final String requireNotEmpty( String str ) { public static final String requireNotEmpty( String str, CharSequence paramName ) {
return requireNotEmpty(str, (Supplier<String>) null); return requireNotEmpty(str, () -> String.format("Parameter <%s> may not be empty string (<%s> provided)", paramName, str));
} }
public static final String requireNotEmpty( String str, CharSequence msg ) { public static final String requireNotEmpty( String str, CharSequence paramName, CharSequence msg ) {
return requireNotEmpty(str, msg::toString); return requireNotEmpty(str, () -> msg.toString().format(paramName.toString()));
} }
public static final String requireNotEmpty( String str, Supplier<String> msg ) { public static final String requireNotEmpty( String str, Supplier<String> msg ) {
if( str.isEmpty() ) if( str.isEmpty() ) {
throw new IllegalArgumentException(msg == null ? String.format("Parameter may not be empty string (<%s> provided)", str) : msg.get()); throw new IllegalArgumentException(msg == null ? String.format("Parameter may not be empty string (<%s> provided)", str) : msg.get());
}
return str; return str;
} }
public static final <T> T[] requireNotEmpty( T[] arr, CharSequence paramName ) {
return requireNotEmpty(arr, () -> String.format("Parameter <%s> may not be empty", paramName));
}
public static final <T> T[] requireNotEmpty( T[] arr, CharSequence paramName, CharSequence msg ) {
return requireNotEmpty(arr, () -> msg.toString().format(paramName.toString()));
}
public static final <T> T[] requireNotEmpty( T[] arr, Supplier<String> msg ) {
if( arr.length == 0 )
throw new IllegalArgumentException(msg == null ? "Parameter array may not be empty" : msg.get());
return arr;
}
public static final int requirePositive( int i ) { public static final int requirePositive( int i ) {
return requirePositive(i, (Supplier<String>) null); return requirePositive(i, (Supplier<String>) null);
} }
@@ -139,21 +159,6 @@ public class Validator {
} }
*/ */
public static final <T> T[] requireNotEmpty( T[] arr ) {
return requireNotEmpty(arr, (Supplier<String>) null);
}
public static final <T> T[] requireNotEmpty( T[] arr, CharSequence msg ) {
return requireNotEmpty(arr, msg::toString);
}
public static final <T> T[] requireNotEmpty( T[] arr, Supplier<String> msg ) {
if( arr.length == 0 )
throw new IllegalArgumentException(msg == null ? "Parameter array may not be empty" : msg.get());
return arr;
}
public static final <T> T[] requireSize( T[] arr, int size ) { public static final <T> T[] requireSize( T[] arr, int size ) {
return requireSize(arr, size, (Supplier<String>) null); return requireSize(arr, size, (Supplier<String>) null);
} }
@@ -168,20 +173,6 @@ public class Validator {
return arr; return arr;
} }
public static final <T> T[] requireValid( T[] arr ) {
return requireValid(arr, (Supplier<String>) null);
}
public static final <T> T[] requireValid( T[] arr, CharSequence msg ) {
return requireValid(arr, msg::toString);
}
public static final <T> T[] requireValid( T[] arr, Supplier<String> msg ) {
if( arr == null || arr.length > 0 )
throw new IllegalArgumentException(msg == null ? "Parameter array may not be null or empty" : msg.get());
return arr;
}
private Validator() { private Validator() {
} }

View File

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

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.Font;
import java.awt.FontFormatException; import java.awt.FontFormatException;
import java.awt.Image;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.MalformedURLException;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
/**
* Eine Helferklasse mit Klassenmethoden, um Schriftarten zu laden.
* <p>
* Schriftarten können von verschiedenen Quellen geladen werden. Schriftarten,
* die aus Dateien geladen wurden, werden in einem internen Cache gespeichert
* und nachfolgende Versuche, dieselbe Schriftart zu laden, werden aus dem Cache
* bedient.
*/
public class FontLoader { public class FontLoader {
private static final Map<String, Font> fontCache = new ConcurrentHashMap<>(); private static final Map<String, Font> fontCache = new ConcurrentHashMap<>();
@@ -22,11 +29,12 @@ public class FontLoader {
* <p> * <p>
* Die Methode kann eine Liste von Schriften bekommen und probiert diese * Die Methode kann eine Liste von Schriften bekommen und probiert diese
* nacheinander zu laden. Die erste Schrift, die Fehlerfrei geladen werden * nacheinander zu laden. Die erste Schrift, die Fehlerfrei geladen werden
* kann, wird zurückgegeben. Kann keine der Schriften geladen werden, ist das * kann, wird zurückgegeben. Kann keine der Schriften geladen werden, ist
* Ergebnis {@code null}. * das Ergebnis {@code null}.
* <p> * <p>
* Die gefundene Schrift wird unter ihrem Dateinamen in den Schriftenspeicher * Die gefundene Schrift wird unter ihrem Dateinamen in den
* geladen und kann danach in der Zeichenmaschine benutzt werden. * Schriftenspeicher geladen und kann danach in der Zeichenmaschine benutzt
* werden.
* <p> * <p>
* Eine Datei mit dem Namen "fonts/Font-Name.ttf" würde mit dem Namen * Eine Datei mit dem Namen "fonts/Font-Name.ttf" würde mit dem Namen
* "Font-Name" geladen und kann danach zum Beispiel in einem * "Font-Name" geladen und kann danach zum Beispiel in einem
@@ -53,7 +61,7 @@ public class FontLoader {
} }
public static Font loadFont( String name, String source ) { public static Font loadFont( String name, String source ) {
Validator.requireNotNull(source,"Font source may not be null"); Validator.requireNotNull(source, "Font source may not be null");
Validator.requireNotEmpty(source, "Font source may not be empty."); Validator.requireNotEmpty(source, "Font source may not be empty.");
if( fontCache.containsKey(name) ) { if( fontCache.containsKey(name) ) {
@@ -96,11 +104,12 @@ public class FontLoader {
* <p> * <p>
* Die Methode kann eine Liste von Schriften bekommen und probiert diese * Die Methode kann eine Liste von Schriften bekommen und probiert diese
* nacheinander zu laden. Die erste Schrift, die Fehlerfrei geladen werden * nacheinander zu laden. Die erste Schrift, die Fehlerfrei geladen werden
* kann, wird zurückgegeben. Kann keine der Schriften geladen werden, ist das * kann, wird zurückgegeben. Kann keine der Schriften geladen werden, ist
* Ergebnis {@code null}. * das Ergebnis {@code null}.
* <p> * <p>
* Die gefundene Schrift wird unter ihrem Dateinamen in den Schriftenspeicher * Die gefundene Schrift wird unter ihrem Dateinamen in den
* geladen und kann danach in der Zeichenmaschine benutzt werden. * Schriftenspeicher geladen und kann danach in der Zeichenmaschine benutzt
* werden.
* <p> * <p>
* Eine Datei mit dem Namen "fonts/Font-Name.ttf" würde mit dem Namen * Eine Datei mit dem Namen "fonts/Font-Name.ttf" würde mit dem Namen
* "Font-Name" geladen und kann danach zum Beispiel in einem * "Font-Name" geladen und kann danach zum Beispiel in einem
@@ -113,7 +122,7 @@ public class FontLoader {
* @see #loadFont(String, String) * @see #loadFont(String, String)
*/ */
public static Font loadFonts( String name, String... sources ) { public static Font loadFonts( String name, String... sources ) {
for( String fontSource: sources ) { for( String fontSource : sources ) {
// TODO: Ignore exceptions in this case and throw own at end? // TODO: Ignore exceptions in this case and throw own at end?
Font font = loadFont(name, fontSource); Font font = loadFont(name, fontSource);
if( font != null ) { if( font != null ) {

View File

@@ -18,6 +18,13 @@ import java.lang.ref.SoftReference;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
/**
* Eine Helferklasse mit Klassenmethoden, um Bilder zu laden.
* <p>
* Bilder können von verschiedenen Quellen als {@link Image} geladen werden. Die
* Objekte werden in einem internen Cache gespeichert und nachfolgende Versuche,
* dieselbe Quelle zu laden, werden aus dem Cache bedient.
*/
public final class ImageLoader { public final class ImageLoader {
public static boolean caching = true; public static boolean caching = true;

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.awt.Stroke;
import java.util.LinkedList; import java.util.LinkedList;
/**
* Eine Ebene, die {@link java.awt.Shape} Objekte zeichnet.
*
* Die Ebene ist für Tests der Kompatibilität mit Java-AWT gedacht.
*/
@SuppressWarnings( "unused" ) @SuppressWarnings( "unused" )
public final class Shape2DLayer extends Layer { public final class Shape2DLayer extends Layer {

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