Fixed thread safety for Terminal implementations. Changed Terminal implementation on windows to Flush System.out/System.err

This commit is contained in:
Adam Murdoch
2012-09-08 09:08:43 +10:00
parent 1c3ab6a289
commit ab1487d215
11 changed files with 179 additions and 38 deletions

View File

@@ -37,8 +37,8 @@ public class Main {
System.out.print("[bold]"); System.out.print("[bold]");
terminal.normal(); terminal.normal();
System.out.println(" [normal]"); System.out.println(" [normal]");
System.out.println(); System.out.println();
System.out.println("COLORS"); System.out.println("COLORS");
for (Terminal.Color color : Terminal.Color.values()) { for (Terminal.Color color : Terminal.Color.values()) {
terminal.foreground(color); terminal.foreground(color);
@@ -48,8 +48,8 @@ public class Main {
terminal.normal(); terminal.normal();
System.out.println(); System.out.println();
} }
System.out.println(); System.out.println();
terminal.reset(); terminal.reset();
if (terminal.supportsCursorMotion()) { if (terminal.supportsCursorMotion()) {
@@ -73,10 +73,10 @@ public class Main {
terminal.cursorDown(1); terminal.cursorDown(1);
terminal.cursorStartOfLine(); terminal.cursorStartOfLine();
terminal.clearToEndOfLine(); terminal.clearToEndOfLine();
terminal.foreground(Terminal.Color.Blue).bold();
System.out.println("done!"); System.out.println("done!");
System.out.println();
} }
} }
System.out.println();
} }
} }

View File

