/*
* Copyright 2015 Red Hat, Inc.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*
*
* Copyright (c) 2015 The original author or authors
* ------------------------------------------------------
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*
*/
package io.vertx.ext.shell.support;
import io.termd.core.tty.TtyConnection;
import io.termd.core.tty.TtyEvent;
import io.termd.core.util.Helper;
import io.termd.core.util.Vector;
import io.vertx.core.Context;
import io.vertx.core.Vertx;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
/**
* @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
*/
public class TestTtyConnection implements TtyConnection {
private final Context context;
private Consumer<String> terminalTypeHandler;
private Consumer<Vector> sizeHandler;
private BiConsumer<TtyEvent, Integer> eventHandler;
private Consumer<int[]> stdinHandler;
private volatile Consumer<Void> closeHandler;
private final StringBuilder out = new StringBuilder();
private volatile boolean closed;
private final CountDownLatch closeLatch = new CountDownLatch(1);
private volatile long lastAccessedTime;
public TestTtyConnection(Vertx vertx) {
this.context = vertx.getOrCreateContext();
}
@Override
public Charset inputCharset() {
return StandardCharsets.UTF_8;
}
@Override
public Charset outputCharset() {
return StandardCharsets.UTF_8;
}
@Override
public long lastAccessedTime() {
return lastAccessedTime;
}
@Override
public String terminalType() {
return "xterm";
}
@Override
public Vector size() {
return new Vector(40, 20);
}
@Override
public Consumer<String> getTerminalTypeHandler() {
return terminalTypeHandler;
}
@Override
public void setTerminalTypeHandler(Consumer<String> handler) {
terminalTypeHandler = handler;
}
@Override
public Consumer<Vector> getSizeHandler() {
return sizeHandler;
}
@Override
public void setSizeHandler(Consumer<Vector> handler) {
sizeHandler = handler;
}
@Override
public BiConsumer<TtyEvent, Integer> getEventHandler() {
return eventHandler;
}
@Override
public void setEventHandler(BiConsumer<TtyEvent, Integer> handler) {
eventHandler = handler;
}
@Override
public Consumer<int[]> getStdinHandler() {
return stdinHandler;
}
@Override
public void setStdinHandler(Consumer<int[]> consumer) {
this.stdinHandler = consumer;
}
@Override
public Consumer<int[]> stdoutHandler() {
return codePoints -> {
synchronized (TestTtyConnection.this) {
Helper.appendCodePoints(codePoints, out());
notify();
}
};
}
@Override
public Consumer<Void> getCloseHandler() {
return closeHandler;
}
@Override
public void setCloseHandler(Consumer<Void> handler) {
closeHandler = handler;
}
@Override
public void close() {
if (!closed) {
closed = true;
if (closeHandler != null) {
context.runOnContext(v -> closeHandler.accept(null));
}
closeLatch.countDown();
}
}
@Override
public void execute(Runnable task) {
context.runOnContext(v -> task.run());
}
@Override
public void schedule(Runnable task, long delay, TimeUnit unit) {
throw new UnsupportedOperationException();
}
public void sendEvent(TtyEvent event) {
int c;
switch (event) {
case INTR:
c = 'C' - 64;
break;
case SUSP:
c = 'Z' - 64;
break;
case EOF:
c = 'D' - 64;
break;
default:
throw new AssertionError();
}
context.runOnContext(v -> {
eventHandler.accept(event, c);
});
}
public void read(String s) {
lastAccessedTime = System.currentTimeMillis();
context.runOnContext(v -> {
stdinHandler.accept(Helper.toCodePoints(s));
});
}
public synchronized String checkWritten(String s) {
while (true) {
int l = Math.min(s.length(), out.length());
String actual = out.substring(0, l);
String expected = s.substring(0, l);
if (actual.equals(expected)) {
out.replace(0, l, "");
s = s.substring(l);
} else {
return "Was expecting <" + actual + "> to be equals to <" + expected + ">";
}
if (s.length() == 0) {
break;
} else {
try {
wait(5000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new AssertionError(e);
}
}
}
return null;
}
public synchronized void assertWritten(String s) {
String report = checkWritten(s);
if (report != null) {
throw new AssertionError(report);
}
}
public StringBuilder out() {
return out;
}
public boolean isClosed() {
return closed;
}
public CountDownLatch getCloseLatch() {
return closeLatch;
}
}