mirror of
https://github.com/jneug/zeichenmaschine.git
synced 2026-10-12 01:45:18 +02:00
Refactorings und Javadoc
This commit is contained in:
@@ -14,6 +14,7 @@ import java.awt.image.ColorModel;
|
||||
* Eine Farbe hat außerdem einen Transparenzwert zwischen 0 (unsichtbar) und 255
|
||||
* (deckend).
|
||||
*/
|
||||
@SuppressWarnings( "unused" )
|
||||
public class Color implements Paint {
|
||||
|
||||
|
||||
@@ -151,7 +152,7 @@ public class Color implements Paint {
|
||||
* @param alpha Transparentwert zwischen 0 und 255.
|
||||
*/
|
||||
public Color( int red, int green, int blue, int alpha ) {
|
||||
rgba = ((alpha & 0xFF) << 24) | ((red & 0xFF) << 16) | ((green & 0xFF) << 8) | ((blue & 0xFF) << 0);
|
||||
rgba = ((alpha & 0xFF) << 24) | ((red & 0xFF) << 16) | ((green & 0xFF) << 8) | (blue & 0xFF);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -160,7 +161,7 @@ public class Color implements Paint {
|
||||
* @param color
|
||||
*/
|
||||
public Color( Color color ) {
|
||||
this(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha());
|
||||
this(color.getRGBA(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -208,8 +209,7 @@ public class Color implements Paint {
|
||||
* @return
|
||||
*/
|
||||
public static Color getRGBColor( int rgba ) {
|
||||
Color c = new Color(rgba, true);
|
||||
return c;
|
||||
return new Color(rgba, true);
|
||||
}
|
||||
|
||||
public static Color getHSBColor( double h, double s, double b ) {
|
||||
@@ -259,8 +259,6 @@ public class Color implements Paint {
|
||||
} else if( hexcode.length() == 8 ) {
|
||||
alpha = Integer.valueOf(hexcode.substring(6, 8), 16);
|
||||
hexcode = hexcode.substring(0, 6);
|
||||
} else {
|
||||
hexcode = hexcode;
|
||||
}
|
||||
|
||||
return Color.getRGBColor((alpha << 24) | Integer.valueOf(hexcode, 16));
|
||||
@@ -307,12 +305,12 @@ public class Color implements Paint {
|
||||
if( c == 0 ) {
|
||||
h_ = 0;
|
||||
} else if( max == r ) {
|
||||
h_ = (float) (g - b) / c;
|
||||
h_ = (g - b) / c;
|
||||
if( h_ < 0 ) h_ += 6.f;
|
||||
} else if( max == g ) {
|
||||
h_ = (float) (b - r) / c + 2.f;
|
||||
h_ = (b - r) / c + 2.f;
|
||||
} else if( max == b ) {
|
||||
h_ = (float) (r - g) / c + 4.f;
|
||||
h_ = (r - g) / c + 4.f;
|
||||
}
|
||||
float h = 60.f * h_;
|
||||
|
||||
@@ -484,7 +482,6 @@ public class Color implements Paint {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
/**
|
||||
* Prüft, ob ein anderes Objekt zu diesem gleich ist.
|
||||
*
|
||||
@@ -494,6 +491,7 @@ public class Color implements Paint {
|
||||
* @param obj Das zu vergleichende Objekt.
|
||||
* @return {@code true}, wenn die Objekte gleich sind, sonst {@code false}.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals( Object obj ) {
|
||||
if( obj == null ) {
|
||||
return false;
|
||||
|
||||
@@ -77,7 +77,7 @@ public abstract class Layer extends Constants implements Drawable, Updatable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die Resourcen der Ebene frei.
|
||||
* Gibt die Ressourcen der Ebene frei.
|
||||
*/
|
||||
public void dispose() {
|
||||
drawing.dispose();
|
||||
@@ -139,14 +139,14 @@ public abstract class Layer extends Constants implements Drawable, Updatable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeichnet den Puffer auf die Grafik-Instanz.
|
||||
* Zeichnet den Puffer auf den Grafikkontext.
|
||||
*
|
||||
* @param pGraphics
|
||||
* @param graphics Der Grafikkontext, auf den gezeichnet wird.
|
||||
*/
|
||||
@Override
|
||||
public void draw( Graphics2D pGraphics ) {
|
||||
public void draw( Graphics2D graphics ) {
|
||||
if( visible ) {
|
||||
pGraphics.drawImage(buffer, 0, 0, null);
|
||||
graphics.drawImage(buffer, 0, 0, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,8 @@ import static java.util.logging.Level.*;
|
||||
* Klasse nur genau ein {@code Log}-Objekt erstellt. Mehrere {@code Log}s nutzen
|
||||
* dann aber denselben {@code Logger}.
|
||||
* <p>
|
||||
* Die API orientiert sich lose an <a href="#">Log4j</a> und vereinfacht die
|
||||
* Die API orientiert sich lose an <a
|
||||
* href="https://logging.apache.org/log4j/2.x/">Log4j</a> und vereinfacht die
|
||||
* Nutzung der Java logging API für die häufigsten Anwendungsfälle.
|
||||
*/
|
||||
public final class Log {
|
||||
@@ -133,7 +134,6 @@ public final class Log {
|
||||
|
||||
if( System.getProperty("java.util.logging.SimpleFormatter.format") == null
|
||||
&& LogManager.getLogManager().getProperty("java.util.logging.SimpleFormatter.format") == null ) {
|
||||
// System.setProperty("java.util.logging.SimpleFormatter.format", DEFAULT_LOG_FORMAT);
|
||||
rootLogger.addHandler(new StreamHandler(System.err, new LogFormatter()) {
|
||||
@Override
|
||||
public synchronized void publish( final LogRecord record ) {
|
||||
@@ -141,15 +141,6 @@ public final class Log {
|
||||
flush();
|
||||
}
|
||||
});
|
||||
// rootLogger.setUseParentHandlers(false);
|
||||
}
|
||||
if( rootLogger.getUseParentHandlers() ) {
|
||||
// This logger was not configured somewhere else
|
||||
// Add a Handler and Formatter
|
||||
|
||||
//StreamHandler rootHandler = new StreamHandler(System.out, new SimpleFormatter());
|
||||
//rootLogger.addHandler(rootHandler);
|
||||
//rootLogger.setUseParentHandlers(false);
|
||||
}
|
||||
|
||||
LOGGING_INIT = true;
|
||||
@@ -208,26 +199,15 @@ public final class Log {
|
||||
}
|
||||
}
|
||||
|
||||
private String inferCallerName() {
|
||||
StackTraceElement[] trace = new Throwable().getStackTrace();
|
||||
for( int i = 0; i < trace.length; i++ ) {
|
||||
/// if( trace[i].getClassName().equals(sourceClass.getName()) ) {
|
||||
if( !trace[i].getClassName().equals(Log.class.getName()) ) {
|
||||
return trace[i].getMethodName();
|
||||
}
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
private void doLog( Level level, final Throwable throwable, final Supplier<String> msgSupplier ) {
|
||||
String clazz = sourceClass.getName();
|
||||
String method = "unknown";
|
||||
|
||||
StackTraceElement[] trace = new Throwable().getStackTrace();
|
||||
for( int i = 0; i < trace.length; i++ ) {
|
||||
if( !trace[i].getClassName().equals(Log.class.getName()) ) {
|
||||
clazz = trace[i].getClassName();
|
||||
method = trace[i].getMethodName();
|
||||
for( StackTraceElement stackTraceElement : trace ) {
|
||||
if( !stackTraceElement.getClassName().equals(Log.class.getName()) ) {
|
||||
clazz = stackTraceElement.getClassName();
|
||||
method = stackTraceElement.getMethodName();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -368,7 +348,7 @@ public final class Log {
|
||||
pw.close();
|
||||
throwable = sw.toString();
|
||||
}
|
||||
return String.format(DEFAULT_LOG_FORMAT,
|
||||
return String.format(DEFAULT_DEBUG_FORMAT,
|
||||
zdt,
|
||||
source,
|
||||
record.getLoggerName(),
|
||||
|
||||
@@ -10,6 +10,7 @@ import java.util.Random;
|
||||
* href="https://adrianb.io/2014/08/09/perlinnoise.html">Beschreibung von
|
||||
* FLAFLA2</a> implementiert.
|
||||
*/
|
||||
@SuppressWarnings( "unused" )
|
||||
public class Noise {
|
||||
|
||||
private static final int N = 256;
|
||||
@@ -42,7 +43,7 @@ public class Noise {
|
||||
/**
|
||||
* Initialisiert diesen Perlin-Noise mit dem angegebenen Zufallsgenerator.
|
||||
*
|
||||
* @param rand
|
||||
* @param rand Ein Zufallsgenerator-Objekt.
|
||||
*/
|
||||
public Noise( Random rand ) {
|
||||
init(rand);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package schule.ngb.zm.util;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@SuppressWarnings( "UnusedReturnValue" )
|
||||
public class Validator {
|
||||
|
||||
public static final <T> T requireNotNull( T obj ) {
|
||||
@@ -147,7 +147,7 @@ public class Validator {
|
||||
|
||||
public static final <T> T[] requireNotEmpty( T[] arr, Supplier<String> msg ) {
|
||||
if( arr.length == 0 )
|
||||
throw new IllegalArgumentException(msg == null ? String.format("Parameter array may not be empty") : msg.get());
|
||||
throw new IllegalArgumentException(msg == null ? "Parameter array may not be empty" : msg.get());
|
||||
return arr;
|
||||
}
|
||||
|
||||
@@ -161,7 +161,7 @@ public class Validator {
|
||||
|
||||
public static final <T> T[] requireSize( T[] arr, int size, Supplier<String> msg ) {
|
||||
if( arr.length != size )
|
||||
throw new IllegalArgumentException(msg == null ? String.format("Parameter array must have <%d> elements (<%d> provided)", size) : msg.get());
|
||||
throw new IllegalArgumentException(msg == null ? String.format("Parameter array must have <%d> elements (<%d> provided)", size, arr.length) : msg.get());
|
||||
return arr;
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ public class Validator {
|
||||
|
||||
public static final <T> T[] requireValid( T[] arr, Supplier<String> msg ) {
|
||||
if( arr == null || arr.length > 0 )
|
||||
throw new IllegalArgumentException(msg == null ? String.format("Parameter array may not be null or empty") : msg.get());
|
||||
throw new IllegalArgumentException(msg == null ? "Parameter array may not be null or empty" : msg.get());
|
||||
return arr;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,9 +9,9 @@ import java.util.function.BiConsumer;
|
||||
|
||||
public class EventDispatcher<E, L extends Listener<E>> {
|
||||
|
||||
private CopyOnWriteArraySet<L> listeners;
|
||||
private final CopyOnWriteArraySet<L> listeners;
|
||||
|
||||
private ConcurrentMap<String, BiConsumer<E, L>> eventRegistry;
|
||||
private final ConcurrentMap<String, BiConsumer<E, L>> eventRegistry;
|
||||
|
||||
public EventDispatcher() {
|
||||
listeners = new CopyOnWriteArraySet<>();
|
||||
@@ -49,9 +49,7 @@ public class EventDispatcher<E, L extends Listener<E>> {
|
||||
|
||||
if( eventRegistered(eventKey) ) {
|
||||
final BiConsumer<E, L> dispatcher = eventRegistry.get(eventKey);
|
||||
listeners.forEach(( listener ) -> {
|
||||
dispatcher.accept(event, listener);
|
||||
});
|
||||
listeners.forEach(( listener ) -> dispatcher.accept(event, listener));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,4 @@ package schule.ngb.zm.util.events;
|
||||
|
||||
public interface Listener<E> {
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public final class FileLoader {
|
||||
|
||||
@@ -34,7 +35,7 @@ public final class FileLoader {
|
||||
LOG.error(ex, "Error while loading lines from source <%s>", source);
|
||||
}
|
||||
|
||||
return Collections.EMPTY_LIST;
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public static String loadText( String source ) {
|
||||
@@ -56,12 +57,11 @@ public final class FileLoader {
|
||||
}
|
||||
|
||||
public static String[][] loadCsv( String source, char separator, boolean skipFirst, Charset charset ) {
|
||||
try {
|
||||
int n = skipFirst ? 1 : 0;
|
||||
return Files
|
||||
try( Stream<String> lines = Files
|
||||
.lines(Paths.get(ResourceStreamProvider.getResourceURL(source).toURI()), charset)
|
||||
.skip(n)
|
||||
.map(
|
||||
) {
|
||||
int n = skipFirst ? 1 : 0;
|
||||
return lines.skip(n).map(
|
||||
( line ) -> line.split(Character.toString(separator))
|
||||
).toArray(String[][]::new);
|
||||
} catch( IOException | URISyntaxException ex ) {
|
||||
@@ -76,12 +76,11 @@ public final class FileLoader {
|
||||
}
|
||||
|
||||
public static double[][] loadValues( String source, char separator, boolean skipFirst, Charset charset ) {
|
||||
try {
|
||||
int n = skipFirst ? 1 : 0;
|
||||
return Files
|
||||
try( Stream<String> lines = Files
|
||||
.lines(Paths.get(ResourceStreamProvider.getResourceURL(source).toURI()), charset)
|
||||
.skip(n)
|
||||
.map(
|
||||
) {
|
||||
int n = skipFirst ? 1 : 0;
|
||||
return lines.skip(n).map(
|
||||
( line ) -> Arrays
|
||||
.stream(line.split(Character.toString(separator)))
|
||||
.mapToDouble(
|
||||
|
||||
@@ -20,8 +20,8 @@ public class FontLoader {
|
||||
* <p>
|
||||
* Die Schrift wird unter ihrem Dateinamen in den Schriftenspeicher geladen
|
||||
* und kann danach in der Zeichenmaschine benutzt werden.
|
||||
*
|
||||
* Ein Datei mit dem Namen "fonts/Font-Name.ttf" würde mit dem Namen
|
||||
* <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
|
||||
* {@link schule.ngb.zm.shapes.Text} mit {@code text.setFont("Font-Name");}
|
||||
* verwendet werden.
|
||||
@@ -34,7 +34,7 @@ public class FontLoader {
|
||||
// Dateipfad entfernen
|
||||
int lastIndex = source.lastIndexOf(File.separatorChar);
|
||||
if( lastIndex > -1 ) {
|
||||
source.substring(lastIndex + 1);
|
||||
source = source.substring(lastIndex + 1);
|
||||
}
|
||||
// Dateiendung entfernen
|
||||
lastIndex = name.lastIndexOf('.');
|
||||
@@ -57,7 +57,7 @@ public class FontLoader {
|
||||
|
||||
// Look for System fonts
|
||||
Font font = Font.decode(source);
|
||||
if( font != null && source.toLowerCase().contains(font.getFamily().toLowerCase()) ) {
|
||||
if( source.toLowerCase().contains(font.getFamily().toLowerCase()) ) {
|
||||
fontCache.put(name, font);
|
||||
fontCache.put(source, font);
|
||||
LOG.debug("Loaded system font for <%s>.", source);
|
||||
|
||||
@@ -22,9 +22,9 @@ public final class ImageLoader {
|
||||
|
||||
public static boolean caching = true;
|
||||
|
||||
private static Map<String, SoftReference<BufferedImage>> imageCache = new ConcurrentHashMap<>();
|
||||
private static final Map<String, SoftReference<BufferedImage>> imageCache = new ConcurrentHashMap<>();
|
||||
|
||||
private static SoftReference<BufferedImage> NOCACHE = new SoftReference<>(null);
|
||||
private static final SoftReference<BufferedImage> NOCACHE = new SoftReference<>(null);
|
||||
|
||||
/**
|
||||
* Lädt ein Bild von der angegebenen Quelle {@code source}.
|
||||
@@ -48,12 +48,12 @@ public final class ImageLoader {
|
||||
|
||||
/**
|
||||
* Lädt ein Bild von der angegebenen Quelle <var>source</var> und gibt das
|
||||
* Bild zurück oder {@code null}, wenn das Bild nicht geladen werden
|
||||
* konnte. Ist ein Bild mit der angegebenen Quelle im Cache, wird das
|
||||
* gespeicherte Bild zurückgegeben. Dies kann mit {@code caching = false}
|
||||
* verhindert werden.
|
||||
* Bild zurück oder {@code null}, wenn das Bild nicht geladen werden konnte.
|
||||
* Ist ein Bild mit der angegebenen Quelle im Cache, wird das gespeicherte
|
||||
* Bild zurückgegeben. Dies kann mit {@code caching = false} verhindert
|
||||
* werden.
|
||||
* <p>
|
||||
* Wurde das Chaching global deaktiviert, kann mit <code>caching =
|
||||
* Wurde das Caching global deaktiviert, kann mit <code>caching =
|
||||
* true</code> das Bild trotzdem aus dem Cache geladen werden, wenn es
|
||||
* vorhanden ist.
|
||||
*
|
||||
@@ -113,9 +113,9 @@ public final class ImageLoader {
|
||||
}
|
||||
|
||||
/**
|
||||
* Speiechert das angegebene Bild unter dem angegebenen Namen im Cache.
|
||||
* Speichert das angegebene Bild unter dem angegebenen Namen im Cache.
|
||||
* <p>
|
||||
* Exisitert zu {@code name} schon ein Bild im Zwischenspeicher
|
||||
* Existiert zu {@code name} schon ein Bild im Zwischenspeicher
|
||||
* ({@code ImageLoader.isCached(name) == true}), dann wird dieses
|
||||
* überschrieben.
|
||||
* <p>
|
||||
@@ -149,10 +149,8 @@ public final class ImageLoader {
|
||||
* @param name Name des Bildes im Cache.
|
||||
*/
|
||||
public static void invalidateCache( final String name ) {
|
||||
if( imageCache.containsKey(name) ) {
|
||||
imageCache.remove(name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Speichert ein Bild als {@link SoftReference} im Cache.
|
||||
@@ -211,7 +209,7 @@ public final class ImageLoader {
|
||||
* Bild zurückgegeben und nicht komplett neu geladen.
|
||||
* <p>
|
||||
* <b>Wichtig:</b> Bildreferenzen, die aus dem Cache geladen werden,
|
||||
* verweisen alle auf dasselbe Objekt. Änderungen schalgen sich daher in
|
||||
* verweisen alle auf dasselbe Objekt. Änderungen schlagen sich daher in
|
||||
* allen anderen Versionen des Bildes nieder (inklusive dem Bild im
|
||||
* Zwischenspeicher). Für Änderungen sollte daher immer
|
||||
* {@link #copyImage(BufferedImage) eine Kopie} des Bildes erstellt werden:
|
||||
@@ -302,7 +300,7 @@ public final class ImageLoader {
|
||||
* @param file Die Zieldatei.
|
||||
* @param overwriteIfExists Bei {@code true} wird eine vorhandene Datei
|
||||
* überschrieben, bei {@code false} wird eine {@link IOException} geworfen,
|
||||
* wenn die Datei schon exisitiert.
|
||||
* wenn die Datei schon existiert.
|
||||
* @throws NullPointerException Falls {@code image} oder {@code file}
|
||||
* {@code null} ist.
|
||||
* @throws IOException Falls es einen Fehler beim Speichern gab.
|
||||
@@ -335,7 +333,7 @@ public final class ImageLoader {
|
||||
* @param file Die Zieldatei.
|
||||
* @param overwriteIfExists Bei {@code true} wird eine vorhandene Datei
|
||||
* überschrieben, bei {@code false} wird eine {@link IOException} geworfen,
|
||||
* wenn die Datei schon exisitiert.
|
||||
* wenn die Datei schon existiert.
|
||||
* @throws NullPointerException Falls {@code image} oder {@code file}
|
||||
* {@code null} ist.
|
||||
* @throws IOException Falls es einen Fehler beim Speichern gab.
|
||||
|
||||
@@ -63,7 +63,6 @@ public class ResourceStreamProvider {
|
||||
}
|
||||
} catch( FileNotFoundException fnfex ) {
|
||||
// Somehow an exception occurred, but we still try other sources
|
||||
in = null;
|
||||
}
|
||||
// File does not exist, try other means
|
||||
// load ressource relative to .class-file
|
||||
@@ -151,6 +150,7 @@ public class ResourceStreamProvider {
|
||||
|
||||
/**
|
||||
* Ver
|
||||
*
|
||||
* @param source
|
||||
* @return
|
||||
* @throws IOException
|
||||
|
||||
@@ -5,35 +5,18 @@ import schule.ngb.zm.Zeichenmaschine;
|
||||
|
||||
public abstract class FrameSynchronizedTask extends Task {
|
||||
|
||||
private static Thread mainThread;
|
||||
|
||||
private static final Thread getMainThread() {
|
||||
if( mainThread == null ) {
|
||||
mainThread = Thread.currentThread();
|
||||
if( !mainThread.getName().equals(Constants.APP_NAME) ) {
|
||||
// Need to search for main Zeichenthread ...
|
||||
}
|
||||
}
|
||||
return mainThread;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
initialize();
|
||||
running = true;
|
||||
|
||||
Object lock = Zeichenmaschine.globalSyncLock;
|
||||
final Object lock = Zeichenmaschine.globalSyncLock;
|
||||
|
||||
// start of thread in ms
|
||||
final long start = System.currentTimeMillis();
|
||||
// current time in ns
|
||||
long beforeTime = System.nanoTime();
|
||||
// store for deltas
|
||||
long overslept = 0L;
|
||||
// internal counters for tick and runtime
|
||||
int _tick = 0;
|
||||
|
||||
double delta = 0.0;
|
||||
// internal counters for tick and delta time
|
||||
int _tick;
|
||||
double delta;
|
||||
|
||||
while( running ) {
|
||||
// delta in seconds
|
||||
@@ -53,10 +36,7 @@ public abstract class FrameSynchronizedTask extends Task {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
running = false;
|
||||
done = true;
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ public abstract class RateLimitedTask extends Task {
|
||||
// store for deltas
|
||||
long overslept = 0L;
|
||||
// delta in ms
|
||||
double delta = 0;
|
||||
double delta;
|
||||
|
||||
running = true;
|
||||
while( running ) {
|
||||
@@ -47,10 +47,7 @@ public abstract class RateLimitedTask extends Task {
|
||||
// Did we sleep to long?
|
||||
overslept = (System.nanoTime() - afterTime) - sleep;
|
||||
}
|
||||
|
||||
running = false;
|
||||
done = true;
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
|
||||
@@ -10,11 +10,6 @@ import java.util.concurrent.*;
|
||||
*/
|
||||
public class TaskRunner {
|
||||
|
||||
private static final int POOL_SIZE = 4;
|
||||
|
||||
private static final int SHUTDOWN_TIME = 100;
|
||||
|
||||
|
||||
private static TaskRunner runner;
|
||||
|
||||
public static TaskRunner getTaskRunner() {
|
||||
|
||||
Reference in New Issue
Block a user