@@ -9,8 +9,8 @@ import java.io.File;
/** /**
* Provides access to the native integrations. Use {@link #get(Class)} to load a particular integration. * Provides access to the native integrations. Use {@link #get(Class)} to load a particular integration.
*/ */
@ThreadSafe
public class Native { public class Native {
private static final Object lock = new Object();
private static boolean loaded; private static boolean loaded;
private Native() { 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 * @param extractDir The directory to extract native resources into. May be null, in which case a default is
* selected. * selected.
*/ */
@ThreadSafe
static public void init(File extractDir) { static public void init(File extractDir) {
synchronized (lock) { synchronized (Native.class) {
if (!loaded) { if (!loaded) {
Platform platform = Platform.current(); Platform platform = Platform.current();
try { try {
@@ -54,6 +55,7 @@ public class Native {
* machine. * machine.
* @throws NativeException On failure to load the native integration. * @throws NativeException On failure to load the native integration.
*/ */
@ThreadSafe
public static <T extends NativeIntegration> T get(Class<T> type) public static <T extends NativeIntegration> T get(Class<T> type)
throws NativeIntegrationUnavailableException, NativeException { throws NativeIntegrationUnavailableException, NativeException {
init(null); init(null);

View File

@@ -3,8 +3,11 @@ package net.rubygrapefruit.platform;
/** /**
* Provides access to the terminal/console. * Provides access to the terminal/console.
* *
* Supported on Linux, OS X, Windows. * <p>On UNIX based platforms, this provides access to the terminal. On Windows platforms, this provides access to the
* console.
* </p>
*/ */
@ThreadSafe
public interface Terminals extends NativeIntegration { public interface Terminals extends NativeIntegration {
/** /**
* System outputs. * System outputs.
@@ -16,6 +19,7 @@ public interface Terminals extends NativeIntegration {
* *
* @throws NativeException On failure. * @throws NativeException On failure.
*/ */
@ThreadSafe
boolean isTerminal(Output output) throws NativeException; 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. * @throws NativeException When the output is not attached to a terminal.
*/ */
@ThreadSafe
Terminal getTerminal(Output output) throws NativeException; Terminal getTerminal(Output output) throws NativeException;
} }

View File

@@ -0,0 +1,7 @@
package net.rubygrapefruit.platform;
/**
* Indicates that the given class or method is thread safe.
*/
public @interface ThreadSafe {
}

View File

@@ -3,15 +3,5 @@ package net.rubygrapefruit.platform.internal;
import net.rubygrapefruit.platform.Terminal; import net.rubygrapefruit.platform.Terminal;
public abstract class AbstractTerminal implements Terminal { public abstract class AbstractTerminal implements Terminal {
public final void init() { protected abstract void init();
doInit();
Runtime.getRuntime().addShutdownHook(new Thread(){
@Override
public void run() {
reset();
}
});
}
protected abstract void doInit();
} }

View File

@@ -14,8 +14,14 @@ public abstract class AbstractTerminals implements Terminals {
} }
if (current == null) { if (current == null) {
AbstractTerminal terminal = createTerminal(output); final AbstractTerminal terminal = createTerminal(output);
terminal.init(); terminal.init();
Runtime.getRuntime().addShutdownHook(new Thread(){
@Override
public void run() {
terminal.reset();
}
});
currentlyOpen = output; currentlyOpen = output;
current = terminal; current = terminal;
} }

View File

@@ -2,22 +2,18 @@ package net.rubygrapefruit.platform.internal;
import net.rubygrapefruit.platform.NativeException; import net.rubygrapefruit.platform.NativeException;
import net.rubygrapefruit.platform.Terminal; import net.rubygrapefruit.platform.Terminal;
import net.rubygrapefruit.platform.Terminals;
import net.rubygrapefruit.platform.TerminalSize; import net.rubygrapefruit.platform.TerminalSize;
import net.rubygrapefruit.platform.Terminals;
import net.rubygrapefruit.platform.internal.jni.PosixTerminalFunctions; import net.rubygrapefruit.platform.internal.jni.PosixTerminalFunctions;
import net.rubygrapefruit.platform.internal.jni.TerminfoFunctions; import net.rubygrapefruit.platform.internal.jni.TerminfoFunctions;
import java.io.PrintStream;
public class TerminfoTerminal extends AbstractTerminal { public class TerminfoTerminal extends AbstractTerminal {
private final Terminals.Output output; private final Terminals.Output output;
private final PrintStream stream;
private final TerminalCapabilities capabilities = new TerminalCapabilities(); private final TerminalCapabilities capabilities = new TerminalCapabilities();
private Color foreground; private Color foreground;
public TerminfoTerminal(Terminals.Output output) { public TerminfoTerminal(Terminals.Output output) {
this.output = output; this.output = output;
stream = output == Terminals.Output.Stdout ? System.out : System.err;
} }
@Override @Override
@@ -26,8 +22,7 @@ public class TerminfoTerminal extends AbstractTerminal {
} }
@Override @Override
protected void doInit() { protected void init() {
stream.flush();
FunctionResult result = new FunctionResult(); FunctionResult result = new FunctionResult();
TerminfoFunctions.initTerminal(output.ordinal(), capabilities, result); TerminfoFunctions.initTerminal(output.ordinal(), capabilities, result);
if (result.isFailed()) { if (result.isFailed()) {
@@ -62,7 +57,6 @@ public class TerminfoTerminal extends AbstractTerminal {
return this; return this;
} }
stream.flush();
FunctionResult result = new FunctionResult(); FunctionResult result = new FunctionResult();
TerminfoFunctions.foreground(color.ordinal(), result); TerminfoFunctions.foreground(color.ordinal(), result);
if (result.isFailed()) { if (result.isFailed()) {
@@ -78,7 +72,6 @@ public class TerminfoTerminal extends AbstractTerminal {
return this; return this;
} }
stream.flush();
FunctionResult result = new FunctionResult(); FunctionResult result = new FunctionResult();
TerminfoFunctions.bold(result); TerminfoFunctions.bold(result);
if (result.isFailed()) { if (result.isFailed()) {
@@ -97,7 +90,6 @@ public class TerminfoTerminal extends AbstractTerminal {
} }
public Terminal reset() { public Terminal reset() {
stream.flush();
FunctionResult result = new FunctionResult(); FunctionResult result = new FunctionResult();
TerminfoFunctions.reset(result); TerminfoFunctions.reset(result);
if (result.isFailed()) { if (result.isFailed()) {
@@ -107,7 +99,6 @@ public class TerminfoTerminal extends AbstractTerminal {
} }
public Terminal cursorDown(int count) { public Terminal cursorDown(int count) {
stream.flush();
FunctionResult result = new FunctionResult(); FunctionResult result = new FunctionResult();
TerminfoFunctions.down(count, result); TerminfoFunctions.down(count, result);
if (result.isFailed()) { if (result.isFailed()) {
@@ -117,7 +108,6 @@ public class TerminfoTerminal extends AbstractTerminal {
} }
public Terminal cursorUp(int count) { public Terminal cursorUp(int count) {
stream.flush();
FunctionResult result = new FunctionResult(); FunctionResult result = new FunctionResult();
TerminfoFunctions.up(count, result); TerminfoFunctions.up(count, result);
if (result.isFailed()) { if (result.isFailed()) {
@@ -127,7 +117,6 @@ public class TerminfoTerminal extends AbstractTerminal {
} }
public Terminal cursorLeft(int count) { public Terminal cursorLeft(int count) {
stream.flush();
FunctionResult result = new FunctionResult(); FunctionResult result = new FunctionResult();
TerminfoFunctions.left(count, result); TerminfoFunctions.left(count, result);
if (result.isFailed()) { if (result.isFailed()) {
@@ -137,7 +126,6 @@ public class TerminfoTerminal extends AbstractTerminal {
} }
public Terminal cursorRight(int count) { public Terminal cursorRight(int count) {
stream.flush();
FunctionResult result = new FunctionResult(); FunctionResult result = new FunctionResult();
TerminfoFunctions.right(count, result); TerminfoFunctions.right(count, result);
if (result.isFailed()) { if (result.isFailed()) {
@@ -147,7 +135,6 @@ public class TerminfoTerminal extends AbstractTerminal {
} }
public Terminal cursorStartOfLine() throws NativeException { public Terminal cursorStartOfLine() throws NativeException {
stream.flush();
FunctionResult result = new FunctionResult(); FunctionResult result = new FunctionResult();
TerminfoFunctions.startLine(result); TerminfoFunctions.startLine(result);
if (result.isFailed()) { if (result.isFailed()) {
@@ -157,7 +144,6 @@ public class TerminfoTerminal extends AbstractTerminal {
} }
public Terminal clearToEndOfLine() throws NativeException { public Terminal clearToEndOfLine() throws NativeException {
stream.flush();
FunctionResult result = new FunctionResult(); FunctionResult result = new FunctionResult();
TerminfoFunctions.clearToEndOfLine(result); TerminfoFunctions.clearToEndOfLine(result);
if (result.isFailed()) { if (result.isFailed()) {

View File

@@ -1,7 +1,10 @@
package net.rubygrapefruit.platform.internal; package net.rubygrapefruit.platform.internal;
import net.rubygrapefruit.platform.Terminals;
import net.rubygrapefruit.platform.internal.jni.PosixTerminalFunctions; import net.rubygrapefruit.platform.internal.jni.PosixTerminalFunctions;
import java.io.PrintStream;
public class TerminfoTerminals extends AbstractTerminals { public class TerminfoTerminals extends AbstractTerminals {
public boolean isTerminal(Output output) { public boolean isTerminal(Output output) {
return PosixTerminalFunctions.isatty(output.ordinal()); return PosixTerminalFunctions.isatty(output.ordinal());
@@ -9,6 +12,7 @@ public class TerminfoTerminals extends AbstractTerminals {
@Override @Override
protected AbstractTerminal createTerminal(Output output) { 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));
} }
} }

View File

@@ -19,7 +19,7 @@ public class WindowsTerminal extends AbstractTerminal {
} }
@Override @Override
protected void doInit() { protected void init() {
FunctionResult result = new FunctionResult(); FunctionResult result = new FunctionResult();
WindowsConsoleFunctions.initConsole(output.ordinal(), result); WindowsConsoleFunctions.initConsole(output.ordinal(), result);
if (result.isFailed()) { if (result.isFailed()) {

View File

@@ -1,8 +1,11 @@
package net.rubygrapefruit.platform.internal; package net.rubygrapefruit.platform.internal;
import net.rubygrapefruit.platform.NativeException; import net.rubygrapefruit.platform.NativeException;
import net.rubygrapefruit.platform.Terminals;
import net.rubygrapefruit.platform.internal.jni.WindowsConsoleFunctions; import net.rubygrapefruit.platform.internal.jni.WindowsConsoleFunctions;
import java.io.PrintStream;
public class WindowsTerminals extends AbstractTerminals { public class WindowsTerminals extends AbstractTerminals {
public boolean isTerminal(Output output) { public boolean isTerminal(Output output) {
FunctionResult result = new FunctionResult(); FunctionResult result = new FunctionResult();
@@ -16,6 +19,7 @@ public class WindowsTerminals extends AbstractTerminals {
@Override @Override
protected AbstractTerminal createTerminal(Output output) { 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));
} }
} }

View File

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