diff --git a/src/main/java/net/rubygrapefruit/platform/Main.java b/src/main/java/net/rubygrapefruit/platform/Main.java index a0e4779..679ccce 100755 --- a/src/main/java/net/rubygrapefruit/platform/Main.java +++ b/src/main/java/net/rubygrapefruit/platform/Main.java @@ -37,8 +37,8 @@ public class Main { System.out.print("[bold]"); terminal.normal(); System.out.println(" [normal]"); - System.out.println(); + System.out.println("COLORS"); for (Terminal.Color color : Terminal.Color.values()) { terminal.foreground(color); @@ -48,8 +48,8 @@ public class Main { terminal.normal(); System.out.println(); } - System.out.println(); + terminal.reset(); if (terminal.supportsCursorMotion()) { @@ -73,10 +73,10 @@ public class Main { terminal.cursorDown(1); terminal.cursorStartOfLine(); terminal.clearToEndOfLine(); + terminal.foreground(Terminal.Color.Blue).bold(); System.out.println("done!"); + System.out.println(); } } - - System.out.println(); } } diff --git a/src/main/java/net/rubygrapefruit/platform/Native.java b/src/main/java/net/rubygrapefruit/platform/Native.java index fb08944..465b84b 100755 --- a/src/main/java/net/rubygrapefruit/platform/Native.java +++ b/src/main/java/net/rubygrapefruit/platform/Native.java @@ -9,8 +9,8 @@ import java.io.File; /** * Provides access to the native integrations. Use {@link #get(Class)} to load a particular integration. */ +@ThreadSafe public class Native { - private static final Object lock = new Object(); private static boolean loaded; private Native() { @@ -22,8 +22,9 @@ public class Native { * @param extractDir The directory to extract native resources into. May be null, in which case a default is * selected. */ + @ThreadSafe static public void init(File extractDir) { - synchronized (lock) { + synchronized (Native.class) { if (!loaded) { Platform platform = Platform.current(); try { @@ -54,6 +55,7 @@ public class Native { * machine. * @throws NativeException On failure to load the native integration. */ + @ThreadSafe public static T get(Class type) throws NativeIntegrationUnavailableException, NativeException { init(null); diff --git a/src/main/java/net/rubygrapefruit/platform/Terminals.java b/src/main/java/net/rubygrapefruit/platform/Terminals.java index 3c82711..a0c5378 100644 --- a/src/main/java/net/rubygrapefruit/platform/Terminals.java +++ b/src/main/java/net/rubygrapefruit/platform/Terminals.java @@ -3,8 +3,11 @@ package net.rubygrapefruit.platform; /** * Provides access to the terminal/console. * - * Supported on Linux, OS X, Windows. + *

On UNIX based platforms, this provides access to the terminal. On Windows platforms, this provides access to the + * console. + *

*/ +@ThreadSafe public interface Terminals extends NativeIntegration { /** * System outputs. @@ -16,6 +19,7 @@ public interface Terminals extends NativeIntegration { * * @throws NativeException On failure. */ + @ThreadSafe boolean isTerminal(Output output) throws NativeException; /** @@ -23,5 +27,6 @@ public interface Terminals extends NativeIntegration { * * @throws NativeException When the output is not attached to a terminal. */ + @ThreadSafe Terminal getTerminal(Output output) throws NativeException; } diff --git a/src/main/java/net/rubygrapefruit/platform/ThreadSafe.java b/src/main/java/net/rubygrapefruit/platform/ThreadSafe.java new file mode 100644 index 0000000..4391182 --- /dev/null +++ b/src/main/java/net/rubygrapefruit/platform/ThreadSafe.java @@ -0,0 +1,7 @@ +package net.rubygrapefruit.platform; + +/** + * Indicates that the given class or method is thread safe. + */ +public @interface ThreadSafe { +} diff --git a/src/main/java/net/rubygrapefruit/platform/internal/AbstractTerminal.java b/src/main/java/net/rubygrapefruit/platform/internal/AbstractTerminal.java index f52b874..aebc5d6 100755 --- a/src/main/java/net/rubygrapefruit/platform/internal/AbstractTerminal.java +++ b/src/main/java/net/rubygrapefruit/platform/internal/AbstractTerminal.java @@ -3,15 +3,5 @@ package net.rubygrapefruit.platform.internal; import net.rubygrapefruit.platform.Terminal; public abstract class AbstractTerminal implements Terminal { - public final void init() { - doInit(); - Runtime.getRuntime().addShutdownHook(new Thread(){ - @Override - public void run() { - reset(); - } - }); - } - - protected abstract void doInit(); + protected abstract void init(); } diff --git a/src/main/java/net/rubygrapefruit/platform/internal/AbstractTerminals.java b/src/main/java/net/rubygrapefruit/platform/internal/AbstractTerminals.java index 01fce60..1b6bcc1 100644 --- a/src/main/java/net/rubygrapefruit/platform/internal/AbstractTerminals.java +++ b/src/main/java/net/rubygrapefruit/platform/internal/AbstractTerminals.java @@ -14,8 +14,14 @@ public abstract class AbstractTerminals implements Terminals { } if (current == null) { - AbstractTerminal terminal = createTerminal(output); + final AbstractTerminal terminal = createTerminal(output); terminal.init(); + Runtime.getRuntime().addShutdownHook(new Thread(){ + @Override + public void run() { + terminal.reset(); + } + }); currentlyOpen = output; current = terminal; } diff --git a/src/main/java/net/rubygrapefruit/platform/internal/TerminfoTerminal.java b/src/main/java/net/rubygrapefruit/platform/internal/TerminfoTerminal.java index 1c6a9c4..916a78a 100755 --- a/src/main/java/net/rubygrapefruit/platform/internal/TerminfoTerminal.java +++ b/src/main/java/net/rubygrapefruit/platform/internal/TerminfoTerminal.java @@ -2,22 +2,18 @@ package net.rubygrapefruit.platform.internal; import net.rubygrapefruit.platform.NativeException; import net.rubygrapefruit.platform.Terminal; -import net.rubygrapefruit.platform.Terminals; import net.rubygrapefruit.platform.TerminalSize; +import net.rubygrapefruit.platform.Terminals; import net.rubygrapefruit.platform.internal.jni.PosixTerminalFunctions; import net.rubygrapefruit.platform.internal.jni.TerminfoFunctions; -import java.io.PrintStream; - public class TerminfoTerminal extends AbstractTerminal { private final Terminals.Output output; - private final PrintStream stream; private final TerminalCapabilities capabilities = new TerminalCapabilities(); private Color foreground; public TerminfoTerminal(Terminals.Output output) { this.output = output; - stream = output == Terminals.Output.Stdout ? System.out : System.err; } @Override @@ -26,8 +22,7 @@ public class TerminfoTerminal extends AbstractTerminal { } @Override - protected void doInit() { - stream.flush(); + protected void init() { FunctionResult result = new FunctionResult(); TerminfoFunctions.initTerminal(output.ordinal(), capabilities, result); if (result.isFailed()) { @@ -62,7 +57,6 @@ public class TerminfoTerminal extends AbstractTerminal { return this; } - stream.flush(); FunctionResult result = new FunctionResult(); TerminfoFunctions.foreground(color.ordinal(), result); if (result.isFailed()) { @@ -78,7 +72,6 @@ public class TerminfoTerminal extends AbstractTerminal { return this; } - stream.flush(); FunctionResult result = new FunctionResult(); TerminfoFunctions.bold(result); if (result.isFailed()) { @@ -97,7 +90,6 @@ public class TerminfoTerminal extends AbstractTerminal { } public Terminal reset() { - stream.flush(); FunctionResult result = new FunctionResult(); TerminfoFunctions.reset(result); if (result.isFailed()) { @@ -107,7 +99,6 @@ public class TerminfoTerminal extends AbstractTerminal { } public Terminal cursorDown(int count) { - stream.flush(); FunctionResult result = new FunctionResult(); TerminfoFunctions.down(count, result); if (result.isFailed()) { @@ -117,7 +108,6 @@ public class TerminfoTerminal extends AbstractTerminal { } public Terminal cursorUp(int count) { - stream.flush(); FunctionResult result = new FunctionResult(); TerminfoFunctions.up(count, result); if (result.isFailed()) { @@ -127,7 +117,6 @@ public class TerminfoTerminal extends AbstractTerminal { } public Terminal cursorLeft(int count) { - stream.flush(); FunctionResult result = new FunctionResult(); TerminfoFunctions.left(count, result); if (result.isFailed()) { @@ -137,7 +126,6 @@ public class TerminfoTerminal extends AbstractTerminal { } public Terminal cursorRight(int count) { - stream.flush(); FunctionResult result = new FunctionResult(); TerminfoFunctions.right(count, result); if (result.isFailed()) { @@ -147,7 +135,6 @@ public class TerminfoTerminal extends AbstractTerminal { } public Terminal cursorStartOfLine() throws NativeException { - stream.flush(); FunctionResult result = new FunctionResult(); TerminfoFunctions.startLine(result); if (result.isFailed()) { @@ -157,7 +144,6 @@ public class TerminfoTerminal extends AbstractTerminal { } public Terminal clearToEndOfLine() throws NativeException { - stream.flush(); FunctionResult result = new FunctionResult(); TerminfoFunctions.clearToEndOfLine(result); if (result.isFailed()) { diff --git a/src/main/java/net/rubygrapefruit/platform/internal/TerminfoTerminals.java b/src/main/java/net/rubygrapefruit/platform/internal/TerminfoTerminals.java index 856a319..b70ac9f 100755 --- a/src/main/java/net/rubygrapefruit/platform/internal/TerminfoTerminals.java +++ b/src/main/java/net/rubygrapefruit/platform/internal/TerminfoTerminals.java @@ -1,7 +1,10 @@ package net.rubygrapefruit.platform.internal; +import net.rubygrapefruit.platform.Terminals; import net.rubygrapefruit.platform.internal.jni.PosixTerminalFunctions; +import java.io.PrintStream; + public class TerminfoTerminals extends AbstractTerminals { public boolean isTerminal(Output output) { return PosixTerminalFunctions.isatty(output.ordinal()); @@ -9,6 +12,7 @@ public class TerminfoTerminals extends AbstractTerminals { @Override protected AbstractTerminal createTerminal(Output output) { - return new TerminfoTerminal(output); + PrintStream stream = output == Terminals.Output.Stdout ? System.out : System.err; + return new WrapperTerminal(stream, new TerminfoTerminal(output)); } } diff --git a/src/main/java/net/rubygrapefruit/platform/internal/WindowsTerminal.java b/src/main/java/net/rubygrapefruit/platform/internal/WindowsTerminal.java index ff275df..52cbd90 100755 --- a/src/main/java/net/rubygrapefruit/platform/internal/WindowsTerminal.java +++ b/src/main/java/net/rubygrapefruit/platform/internal/WindowsTerminal.java @@ -19,7 +19,7 @@ public class WindowsTerminal extends AbstractTerminal { } @Override - protected void doInit() { + protected void init() { FunctionResult result = new FunctionResult(); WindowsConsoleFunctions.initConsole(output.ordinal(), result); if (result.isFailed()) { diff --git a/src/main/java/net/rubygrapefruit/platform/internal/WindowsTerminals.java b/src/main/java/net/rubygrapefruit/platform/internal/WindowsTerminals.java index 20c8cfd..2f4a240 100755 --- a/src/main/java/net/rubygrapefruit/platform/internal/WindowsTerminals.java +++ b/src/main/java/net/rubygrapefruit/platform/internal/WindowsTerminals.java @@ -1,8 +1,11 @@ package net.rubygrapefruit.platform.internal; import net.rubygrapefruit.platform.NativeException; +import net.rubygrapefruit.platform.Terminals; import net.rubygrapefruit.platform.internal.jni.WindowsConsoleFunctions; +import java.io.PrintStream; + public class WindowsTerminals extends AbstractTerminals { public boolean isTerminal(Output output) { FunctionResult result = new FunctionResult(); @@ -16,6 +19,7 @@ public class WindowsTerminals extends AbstractTerminals { @Override protected AbstractTerminal createTerminal(Output output) { - return new WindowsTerminal(output); + PrintStream stream = output == Terminals.Output.Stdout ? System.out : System.err; + return new WrapperTerminal(stream, new WindowsTerminal(output)); } } diff --git a/src/main/java/net/rubygrapefruit/platform/internal/WrapperTerminal.java b/src/main/java/net/rubygrapefruit/platform/internal/WrapperTerminal.java new file mode 100644 index 0000000..96f372d --- /dev/null +++ b/src/main/java/net/rubygrapefruit/platform/internal/WrapperTerminal.java @@ -0,0 +1,137 @@ +package net.rubygrapefruit.platform.internal; + +import net.rubygrapefruit.platform.NativeException; +import net.rubygrapefruit.platform.Terminal; +import net.rubygrapefruit.platform.TerminalSize; + +import java.io.PrintStream; + +/** + * A {@link Terminal} implementation that wraps another to add thread safety. + */ +public class WrapperTerminal extends AbstractTerminal { + private final AbstractTerminal terminal; + private final PrintStream stream; + private final Object lock = new Object(); + + public WrapperTerminal(PrintStream stream, AbstractTerminal terminal) { + this.stream = stream; + this.terminal = terminal; + } + + @Override + protected void init() { + stream.flush(); + terminal.init(); + } + + @Override + public TerminalSize getTerminalSize() throws NativeException { + return terminal.getTerminalSize(); + } + + @Override + public boolean supportsColor() { + return terminal.supportsColor(); + } + + @Override + public boolean supportsCursorMotion() { + return terminal.supportsCursorMotion(); + } + + @Override + public boolean supportsTextAttributes() { + return terminal.supportsTextAttributes(); + } + + @Override + public Terminal normal() throws NativeException { + stream.flush(); + synchronized (lock) { + terminal.normal(); + } + return this; + } + + @Override + public Terminal bold() throws NativeException { + stream.flush(); + synchronized (lock) { + terminal.bold(); + } + return this; + } + + @Override + public Terminal reset() throws NativeException { + stream.flush(); + synchronized (lock) { + terminal.reset(); + } + return this; + } + + @Override + public Terminal foreground(Color color) throws NativeException { + stream.flush(); + synchronized (lock) { + terminal.foreground(color); + } + return this; + } + + @Override + public Terminal cursorLeft(int count) throws NativeException { + stream.flush(); + synchronized (lock) { + terminal.cursorLeft(count); + } + return this; + } + + @Override + public Terminal cursorRight(int count) throws NativeException { + stream.flush(); + synchronized (lock) { + terminal.cursorRight(count); + } + return this; + } + + @Override + public Terminal cursorUp(int count) throws NativeException { + stream.flush(); + synchronized (lock) { + terminal.cursorUp(count); + } + return this; + } + + @Override + public Terminal cursorDown(int count) throws NativeException { + stream.flush(); + synchronized (lock) { + terminal.cursorDown(count); + } + return this; + } + + @Override + public Terminal cursorStartOfLine() throws NativeException { + stream.flush(); + synchronized (lock) { + terminal.cursorStartOfLine(); + } + return this; + } + + @Override + public Terminal clearToEndOfLine() throws NativeException { + stream.flush(); + synchronized (lock) { + terminal.clearToEndOfLine(); + } + return this; + } +}