bug 328566: enhanced telnet support over the equinox console
diff --git a/org.eclipse.virgo.osgi.console/.classpath b/org.eclipse.virgo.osgi.console/.classpath
new file mode 100644
index 0000000..bbd454f
--- /dev/null
+++ b/org.eclipse.virgo.osgi.console/.classpath
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
+ <classpathentry kind="src" path="src/main/java"/>
+ <classpathentry kind="src" path="src/test/java"/>
+ <classpathentry kind="var" path="OSGI_IVY_CACHE/org.junit/com.springsource.org.junit/4.7.0/com.springsource.org.junit-4.7.0.jar" sourcepath="/OSGI_IVY_CACHE/org.junit/com.springsource.org.junit/4.7.0/com.springsource.org.junit-sources-4.7.0.jar"/>
+ <classpathentry kind="var" path="OSGI_IVY_CACHE/org.eclipse.osgi/org.eclipse.osgi/3.7.0.v20101022/org.eclipse.osgi-3.7.0.v20101022.jar" sourcepath="/OSGI_IVY_CACHE/org.eclipse.osgi/org.eclipse.osgi/3.7.0.v20101022/org.eclipse.osgi-sources-3.7.0.v20101022.jar"/>
+ <classpathentry kind="var" path="OSGI_IVY_CACHE/org.easymock/com.springsource.org.easymock/2.3.0/com.springsource.org.easymock-2.3.0.jar" sourcepath="/OSGI_IVY_CACHE/org.easymock/com.springsource.org.easymock/2.3.0/com.springsource.org.easymock-sources-2.3.0.jar"/>
+ <classpathentry kind="var" path="OSGI_IVY_CACHE/org.eclipse.virgo.teststubs/org.eclipse.virgo.teststubs.osgi/2.2.0.D-20101207145338/org.eclipse.virgo.teststubs.osgi-2.2.0.D-20101207145338.jar" sourcepath="OSGI_IVY_CACHE/org.eclipse.virgo.teststubs/org.eclipse.virgo.teststubs.osgi/2.2.0.D-20101207145338/org.eclipse.virgo.teststubs.osgi-sources-2.2.0.D-20101207145338.jar"/>
+ <classpathentry kind="var" path="OSGI_IVY_CACHE/org.aspectj/com.springsource.org.aspectj.runtime/1.6.6.RELEASE/com.springsource.org.aspectj.runtime-1.6.6.RELEASE.jar" sourcepath="/OSGI_IVY_CACHE/org.aspectj/com.springsource.org.aspectj.runtime/1.6.6.RELEASE/com.springsource.org.aspectj.runtime-sources-1.6.6.RELEASE.jar"/>
+ <classpathentry kind="output" path="target/classes"/>
+</classpath>
diff --git a/org.eclipse.virgo.osgi.console/.project b/org.eclipse.virgo.osgi.console/.project
new file mode 100644
index 0000000..b8d3068
--- /dev/null
+++ b/org.eclipse.virgo.osgi.console/.project
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.eclipse.virgo.osgi.console</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.ManifestBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.SchemaBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.pde.PluginNature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
diff --git a/org.eclipse.virgo.osgi.console/build.xml b/org.eclipse.virgo.osgi.console/build.xml
new file mode 100644
index 0000000..0c6ddcb
--- /dev/null
+++ b/org.eclipse.virgo.osgi.console/build.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project name="org.eclipse.virgo.osgi.console">
+
+ <property file="${basedir}/../build.properties"/>
+ <property file="${basedir}/../build.versions"/>
+ <!--import file="${basedir}/smoke-test.xml"/-->
+ <import file="${basedir}/../virgo-build/standard/default.xml"/>
+
+ <!--target name="test.do" depends="quality-common.test.do, smoke-test"/-->
+
+</project>
diff --git a/org.eclipse.virgo.osgi.console/ivy.xml b/org.eclipse.virgo.osgi.console/ivy.xml
new file mode 100644
index 0000000..10c01c4
--- /dev/null
+++ b/org.eclipse.virgo.osgi.console/ivy.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?xml-stylesheet type="text/xsl" href="http://ivyrep.jayasoft.org/ivy-doc.xsl"?>
+<ivy-module
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:noNamespaceSchemaLocation="http://incubator.apache.org/ivy/schemas/ivy.xsd"
+ version="1.3">
+
+ <info organisation="org.eclipse.virgo.osgi" module="${ant.project.name}" />
+
+ <configurations>
+ <include file="${virgo.build.dir}/common/default-ivy-configurations.xml"/>
+ </configurations>
+
+ <publications>
+ <artifact name="${ant.project.name}"/>
+ <artifact name="${ant.project.name}-sources" type="src" ext="jar"/>
+ </publications>
+
+ <dependencies>
+ <dependency org="org.eclipse.osgi" name="org.eclipse.osgi" rev="${org.eclipse.osgi}" conf="compile->compile"/>
+ <dependency org="org.junit" name="com.springsource.org.junit" rev="${org.junit}" conf="test->runtime"/>
+ <dependency org="org.easymock" name="com.springsource.org.easymock" rev="${org.easymock}" conf="test->runtime"/>
+ <dependency org="org.eclipse.virgo.teststubs" name="org.eclipse.virgo.teststubs.osgi" rev="${org.eclipse.virgo.teststubs}" conf="test->runtime"/>
+
+ </dependencies>
+
+</ivy-module>
diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/common/ConsoleInputStream.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/common/ConsoleInputStream.java
new file mode 100644
index 0000000..33ea0c0
--- /dev/null
+++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/common/ConsoleInputStream.java
@@ -0,0 +1,86 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial contribution
+ ******************************************************************************/
+
+package org.eclipse.virgo.osgi.console.common;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+
+/**
+ * This class serves as an input stream, which wraps the actual input (e.g. from the telnet) and buffers the lines.
+ */
+public class ConsoleInputStream extends InputStream {
+
+ private final ArrayList<byte[]> buffer = new ArrayList<byte[]>();
+
+ private byte[] current;
+
+ private int pos;
+
+ private boolean isClosed;
+
+ public synchronized int read() {
+ while (current == null && buffer.isEmpty() && !isClosed) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ return -1;
+ }
+ }
+ if (isClosed) {
+ return -1;
+ }
+
+ try {
+ if (current == null) {
+ current = buffer.remove(0);
+ return current[pos++] & 0xFF;
+ } else {
+
+ return current[pos++] & 0xFF;
+ }
+ } finally {
+ if (current != null) {
+ if (pos == current.length) {
+ current = null;
+ pos = 0;
+ }
+ }
+ }
+
+ }
+
+ public int read(byte b[], int off, int len) throws IOException {
+ if (len == 0) {
+ return len;
+ }
+ int i = read();
+ if (i == -1) {
+ return -1;
+ }
+ b[off] = (byte) i;
+ return 1;
+ }
+
+ public synchronized void close() throws IOException {
+ isClosed = true;
+ notifyAll();
+ }
+
+ public synchronized void add(byte[] data) {
+ if (data.length > 0) {
+ buffer.add(data);
+ notify();
+ }
+ }
+
+}
diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/common/ConsoleOutputStream.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/common/ConsoleOutputStream.java
new file mode 100644
index 0000000..16a4d76
--- /dev/null
+++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/common/ConsoleOutputStream.java
@@ -0,0 +1,145 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial contribution
+ ******************************************************************************/
+
+package org.eclipse.virgo.osgi.console.common;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * This class wraps the actual output stream (e.g., a socket output stream) and is responsible for buffering and
+ * flushing the characters to the actual output stream.
+ */
+public class ConsoleOutputStream extends OutputStream {
+
+ /**
+ * A size of the used buffer.
+ */
+ public static final int BUFFER_SIZE = 2048;
+
+ public final static byte CR = (byte) '\r';
+
+ public final static byte LF = (byte) '\n';
+
+ OutputStream out;
+
+ OutputStream oldOut;
+
+ private boolean isEcho = true;
+
+ private boolean queueing = false;
+
+ private byte prevByte;
+
+ private byte[] buffer;
+
+ private int pos;
+
+ /**
+ * Initiates with instance of the output stream to which it will send data. Here it writes to a socket output
+ * stream.
+ *
+ * @param out OutputStream for console output
+ */
+ public ConsoleOutputStream(OutputStream out) {
+ this.out = out;
+ buffer = new byte[BUFFER_SIZE];
+ pos = 0;
+ }
+
+ /**
+ * An implementation of the corresponding abstract method in OutputStream.
+ */
+ public synchronized void write(int i) throws IOException {
+
+ if (!queueing) {
+ if (isEcho) {
+ if (i == '\r' || i == '\0') {
+ queueing = true;
+ prevByte = (byte) i;
+ } else if (i == '\n') {
+ add(CR);
+ add(LF);
+ } else {
+ add(i);
+ }
+ }
+ } else { // awaiting '\n' AFTER '\r', and '\b' AFTER '\0'
+ if (prevByte == '\0' && i == '\b') {
+ isEcho = !isEcho;
+ } else if (isEcho) {
+ if (prevByte == '\r' && i == '\n') {
+ add(CR);
+ add(LF);
+ } else {
+ add(CR);
+ add(LF);
+ add(i);
+ }
+ }
+
+ queueing = false;
+ flush();
+ }
+
+ }
+
+ /**
+ * Empties the buffer and sends data to the socket output stream.
+ *
+ * @throws IOException
+ */
+ public synchronized void flush() throws IOException {
+ if (pos > 0) {
+ out.write(buffer, 0, pos);
+ pos = 0;
+ }
+ }
+
+ /**
+ * Adds a variable of type integer to the buffer.
+ *
+ * @param i integer to add
+ * @throws java.io.IOException if there are problems adding the integer
+ */
+ private void add(int i) throws IOException {
+ buffer[pos] = (byte) i;
+ pos++;
+
+ if (pos == buffer.length) {
+ flush();
+ }
+ }
+
+ /**
+ * Closes this OutputStream.
+ *
+ * @throws IOException
+ */
+ public void close() throws IOException {
+ out.close();
+ }
+
+ /**
+ * Substitutes the output stream. The old one is stored so that it can be restored later.
+ *
+ * @param newOut new output stream to use.
+ */
+ public void setOutput(OutputStream newOut) {
+ if (newOut != null) {
+ oldOut = out;
+ out = newOut;
+ } else {
+ out = oldOut;
+ }
+
+ }
+}
diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/common/InputHandler.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/common/InputHandler.java
new file mode 100644
index 0000000..06bd6e6
--- /dev/null
+++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/common/InputHandler.java
@@ -0,0 +1,76 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial contribution
+ ******************************************************************************/
+
+package org.eclipse.virgo.osgi.console.common;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.eclipse.virgo.osgi.console.common.ConsoleInputStream;
+import org.eclipse.virgo.osgi.console.common.Scanner;
+
+/**
+ * This class represents a generic handler of content, read from some input stream. It reads from the stream, and passes
+ * what is read to a processor, which performs some actions on the content, eventually writing to an output stream. This
+ * handler should be customized with a concrete content processor.
+ */
+public abstract class InputHandler extends Thread {
+
+ protected Scanner inputScanner;
+
+ protected OutputStream out;
+
+ protected ConsoleInputStream in;
+
+ protected InputStream input;
+
+ protected byte[] buffer;
+
+ protected static final int MAX_SIZE = 2048;
+
+ public InputHandler(InputStream input, ConsoleInputStream in, OutputStream out) {
+ this.input = input;
+ this.in = in;
+ this.out = out;
+ buffer = new byte[MAX_SIZE];
+ }
+
+ public void run() {
+ int count;
+ try {
+ while ((count = input.read(buffer)) > -1) {
+ for (int i = 0; i < count; i++) {
+ inputScanner.scan(buffer[i]);
+ }
+ }
+ } catch (IOException e) {
+ // Printing stack trace is not needed since the streams are closed immediately
+ // do nothing
+ } finally {
+ try {
+ in.close();
+ } catch (IOException e1) {
+ // do nothing
+ }
+ try {
+ out.close();
+ } catch (IOException e1) {
+ // do nothing
+ }
+ }
+ }
+
+ public Scanner getScanner() {
+ return inputScanner;
+ }
+
+}
diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/common/KEYS.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/common/KEYS.java
new file mode 100644
index 0000000..0a1636a
--- /dev/null
+++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/common/KEYS.java
@@ -0,0 +1,16 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial contribution
+ ******************************************************************************/
+
+package org.eclipse.virgo.osgi.console.common;
+
+public enum KEYS {
+ UP, DOWN, RIGHT, LEFT, CENTER, HOME, END, PGUP, PGDN, INS, DEL, UNFINISHED, UNKNOWN
+}
diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/common/Scanner.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/common/Scanner.java
new file mode 100644
index 0000000..903f480
--- /dev/null
+++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/common/Scanner.java
@@ -0,0 +1,151 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial contribution
+ ******************************************************************************/
+
+package org.eclipse.virgo.osgi.console.common;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.virgo.osgi.console.common.ConsoleInputStream;
+import org.eclipse.virgo.osgi.console.common.KEYS;
+import org.eclipse.virgo.osgi.console.telnet.ANSITerminalTypeMappings;
+import org.eclipse.virgo.osgi.console.telnet.SCOTerminalTypeMappings;
+import org.eclipse.virgo.osgi.console.telnet.TerminalTypeMappings;
+import org.eclipse.virgo.osgi.console.telnet.VT100TerminalTypeMappings;
+import org.eclipse.virgo.osgi.console.telnet.VT220TerminalTypeMappings;
+import org.eclipse.virgo.osgi.console.telnet.VT320TerminalTypeMappings;
+
+/**
+ * A common superclass for content processor for the telnet protocol and for command line editing (processing delete,
+ * backspace, arrows, command history, etc.).
+ */
+public abstract class Scanner {
+
+ protected static final byte BS = 8;
+
+ private byte BACKSPACE;
+
+ protected static final byte LF = 10;
+
+ protected static final byte CR = 13;
+
+ protected static final byte ESC = 27;
+
+ protected static final byte SPACE = 32;
+
+ private byte DEL;
+
+ protected static final byte MAX_CHAR = 127;
+
+ protected static final String DEFAULT_TTYPE = File.separatorChar == '/' ? "XTERM" : "ANSI";
+
+ protected OutputStream toTelnet;
+
+ protected ConsoleInputStream toShell;
+
+ protected Map<String, KEYS> currentEscapesToKey;
+
+ protected final Map<String, TerminalTypeMappings> supportedEscapeSequences;
+
+ protected String[] escapes;
+
+ public Scanner(ConsoleInputStream toShell, OutputStream toTelnet) {
+ this.toShell = toShell;
+ this.toTelnet = toTelnet;
+ supportedEscapeSequences = new HashMap<String, TerminalTypeMappings>();
+ supportedEscapeSequences.put("ANSI", new ANSITerminalTypeMappings());
+ supportedEscapeSequences.put("VT100", new VT100TerminalTypeMappings());
+ VT220TerminalTypeMappings vtMappings = new VT220TerminalTypeMappings();
+ supportedEscapeSequences.put("VT220", new VT220TerminalTypeMappings());
+ supportedEscapeSequences.put("XTERM", vtMappings);
+ supportedEscapeSequences.put("VT320", new VT320TerminalTypeMappings());
+ supportedEscapeSequences.put("SCO", new SCOTerminalTypeMappings());
+ }
+
+ public abstract void scan(int b) throws IOException;
+
+ protected void echo(int b) throws IOException {
+ toTelnet.write(b);
+ }
+
+ protected void flush() throws IOException {
+ toTelnet.flush();
+ }
+
+ protected KEYS checkEscape(String possibleEsc) {
+ if (currentEscapesToKey.get(possibleEsc) != null) {
+ return currentEscapesToKey.get(possibleEsc);
+ }
+
+ for (String escape : escapes) {
+ if (escape.startsWith(possibleEsc)) {
+ return KEYS.UNFINISHED;
+ }
+ }
+ return KEYS.UNKNOWN;
+ }
+
+ protected String esc;
+
+ protected boolean isEsc = false;
+
+ protected void startEsc() {
+ isEsc = true;
+ esc = "";
+ }
+
+ protected abstract void scanEsc(final int b) throws IOException;
+
+ public byte getBackspace() {
+ return BACKSPACE;
+ }
+
+ public void setBackspace(byte backspace) {
+ BACKSPACE = backspace;
+ }
+
+ public byte getDel() {
+ return DEL;
+ }
+
+ public void setDel(byte del) {
+ DEL = del;
+ }
+
+ public Map<String, KEYS> getCurrentEscapesToKey() {
+ return currentEscapesToKey;
+ }
+
+ public void setCurrentEscapesToKey(Map<String, KEYS> currentEscapesToKey) {
+ this.currentEscapesToKey = currentEscapesToKey;
+ }
+
+ public String[] getEscapes() {
+ if (escapes != null) {
+ return Arrays.copyOf(escapes, escapes.length);
+ } else {
+ return null;
+ }
+ }
+
+ public void setEscapes(String[] escapes) {
+ if (escapes != null) {
+ this.escapes = Arrays.copyOf(escapes, escapes.length);
+ } else {
+ this.escapes = null;
+ }
+ }
+
+}
diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/common/SimpleByteBuffer.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/common/SimpleByteBuffer.java
new file mode 100644
index 0000000..d00ca53
--- /dev/null
+++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/common/SimpleByteBuffer.java
@@ -0,0 +1,149 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial contribution
+ ******************************************************************************/
+
+package org.eclipse.virgo.osgi.console.common;
+
+/**
+ * This is a helper class, which buffers one line of input. It provides for simple line editing - insertion, deletion,
+ * left and right movement, deletion through the backspace key.
+ */
+public class SimpleByteBuffer {
+
+ private static int INITAL_SIZE = 13;
+
+ private byte[] buffer;
+
+ private int pos = 0;
+
+ private int size = 0;
+
+ public SimpleByteBuffer() {
+ buffer = new byte[INITAL_SIZE];
+ }
+
+ public void add(final int b) {
+ if (size >= buffer.length) {
+ rezize();
+ }
+ buffer[size++] = (byte) b;
+ }
+
+ private void rezize() {
+ final byte[] newbuffeer = new byte[buffer.length << 1];
+ System.arraycopy(buffer, 0, newbuffeer, 0, buffer.length);
+ buffer = newbuffeer;
+ }
+
+ public void insert(int b) {
+ if (size >= buffer.length) {
+ rezize();
+ }
+ final int forCopy = size - pos;
+ if (forCopy > 0) {
+ System.arraycopy(buffer, pos, buffer, pos + 1, forCopy);
+ }
+ buffer[pos++] = (byte) b;
+ size++;
+ }
+
+ public int goRight() {
+ if (pos < size) {
+ return buffer[pos++] & 0xFF;
+ }
+ return -1;
+ }
+
+ public boolean goLeft() {
+ if (pos > 0) {
+ pos--;
+ return true;
+ }
+ return false;
+ }
+
+ public void delete() {
+ if (pos < size) {
+ final int forCopy = size - pos;
+ System.arraycopy(buffer, pos + 1, buffer, pos, forCopy);
+ size--;
+ }
+ }
+
+ public boolean backSpace() {
+ if (pos > 0 && size > 0) {
+ final int forCopy = size - pos;
+ System.arraycopy(buffer, pos, buffer, pos - 1, forCopy);
+ size--;
+ pos--;
+ return true;
+ }
+ return false;
+ }
+
+ public void delAll() {
+ pos = 0;
+ size = 0;
+ }
+
+ public byte[] getCurrentData() {
+ byte[] res = new byte[size];
+ System.arraycopy(buffer, 0, res, 0, size);
+ pos = 0;
+ size = 0;
+ return res;
+ }
+
+ public void set(byte[] newData) {
+ pos = 0;
+ size = 0;
+ if (newData != null) {
+ for (byte data : newData) {
+ insert(data);
+ }
+ }
+ }
+
+ public int getPos() {
+ return pos;
+ }
+
+ public byte[] copyCurrentData() {
+ byte[] res = new byte[size];
+ System.arraycopy(buffer, 0, res, 0, size);
+ return res;
+ }
+
+ public int getCurrentChar() {
+ if (pos < size) {
+ return buffer[pos] & 0xFF;
+ } else {
+ return -1;
+ }
+ }
+
+ public int getSize() {
+ return size;
+ }
+
+ public int resetPos() {
+ int res = pos;
+ pos = 0;
+ return res;
+ }
+
+ public void replace(int b) {
+ if (pos == size) {
+ insert(b);
+ } else {
+ buffer[pos++] = (byte) b;
+ }
+ }
+}
diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/supportability/CommandCompleter.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/supportability/CommandCompleter.java
new file mode 100644
index 0000000..42bea12
--- /dev/null
+++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/supportability/CommandCompleter.java
@@ -0,0 +1,91 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial contribution
+ ******************************************************************************/
+
+package org.eclipse.virgo.osgi.console.supportability;
+
+import org.eclipse.osgi.framework.console.CommandProvider;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * This class implements basic command completion. It can complete only OSGi commands, not command parameters. It
+ * registers a tracker, with which it tracks all CommandProvider services, and when a new one becomes available, it adds
+ * its command methods in a local cache, which the completer uses to complete the command name given the first letters
+ * of the command.
+ */
+public class CommandCompleter {
+
+ private Set<String> availableCommands;
+
+ private ServiceTracker<CommandProvider, Object> cpTracker;
+
+ private BundleContext context = null;
+
+ public CommandCompleter(BundleContext context) {
+ this.context = context;
+ availableCommands = new HashSet<String>();
+ availableCommands = Collections.synchronizedSet(availableCommands);
+ availableCommands.add("more");
+ availableCommands.add("disconnect");
+ availableCommands.add("grep");
+ if (context != null) {
+ cpTracker = new ServiceTracker<CommandProvider, Object>(context, CommandProvider.class.getName(), new CommandProviderCustomizer());
+ cpTracker.open();
+ }
+ }
+
+ public String[] complete(String prefix) {
+ ArrayList<String> candidates = new ArrayList<String>();
+ for (String command : availableCommands) {
+ if (command.startsWith(prefix)) {
+ candidates.add(command);
+ }
+ }
+
+ return candidates.toArray(new String[candidates.size()]);
+ }
+
+ class CommandProviderCustomizer implements ServiceTrackerCustomizer<CommandProvider, Object> {
+
+ public Object addingService(ServiceReference<CommandProvider> reference) {
+ CommandProvider provider = context.getService(reference);
+ Method[] methods = provider.getClass().getMethods();
+ for (Method method : methods) {
+ if (method.getName().startsWith("_")) {
+ availableCommands.add(method.getName().substring(1));
+ }
+ }
+ return null;
+ }
+
+ public void modifiedService(ServiceReference<CommandProvider> reference, Object service) {
+ // do nothing
+ }
+
+ public void removedService(ServiceReference<CommandProvider> reference, Object service) {
+ CommandProvider provider = context.getService(reference);
+ Method[] methods = provider.getClass().getMethods();
+ for (Method method : methods) {
+ if (method.getName().startsWith("_")) {
+ availableCommands.remove(method.getName());
+ }
+ }
+ }
+ }
+}
diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/supportability/ConsoleInputHandler.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/supportability/ConsoleInputHandler.java
new file mode 100644
index 0000000..234b522
--- /dev/null
+++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/supportability/ConsoleInputHandler.java
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * Copyright (c) 2010 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial contribution
+ ******************************************************************************/
+
+package org.eclipse.virgo.osgi.console.supportability;
+
+import org.eclipse.virgo.osgi.console.supportability.ConsoleInputScanner;
+import org.eclipse.virgo.osgi.console.common.ConsoleInputStream;
+import org.eclipse.virgo.osgi.console.common.InputHandler;
+import org.osgi.framework.BundleContext;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * This class customizes the generic handler with a concrete content processor, which provides command line editing.
+ */
+public class ConsoleInputHandler extends InputHandler {
+
+ public ConsoleInputHandler(InputStream input, ConsoleInputStream in, OutputStream out, BundleContext context) {
+ super(input, in, out);
+ inputScanner = new ConsoleInputScanner(in, out, context);
+ }
+}
diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/supportability/ConsoleInputScanner.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/supportability/ConsoleInputScanner.java
new file mode 100644
index 0000000..7dcab17
--- /dev/null
+++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/supportability/ConsoleInputScanner.java
@@ -0,0 +1,374 @@
+/*******************************************************************************
+ * Copyright (c) 2010 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial contribution
+ ******************************************************************************/
+
+package org.eclipse.virgo.osgi.console.supportability;
+
+import org.eclipse.virgo.osgi.console.supportability.CommandCompleter;
+import org.eclipse.virgo.osgi.console.supportability.Grep;
+import org.eclipse.virgo.osgi.console.supportability.HistoryHolder;
+import org.eclipse.virgo.osgi.console.common.ConsoleInputStream;
+import org.eclipse.virgo.osgi.console.common.KEYS;
+import org.eclipse.virgo.osgi.console.common.Scanner;
+import org.eclipse.virgo.osgi.console.common.SimpleByteBuffer;
+import org.osgi.framework.BundleContext;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Arrays;
+
+/**
+ * This class performs the processing of the input special characters, and updates respectively what is displayed in the
+ * output. It handles escape sequences, delete, backspace, arrows, and provides command history and grep.
+ */
+public class ConsoleInputScanner extends Scanner {
+
+ private static final byte TAB = 9;
+
+ private boolean isCR = false;
+
+ private boolean replace = false;
+
+ private final HistoryHolder history;
+
+ private final SimpleByteBuffer buffer;
+
+ private CommandCompleter completer;
+
+ public ConsoleInputScanner(ConsoleInputStream toShell, OutputStream toTelnet, BundleContext context) {
+ super(toShell, toTelnet);
+ history = new HistoryHolder();
+ buffer = new SimpleByteBuffer();
+ completer = new CommandCompleter(context);
+ }
+
+ public void scan(int b) throws IOException {
+ b &= 0xFF;
+ if (isCR) {
+ isCR = false;
+ if (b == LF) {
+ return;
+ }
+ }
+ if (isEsc) {
+ scanEsc(b);
+ } else {
+ if (b == getBackspace()) {
+ backSpace();
+ } else if (b == TAB) {
+ tab();
+ } else if (b == CR) {
+ isCR = true;
+ processData();
+ } else if (b == LF) {
+ processData();
+ } else if (b == ESC) {
+ startEsc();
+ } else if (b == getDel()) {
+ delete();
+ } else {
+ if (b >= SPACE && b < MAX_CHAR) {
+ newChar(b);
+ }
+ }
+ }
+ }
+
+ private void delete() throws IOException {
+ clearLine();
+ buffer.delete();
+ echoBuff();
+ flush();
+ }
+
+ private void backSpace() throws IOException {
+ clearLine();
+ buffer.backSpace();
+ echoBuff();
+ flush();
+ }
+
+ private void tab() throws IOException {
+ byte[] cur = buffer.copyCurrentData();
+ String currentInput = new String(cur).trim();
+ String[] completionCandidates = completer.complete(currentInput);
+
+ if (completionCandidates.length == 1) {
+ String suffix = completionCandidates[0].substring(currentInput.length());
+ byte[] completion = suffix.getBytes();
+ for (byte symbol : completion) {
+ buffer.insert(symbol);
+ echo(symbol);
+
+ }
+ flush();
+ return;
+ }
+
+ echo(CR);
+ echo(LF);
+ flush();
+
+ if (completionCandidates.length == 0) {
+ buffer.getCurrentData();
+ String errorMessage = "No such command";
+ for (byte symbol : errorMessage.getBytes()) {
+ echo(symbol);
+ }
+ } else {
+ for (String candidate : completionCandidates) {
+ for (byte symbol : candidate.getBytes()) {
+ echo(symbol);
+ }
+ echo(SPACE);
+ echo(SPACE);
+ }
+ }
+
+ echo(CR);
+ echo(LF);
+ flush();
+ echo('o');
+ echo('s');
+ echo('g');
+ echo('i');
+ echo('>');
+ echo(SPACE);
+ echoBuff();
+ flush();
+ }
+
+ protected void clearLine() throws IOException {
+ int size = buffer.getSize();
+ int pos = buffer.getPos();
+ for (int i = size - pos; i < size; i++) {
+ echo(BS);
+ }
+ for (int i = 0; i < size; i++) {
+ echo(SPACE);
+ }
+ for (int i = 0; i < size; i++) {
+ echo(BS);
+ }
+ }
+
+ protected void echoBuff() throws IOException {
+ byte[] data = buffer.copyCurrentData();
+ for (byte b : data) {
+ echo(b);
+ }
+ int pos = buffer.getPos();
+ for (int i = data.length; i > pos; i--) {
+ echo(BS);
+ }
+ }
+
+ protected void newChar(int b) throws IOException {
+ if (buffer.getPos() < buffer.getSize()) {
+ if (replace) {
+ buffer.replace(b);
+ } else {
+ buffer.insert(b);
+ }
+ clearLine();
+ echoBuff();
+ flush();
+ } else {
+ if (replace) {
+ buffer.replace(b);
+ } else {
+ buffer.insert(b);
+ }
+ }
+ }
+
+ private void processData() throws IOException {
+ buffer.add(CR);
+ buffer.add(LF);
+ echo(CR);
+ echo(LF);
+ flush();
+ byte[] curr = buffer.getCurrentData();
+ history.add(curr);
+
+ int index = 0;
+ boolean isGrep = false;
+ for (; index < curr.length; index++) {
+ if (curr[index] == '|') {
+ isGrep = true;
+ break;
+ }
+ }
+
+ if (isGrep) {
+ byte[] grepExpression = new byte[curr.length - index - 1];
+ System.arraycopy(curr, index + 1, grepExpression, 0, grepExpression.length);
+ Grep grep = new Grep(grepExpression, toTelnet);
+ grep.start();
+ byte[] array = Arrays.copyOf(curr, index + 2);
+ array[index] = CR;
+ array[index + 1] = LF;
+ toShell.add(array);
+ } else {
+ toShell.add(curr);
+ }
+ }
+
+ public void resetHistory() {
+ history.reset();
+ }
+
+ protected void scanEsc(final int b) throws IOException {
+ esc += (char) b;
+ KEYS key = checkEscape(esc);
+ if (key == KEYS.UNFINISHED) {
+ return;
+ }
+ if (key == KEYS.UNKNOWN) {
+ isEsc = false;
+ scan(b);
+ return;
+ }
+ isEsc = false;
+ switch (key) {
+ case UP:
+ processUpArrow();
+ break;
+ case DOWN:
+ processDownArrow();
+ break;
+ case RIGHT:
+ processRightArrow();
+ break;
+ case LEFT:
+ processLeftArrow();
+ break;
+ case HOME:
+ processHome();
+ break;
+ case END:
+ processEnd();
+ break;
+ case PGUP:
+ processPgUp();
+ break;
+ case PGDN:
+ processPgDn();
+ break;
+ case INS:
+ processIns();
+ break;
+ case DEL:
+ delete();
+ break;
+ default: // CENTER
+ break;
+ }
+ }
+
+ private static final byte[] INVERSE_ON = { ESC, '[', '7', 'm' };
+
+ private static final byte[] INVERSE_OFF = { ESC, '[', '2', '7', 'm' };
+
+ private void echo(byte[] data) throws IOException {
+ for (byte b : data) {
+ echo(b);
+ }
+ }
+
+ private void processIns() throws IOException {
+ replace = !replace;
+ int b = buffer.getCurrentChar();
+ echo(INVERSE_ON);
+ echo(replace ? 'R' : 'I');
+ flush();
+ try {
+ Thread.sleep(300);
+ } catch (InterruptedException e) {
+ // do not care $JL-EXC$
+ }
+ echo(INVERSE_OFF);
+ echo(BS);
+ echo(b == -1 ? SPACE : b);
+ echo(BS);
+ flush();
+ }
+
+ private void processPgDn() throws IOException {
+ byte[] last = history.last();
+ if (last != null) {
+ clearLine();
+ buffer.set(last);
+ echoBuff();
+ flush();
+ }
+ }
+
+ private void processPgUp() throws IOException {
+ byte[] first = history.first();
+ if (first != null) {
+ clearLine();
+ buffer.set(first);
+ echoBuff();
+ flush();
+ }
+ }
+
+ private void processHome() throws IOException {
+ int pos = buffer.resetPos();
+ if (pos > 0) {
+ for (int i = 0; i < pos; i++) {
+ echo(BS);
+ }
+ flush();
+ }
+ }
+
+ private void processEnd() throws IOException {
+ int b;
+ while ((b = buffer.goRight()) != -1) {
+ echo(b);
+ }
+ flush();
+ }
+
+ private void processLeftArrow() throws IOException {
+ if (buffer.goLeft()) {
+ echo(BS);
+ flush();
+ }
+ }
+
+ private void processRightArrow() throws IOException {
+ int b = buffer.goRight();
+ if (b != -1) {
+ echo(b);
+ flush();
+ }
+ }
+
+ private void processDownArrow() throws IOException {
+ byte[] next = history.next();
+ if (next != null) {
+ clearLine();
+ buffer.set(next);
+ echoBuff();
+ flush();
+ }
+ }
+
+ private void processUpArrow() throws IOException {
+ clearLine();
+ byte[] prev = history.prev();
+ buffer.set(prev);
+ echoBuff();
+ flush();
+ }
+}
diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/supportability/Grep.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/supportability/Grep.java
new file mode 100644
index 0000000..2f26001
--- /dev/null
+++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/supportability/Grep.java
@@ -0,0 +1,119 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial contribution
+ ******************************************************************************/
+
+package org.eclipse.virgo.osgi.console.supportability;
+
+import org.eclipse.virgo.osgi.console.common.ConsoleOutputStream;
+
+import java.io.*;
+import java.util.ArrayList;
+
+/**
+ * This class implements grep. Since in Equinox 3.6 there is not piping support, grep cannot be implemented as a shell
+ * command. That is why it is implemented as part of the command line editing features. The socket output stream inside
+ * ConsoleOutputStream is substituted with a PipedOutputStream, so that what the command writes to the output stream
+ * does not go to the console, but is read through a PipedInputStream and is filtered for the searched expression. After
+ * all output of the command is read and filtered, the socket output stream inside ConsoleOutputStream is restored and
+ * the lines of the command output, which match the grep expression, are written to it.
+ */
+public class Grep extends Thread {
+
+ private String expression;
+
+ private ConsoleOutputStream out;
+
+ private PipedInputStream input;
+
+ private PipedOutputStream output;
+
+ private BufferedReader reader;
+
+ private ArrayList<String> filteredOutput;
+
+ private static int LENGTH = 4;
+
+ public Grep(byte[] expression, OutputStream out) {
+ String expr = (new String(expression)).trim();
+ int index = expr.indexOf("grep");
+ this.expression = expr.substring(index + LENGTH).trim();
+ this.out = (ConsoleOutputStream) out;
+ input = new PipedInputStream();
+ try {
+ output = new PipedOutputStream(input);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ this.out.setOutput(output);
+ filteredOutput = new ArrayList<String>();
+ }
+
+ public void run() {
+ reader = new BufferedReader(new InputStreamReader(input));
+ boolean hasMore = true;
+ try {
+ while (hasMore) {
+ String line = getLine();
+ hasMore = line != null;
+ if (hasMore) {
+ // last line containing the osgi prompt should be output although it does not contain the grep
+ // expression
+ if (line.contains(expression) || line.contains("osgi>")) {
+ filteredOutput.add(line);
+ }
+
+ if (line.contains("osgi>")) {
+ hasMore = false;
+ }
+ }
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ out.setOutput(null);
+ PrintWriter writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(out)));
+ for (int i = 0; i < filteredOutput.size(); i++) {
+ if (i == filteredOutput.size() - 1 && filteredOutput.get(i).contains("osgi>")) {
+ writer.print(filteredOutput.get(i));
+ writer.print(" ");
+ } else {
+ writer.println(filteredOutput.get(i));
+ }
+ writer.flush();
+ }
+ }
+
+ private String getLine() throws IOException {
+ StringBuilder line = new StringBuilder();
+ boolean quit = false;
+ while (!quit) {
+ int c = reader.read();
+ if (c < 0) {
+ quit = true;
+ } else {
+ switch (c) {
+ case '\r':
+ break;
+ case '\n':
+ return line.toString();
+ default:
+ line.append((char) c);
+ if (line.toString().contains("osgi>")) {
+ return line.toString();
+ }
+ break;
+ }
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/supportability/HistoryHolder.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/supportability/HistoryHolder.java
new file mode 100644
index 0000000..dc39af9
--- /dev/null
+++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/supportability/HistoryHolder.java
@@ -0,0 +1,103 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial contribution
+ ******************************************************************************/
+
+package org.eclipse.virgo.osgi.console.supportability;
+
+import java.io.UnsupportedEncodingException;
+import java.util.Arrays;
+
+/**
+ * A helper class, which implements history.
+ */
+public class HistoryHolder {
+
+ private static final int MAX = 100;
+
+ private final byte[][] history;
+
+ private int size;
+
+ private int pos;
+
+ public HistoryHolder() {
+ history = new byte[MAX][];
+ }
+
+ public synchronized void reset() {
+ size = 0;
+ pos = 0;
+ for (int i = 0; i < MAX; i++) {
+ history[i] = null;
+ }
+ }
+
+ public synchronized void add(byte[] data) {
+ try {
+ data = new String(data, "US-ASCII").trim().getBytes("US-ASCII");
+ } catch (UnsupportedEncodingException e) {
+
+ }
+ if (data.length == 0) {
+ pos = size;
+ return;
+ }
+ for (int i = 0; i < size; i++) {
+ if (Arrays.equals(history[i], data)) {
+ System.arraycopy(history, i + 1, history, i, size - i - 1);
+ history[size - 1] = data;
+ pos = size;
+ return;
+ }
+ }
+ if (size >= MAX) {
+ System.arraycopy(history, 1, history, 0, size - 1);
+ size--;
+ }
+ history[size++] = data;
+ pos = size;
+ }
+
+ public synchronized byte[] next() {
+ if (pos >= size - 1) {
+ return null;
+ }
+ return history[++pos];
+ }
+
+ public synchronized byte[] last() {
+ if (size > 0) {
+ pos = size - 1;
+ return history[pos];
+ } else {
+ return null;
+ }
+ }
+
+ public synchronized byte[] first() {
+ if (size > 0) {
+ pos = 0;
+ return history[pos];
+ } else {
+ return null;
+ }
+ }
+
+ public synchronized byte[] prev() {
+ if (size == 0) {
+ return null;
+ }
+ if (pos == 0) {
+ return history[pos];
+ } else {
+ return history[--pos];
+ }
+ }
+}
diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/ANSITerminalTypeMappings.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/ANSITerminalTypeMappings.java
new file mode 100644
index 0000000..9468f7f
--- /dev/null
+++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/ANSITerminalTypeMappings.java
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial contribution
+ ******************************************************************************/
+
+package org.eclipse.virgo.osgi.console.telnet;
+
+import org.eclipse.virgo.osgi.console.telnet.TerminalTypeMappings;
+import org.eclipse.virgo.osgi.console.common.KEYS;
+
+public class ANSITerminalTypeMappings extends TerminalTypeMappings {
+
+ public ANSITerminalTypeMappings() {
+ super();
+ BACKSPACE = 8;
+ DEL = 127;
+ }
+
+ public void setKeypadMappings() {
+ escapesToKey.put("[1~", KEYS.HOME); //$NON-NLS-1$
+ escapesToKey.put("[4~", KEYS.END); //$NON-NLS-1$
+ escapesToKey.put("[5~", KEYS.PGUP); //$NON-NLS-1$
+ escapesToKey.put("[6~", KEYS.PGDN); //$NON-NLS-1$
+ escapesToKey.put("[2~", KEYS.INS); //$NON-NLS-1$
+ escapesToKey.put("[3~", KEYS.DEL); //$NON-NLS-1$
+ }
+}
diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/Callback.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/Callback.java
new file mode 100644
index 0000000..6b2474e
--- /dev/null
+++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/Callback.java
@@ -0,0 +1,17 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial contribution
+ ******************************************************************************/
+
+package org.eclipse.virgo.osgi.console.telnet;
+
+public interface Callback {
+
+ public void finished();
+}
diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/NegotiationFinishedCallback.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/NegotiationFinishedCallback.java
new file mode 100644
index 0000000..883c6ea
--- /dev/null
+++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/NegotiationFinishedCallback.java
@@ -0,0 +1,30 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial contribution
+ ******************************************************************************/
+
+package org.eclipse.virgo.osgi.console.telnet;
+
+import org.eclipse.virgo.osgi.console.telnet.Callback;
+import org.eclipse.virgo.osgi.console.telnet.TelnetConsoleSession;
+
+public class NegotiationFinishedCallback implements Callback {
+
+ private TelnetConsoleSession consoleSession;
+
+ public NegotiationFinishedCallback(TelnetConsoleSession consoleSession) {
+ this.consoleSession = consoleSession;
+ }
+
+ @Override
+ public void finished() {
+ consoleSession.telnetNegotiationFinished();
+ }
+
+}
diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/SCOTerminalTypeMappings.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/SCOTerminalTypeMappings.java
new file mode 100644
index 0000000..b337e99
--- /dev/null
+++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/SCOTerminalTypeMappings.java
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial contribution
+ ******************************************************************************/
+
+package org.eclipse.virgo.osgi.console.telnet;
+
+import org.eclipse.virgo.osgi.console.telnet.TerminalTypeMappings;
+import org.eclipse.virgo.osgi.console.common.KEYS;
+
+public class SCOTerminalTypeMappings extends TerminalTypeMappings {
+
+ public SCOTerminalTypeMappings() {
+ super();
+
+ BACKSPACE = -1;
+ DEL = 127;
+ }
+
+ @Override
+ public void setKeypadMappings() {
+ escapesToKey.put("[H", KEYS.HOME);
+ escapesToKey.put("F", KEYS.END);
+ escapesToKey.put("[L", KEYS.INS);
+ escapesToKey.put("[I", KEYS.PGUP);
+ escapesToKey.put("[G", KEYS.PGDN);
+ }
+}
diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/TelnetConsoleSession.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/TelnetConsoleSession.java
new file mode 100644
index 0000000..22758c9
--- /dev/null
+++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/TelnetConsoleSession.java
@@ -0,0 +1,134 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial contribution
+ ******************************************************************************/
+
+package org.eclipse.virgo.osgi.console.telnet;
+
+import org.eclipse.virgo.osgi.console.telnet.Callback;
+import org.eclipse.virgo.osgi.console.telnet.NegotiationFinishedCallback;
+import org.eclipse.virgo.osgi.console.telnet.TelnetInputHandler;
+import org.eclipse.virgo.osgi.console.telnet.TelnetOutputStream;
+import org.eclipse.osgi.framework.console.ConsoleSession;
+import org.eclipse.virgo.osgi.console.common.ConsoleInputStream;
+import org.eclipse.virgo.osgi.console.supportability.ConsoleInputHandler;
+import org.osgi.framework.BundleContext;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.Socket;
+
+/**
+ * This class provides an implementation of a ConsoleSession. It creates a handler for the input from telnet and wraps
+ * its streams to add handling for command line editing.
+ */
+public class TelnetConsoleSession extends ConsoleSession {
+
+ private final Socket s;
+
+ private InputStream input;
+
+ private final ConsoleInputStream in;
+
+ private final TelnetOutputStream out;
+
+ protected boolean isTelnetNegotiationFinished = false;
+
+ private Callback callback;
+
+ private static final long TIMEOUT = 1000;
+
+ private static final long NEGOTIATION_TIMEOUT = 60000;
+
+ private final BundleContext context;
+
+ public TelnetConsoleSession(Socket s, BundleContext context) throws IOException {
+ in = new ConsoleInputStream();
+ out = new TelnetOutputStream(s.getOutputStream());
+ out.autoSend();
+ this.s = s;
+ this.context = context;
+
+ callback = new NegotiationFinishedCallback(this);
+ }
+
+ public synchronized void start() throws IOException {
+ TelnetInputHandler telnetInputHandler = new TelnetInputHandler(s.getInputStream(), in, out, callback);
+ telnetInputHandler.start();
+ long start = System.currentTimeMillis();
+ while (isTelnetNegotiationFinished == false && System.currentTimeMillis() - start < NEGOTIATION_TIMEOUT) {
+ try {
+ wait(TIMEOUT);
+ } catch (InterruptedException e) {
+ // do nothing
+ }
+ }
+
+ ConsoleInputStream inp = new ConsoleInputStream();
+
+ ConsoleInputHandler consoleInputHandler = new ConsoleInputHandler(in, inp, out, context);
+ consoleInputHandler.getScanner().setBackspace(telnetInputHandler.getScanner().getBackspace());
+ consoleInputHandler.getScanner().setDel(telnetInputHandler.getScanner().getDel());
+ consoleInputHandler.getScanner().setCurrentEscapesToKey(telnetInputHandler.getScanner().getCurrentEscapesToKey());
+ consoleInputHandler.getScanner().setEscapes(telnetInputHandler.getScanner().getEscapes());
+
+ consoleInputHandler.start();
+ input = inp;
+ }
+
+ public synchronized void telnetNegotiationFinished() {
+ isTelnetNegotiationFinished = true;
+ notify();
+
+ }
+
+ public synchronized InputStream getInput() {
+ return input;
+ }
+
+ public synchronized OutputStream getOutput() {
+ return out;
+ }
+
+ public synchronized void doClose() {
+ if (s != null) {
+ try {
+ s.close();
+ } catch (IOException ioe) {
+ // do nothing
+ }
+ }
+
+ if (out != null) {
+ try {
+ out.close();
+ } catch (IOException e) {
+ // do nothing
+ }
+ }
+
+ if (in != null) {
+ try {
+ in.close();
+ } catch (IOException ioe) {
+ // do nothing
+ }
+ }
+
+ if (input != null) {
+ try {
+ input.close();
+ } catch (IOException ioe) {
+ // do nothing
+ }
+ }
+ }
+
+}
diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/TelnetInputHandler.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/TelnetInputHandler.java
new file mode 100644
index 0000000..8ab3f3c
--- /dev/null
+++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/TelnetInputHandler.java
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial contribution
+ ******************************************************************************/
+
+package org.eclipse.virgo.osgi.console.telnet;
+
+import org.eclipse.virgo.osgi.console.telnet.Callback;
+import org.eclipse.virgo.osgi.console.telnet.TelnetInputScanner;
+import org.eclipse.virgo.osgi.console.common.ConsoleInputStream;
+import org.eclipse.virgo.osgi.console.common.ConsoleOutputStream;
+import org.eclipse.virgo.osgi.console.common.InputHandler;
+
+import java.io.InputStream;
+
+/**
+ * This class customizes the generic handler with a concrete content processor, which provides telnet protocol handling.
+ */
+public class TelnetInputHandler extends InputHandler {
+
+ public TelnetInputHandler(InputStream input, ConsoleInputStream in, ConsoleOutputStream out, Callback callback) {
+ super(input, in, out);
+ inputScanner = new TelnetInputScanner(in, out, callback);
+ }
+}
diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/TelnetInputScanner.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/TelnetInputScanner.java
new file mode 100644
index 0000000..8f865b9
--- /dev/null
+++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/TelnetInputScanner.java
@@ -0,0 +1,296 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial contribution
+ ******************************************************************************/
+
+package org.eclipse.virgo.osgi.console.telnet;
+
+import org.eclipse.virgo.osgi.console.telnet.Callback;
+import org.eclipse.virgo.osgi.console.telnet.TerminalTypeMappings;
+import org.eclipse.virgo.osgi.console.common.ConsoleInputStream;
+import org.eclipse.virgo.osgi.console.common.ConsoleOutputStream;
+import org.eclipse.virgo.osgi.console.common.KEYS;
+import org.eclipse.virgo.osgi.console.common.Scanner;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * This class performs the processing of the telnet commands, and updates respectively what is displayed in the output.
+ */
+public class TelnetInputScanner extends Scanner {
+
+ private boolean isCommand = false;
+
+ private boolean isReadingTtype = false;
+
+ private boolean shouldFinish = false;
+
+ private boolean tTypeNegotiationStarted = false;
+
+ private int lastRead = -1;
+
+ private ArrayList<Integer> currentTerminalType = new ArrayList<Integer>();
+
+ private ArrayList<Integer> lastTerminalType = null;
+
+ private Set<String> supportedTerminalTypes = new HashSet<String>();
+
+ private Callback callback;
+
+ public TelnetInputScanner(ConsoleInputStream toShell, ConsoleOutputStream toTelnet, Callback callback) {
+ super(toShell, toTelnet);
+ initializeSupportedTerminalTypes();
+ TerminalTypeMappings currentMapping = supportedEscapeSequences.get(DEFAULT_TTYPE);
+ currentEscapesToKey = currentMapping.getEscapesToKey();
+ escapes = currentMapping.getEscapes();
+ setBackspace(currentMapping.getBackspace());
+ setDel(currentMapping.getDel());
+ this.callback = callback;
+ }
+
+ private void initializeSupportedTerminalTypes() {
+ supportedTerminalTypes.add("ANSI");
+ supportedTerminalTypes.add("VT100");
+ supportedTerminalTypes.add("VT220");
+ supportedTerminalTypes.add("VT320");
+ supportedTerminalTypes.add("XTERM");
+ supportedTerminalTypes.add("SCO");
+ }
+
+ public void scan(int b) throws IOException {
+ b &= 0xFF;
+
+ if (isEsc) {
+ scanEsc(b);
+ } else if (isCommand) {
+ scanCommand(b);
+ } else if (b == IAC) {
+ startCommand();
+ } else {
+ switch (b) {
+ case ESC:
+ startEsc();
+ toShell.add(new byte[] { (byte) b });
+ break;
+ default:
+ if (b >= SPACE && b < MAX_CHAR) {
+ echo((byte) b);
+ flush();
+ }
+ toShell.add(new byte[] { (byte) b });
+ }
+
+ }
+ lastRead = b;
+ }
+
+ /*
+ * Telnet command codes are described in RFC 854, TELNET PROTOCOL SPECIFICATION available at
+ * http://www.ietf.org/rfc/rfc854.txt
+ *
+ * Telnet terminal type negotiation option is described in RFC 1091, Telnet Terminal-Type Option available at
+ * http://www.ietf.org/rfc/rfc1091.txt
+ */
+ private static final int SE = 240;
+
+ private static final int EC = 247;
+
+ private static final int EL = 248;
+
+ private static final int SB = 250;
+
+ private static final int WILL = 251;
+
+ private static final int WILL_NOT = 252;
+
+ private static final int DO = 253;
+
+ private static final int DO_NOT = 254;
+
+ private static final int TTYPE = 24;
+
+ private static final int SEND = 1;
+
+ private static final int IAC = 255;
+
+ private static final int IS = 0;
+
+ private boolean isNegotiation;
+
+ private boolean isWill;
+
+ private byte[] tTypeRequest = { (byte) IAC, (byte) SB, (byte) TTYPE, (byte) SEND, (byte) IAC, (byte) SE };
+
+ private void scanCommand(final int b) throws IOException {
+ if (isNegotiation) {
+ scanNegotiation(b);
+ } else if (isWill) {
+ isWill = false;
+ isCommand = false;
+ if (b == TTYPE && tTypeNegotiationStarted == false) {
+ sendRequest();
+ }
+ } else {
+ switch (b) {
+ case WILL:
+ isWill = true;
+ break;
+ case WILL_NOT:
+ break;
+ case DO:
+ break;
+ case DO_NOT:
+ break;
+ case SB:
+ isNegotiation = true;
+ break;
+ case EC:
+ eraseChar();
+ isCommand = false;
+ break;
+ case EL:
+ default:
+ isCommand = false;
+ break;
+ }
+ }
+ }
+
+ private void scanNegotiation(final int b) {
+ if (lastRead == SB && b == TTYPE) {
+ isReadingTtype = true;
+ } else if (b == IS) {
+
+ } else if (b == IAC) {
+
+ } else if (b == SE) {
+ isNegotiation = false;
+ isCommand = false;
+ if (isReadingTtype == true) {
+ isReadingTtype = false;
+ if (shouldFinish == true) {
+ setCurrentTerminalType();
+ shouldFinish = false;
+ return;
+ }
+ boolean isMatch = isTerminalTypeSupported();
+ boolean isLast = isLast();
+ if (isMatch == true) {
+ setCurrentTerminalType();
+ return;
+ }
+ lastTerminalType = currentTerminalType;
+ currentTerminalType = new ArrayList<Integer>();
+ if (isLast == true && isMatch == false) {
+ shouldFinish = true;
+ sendRequest();
+ } else if (isLast == false && isMatch == false) {
+ sendRequest();
+ }
+ }
+ } else if (isReadingTtype == true) {
+ currentTerminalType.add(b);
+ }
+ }
+
+ private boolean isTerminalTypeSupported() {
+ byte[] tmp = new byte[currentTerminalType.size()];
+ int idx = 0;
+ for (Integer i : currentTerminalType) {
+ tmp[idx] = i.byteValue();
+ idx++;
+ }
+ String tType = new String(tmp);
+
+ for (String terminal : supportedTerminalTypes) {
+ if (tType.toUpperCase().contains(terminal)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private boolean isLast() {
+ if (currentTerminalType.equals(lastTerminalType)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private void setCurrentTerminalType() {
+ byte[] tmp = new byte[currentTerminalType.size()];
+ int idx = 0;
+ for (Integer i : currentTerminalType) {
+ tmp[idx] = i.byteValue();
+ idx++;
+ }
+ String tType = new String(tmp);
+ String term = null;
+ for (String terminal : supportedTerminalTypes) {
+ if (tType.toUpperCase().contains(terminal)) {
+ term = terminal;
+ }
+ }
+ TerminalTypeMappings currentMapping = supportedEscapeSequences.get(term);
+ if (currentMapping == null) {
+ currentMapping = supportedEscapeSequences.get(DEFAULT_TTYPE);
+ }
+ currentEscapesToKey = currentMapping.getEscapesToKey();
+ escapes = currentMapping.getEscapes();
+ setBackspace(currentMapping.getBackspace());
+ setDel(currentMapping.getDel());
+ if (callback != null) {
+ callback.finished();
+ }
+ }
+
+ private void sendRequest() {
+ try {
+ toTelnet.write(tTypeRequest);
+ toTelnet.flush();
+ if (tTypeNegotiationStarted == false) {
+ tTypeNegotiationStarted = true;
+ }
+ } catch (IOException e) {
+
+ e.printStackTrace();
+ }
+ }
+
+ private void startCommand() {
+ isCommand = true;
+ isNegotiation = false;
+ isWill = false;
+ }
+
+ private void eraseChar() throws IOException {
+ toShell.add(new byte[] { BS });
+ }
+
+ protected void scanEsc(int b) throws IOException {
+ esc += (char) b;
+ toShell.add(new byte[] { (byte) b });
+ KEYS key = checkEscape(esc);
+ if (key == KEYS.UNFINISHED) {
+ return;
+ }
+ if (key == KEYS.UNKNOWN) {
+ isEsc = false;
+ scan(b);
+ return;
+ }
+ isEsc = false;
+ }
+
+}
diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/TelnetManager.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/TelnetManager.java
new file mode 100644
index 0000000..16c870c
--- /dev/null
+++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/TelnetManager.java
@@ -0,0 +1,149 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial contribution
+ ******************************************************************************/
+
+package org.eclipse.virgo.osgi.console.telnet;
+
+import org.eclipse.virgo.osgi.console.telnet.TelnetConsoleSession;
+import org.eclipse.osgi.framework.console.ConsoleSession;
+import org.osgi.framework.BundleContext;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+public class TelnetManager {
+
+ private ConsoleSocketGetter csg;
+
+ private int telnetPort = -1;
+
+ private String host = null;
+
+ private BundleContext context = null;
+
+ private static final String PROP_CONSOLE = "osgi.console";
+
+ public TelnetManager(BundleContext bundleContext) {
+ String consoleValue = null;
+ try {
+ consoleValue = bundleContext.getProperty(PROP_CONSOLE);
+ if (consoleValue != null && !"".equals(consoleValue) && !"none".equals(consoleValue)) {
+ parseHostAndPort(consoleValue);
+ }
+ } catch (NumberFormatException e) {
+ System.out.println("Invalid host/port in " + consoleValue + "; " + e.getMessage());
+ e.printStackTrace();
+ }
+
+ context = bundleContext;
+ }
+
+ public void startConsoleListener() {
+ if (telnetPort != -1) {
+ try {
+ if (host != null) {
+ csg = new ConsoleSocketGetter(new ServerSocket(telnetPort, 0, InetAddress.getByName(host)), context);
+ } else {
+ csg = new ConsoleSocketGetter(new ServerSocket(telnetPort), context);
+ }
+ } catch (IOException e) {
+ System.out.println("Unable to open telnet. Reason: " + e.getMessage());
+ e.printStackTrace();
+ }
+ }
+ }
+
+ public void stop() {
+ if (csg != null) {
+ csg.shutdown();
+ }
+ }
+
+ public BundleContext getContext() {
+ return context;
+ }
+
+ private void parseHostAndPort(String consoleValue) {
+ int index = consoleValue.lastIndexOf(":");
+ if (index > -1) {
+ host = consoleValue.substring(0, index);
+ }
+
+ telnetPort = Integer.parseInt(consoleValue.substring(index + 1));
+ }
+
+ /**
+ * ConsoleSocketGetter - provides a Thread that listens on the port for telnet connections.
+ */
+ static class ConsoleSocketGetter implements Runnable {
+
+ /**
+ * The ServerSocket to accept connections from
+ */
+ private final ServerSocket server;
+
+ private final BundleContext context;
+
+ private volatile boolean shutdown = false;
+
+ /**
+ * Constructor - sets the server and starts the thread to listen for connections.
+ *
+ * @param server a ServerSocket to accept connections from
+ * @param context Bundle context
+ */
+ ConsoleSocketGetter(ServerSocket server, BundleContext context) {
+ this.server = server;
+ this.context = context;
+ try {
+ Method reuseAddress = server.getClass().getMethod("setReuseAddress", new Class[] { boolean.class }); //$NON-NLS-1$
+ reuseAddress.invoke(server, Boolean.TRUE);
+ } catch (Exception ex) {
+ // try to set the socket re-use property, it isn't a problem if it can't be set
+ }
+ Thread t = new Thread(this, "ConsoleSocketGetter"); //$NON-NLS-1$
+ t.setDaemon(true);
+ t.start();
+ }
+
+ public void run() {
+ // Print message containing port console actually bound to..
+ System.out.println("Listening on port: " + Integer.toString(server.getLocalPort()));
+ while (!shutdown) {
+ try {
+ Socket socket = server.accept();
+ if (socket == null)
+ throw new IOException("No socket available. Probably caused by a shutdown."); //$NON-NLS-1$
+
+ TelnetConsoleSession session = new TelnetConsoleSession(socket, context);
+ session.start(); // start the Input Handler
+ context.registerService(ConsoleSession.class.getName(), session, null);
+ } catch (Exception e) {
+ if (!shutdown)
+ e.printStackTrace();
+ }
+ }
+ }
+
+ public void shutdown() {
+ if (shutdown)
+ return;
+ shutdown = true;
+ try {
+ server.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+}
diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/TelnetOutputStream.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/TelnetOutputStream.java
new file mode 100644
index 0000000..9dced41
--- /dev/null
+++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/TelnetOutputStream.java
@@ -0,0 +1,41 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial contribution
+ ******************************************************************************/
+
+package org.eclipse.virgo.osgi.console.telnet;
+
+import org.eclipse.virgo.osgi.console.common.ConsoleOutputStream;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * This class adds to the output stream wrapper initial negotiation of telnet communication.
+ */
+public class TelnetOutputStream extends ConsoleOutputStream {
+
+ static final byte[] autoMessage = new byte[] { (byte) 255, (byte) 251, (byte) 1, // IAC WILL ECHO
+ (byte) 255, (byte) 251, (byte) 3, // IAC WILL SUPPRESS GO_AHEAD
+ (byte) 255, (byte) 253, (byte) 31, // IAC DO NAWS
+ (byte) 255, (byte) 253, (byte) 24 }; // IAC DO TTYPE
+
+ public TelnetOutputStream(OutputStream out) {
+ super(out);
+ }
+
+ /**
+ * Sends the options which a server wants to negotiate with a telnet client.
+ */
+ public synchronized void autoSend() throws IOException {
+ write(autoMessage);
+ flush();
+ }
+
+}
diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/TerminalTypeMappings.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/TerminalTypeMappings.java
new file mode 100644
index 0000000..16e8fc3
--- /dev/null
+++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/TerminalTypeMappings.java
@@ -0,0 +1,70 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial contribution
+ ******************************************************************************/
+
+package org.eclipse.virgo.osgi.console.telnet;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.virgo.osgi.console.common.KEYS;
+
+public abstract class TerminalTypeMappings {
+
+ protected Map<String, KEYS> escapesToKey;
+
+ protected String[] escapes;
+
+ protected byte BACKSPACE;
+
+ protected byte DEL;
+
+ public TerminalTypeMappings() {
+ escapesToKey = new HashMap<String, KEYS>();
+ escapesToKey.put("[A", KEYS.UP); //$NON-NLS-1$
+ escapesToKey.put("[B", KEYS.DOWN); //$NON-NLS-1$
+ escapesToKey.put("[C", KEYS.RIGHT); //$NON-NLS-1$
+ escapesToKey.put("[D", KEYS.LEFT); //$NON-NLS-1$
+ escapesToKey.put("[G", KEYS.CENTER); //$NON-NLS-1$
+ setKeypadMappings();
+ createEscapes();
+ }
+
+ public Map<String, KEYS> getEscapesToKey() {
+ return escapesToKey;
+ }
+
+ public String[] getEscapes() {
+ if (escapes != null) {
+ return Arrays.copyOf(escapes, escapes.length);
+ } else {
+ return null;
+ }
+ }
+
+ public byte getBackspace() {
+ return BACKSPACE;
+ }
+
+ public byte getDel() {
+ return DEL;
+ }
+
+ public abstract void setKeypadMappings();
+
+ private void createEscapes() {
+ escapes = new String[escapesToKey.size()];
+ Object[] temp = escapesToKey.keySet().toArray();
+ for (int i = 0; i < escapes.length; i++) {
+ escapes[i] = (String) temp[i];
+ }
+ }
+}
diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/VT100TerminalTypeMappings.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/VT100TerminalTypeMappings.java
new file mode 100644
index 0000000..375452c
--- /dev/null
+++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/VT100TerminalTypeMappings.java
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial contribution
+ ******************************************************************************/
+
+package org.eclipse.virgo.osgi.console.telnet;
+
+import org.eclipse.virgo.osgi.console.telnet.TerminalTypeMappings;
+import org.eclipse.virgo.osgi.console.common.KEYS;
+
+public class VT100TerminalTypeMappings extends TerminalTypeMappings {
+
+ public VT100TerminalTypeMappings() {
+ super();
+ BACKSPACE = 127;
+ DEL = -1;
+ }
+
+ @Override
+ public void setKeypadMappings() {
+ escapesToKey.put("[H", KEYS.HOME); //$NON-NLS-1$
+ escapesToKey.put("[4~", KEYS.END); //$NON-NLS-1$
+ escapesToKey.put("[5~", KEYS.PGUP); //$NON-NLS-1$
+ escapesToKey.put("[6~", KEYS.PGDN); //$NON-NLS-1$
+ escapesToKey.put("[2~", KEYS.INS); //$NON-NLS-1$
+ escapesToKey.put("[3~", KEYS.DEL); //$NON-NLS-1$
+ }
+}
diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/VT220TerminalTypeMappings.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/VT220TerminalTypeMappings.java
new file mode 100644
index 0000000..12075f3
--- /dev/null
+++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/VT220TerminalTypeMappings.java
@@ -0,0 +1,24 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial contribution
+ ******************************************************************************/
+
+package org.eclipse.virgo.osgi.console.telnet;
+
+import org.eclipse.virgo.osgi.console.telnet.ANSITerminalTypeMappings;
+
+public class VT220TerminalTypeMappings extends ANSITerminalTypeMappings {
+
+ public VT220TerminalTypeMappings() {
+ super();
+
+ BACKSPACE = 127;
+ DEL = -1;
+ }
+}
diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/VT320TerminalTypeMappings.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/VT320TerminalTypeMappings.java
new file mode 100644
index 0000000..9a83537
--- /dev/null
+++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/VT320TerminalTypeMappings.java
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial contribution
+ ******************************************************************************/
+
+package org.eclipse.virgo.osgi.console.telnet;
+
+import org.eclipse.virgo.osgi.console.telnet.TerminalTypeMappings;
+import org.eclipse.virgo.osgi.console.common.KEYS;
+
+public class VT320TerminalTypeMappings extends TerminalTypeMappings {
+
+ public VT320TerminalTypeMappings() {
+ super();
+ BACKSPACE = 8;
+ DEL = 127;
+ }
+
+ @Override
+ public void setKeypadMappings() {
+ escapesToKey.put("[H", KEYS.HOME);
+ escapesToKey.put("[F", KEYS.END);
+ escapesToKey.put("[5~", KEYS.PGUP); //$NON-NLS-1$
+ escapesToKey.put("[6~", KEYS.PGDN); //$NON-NLS-1$
+ escapesToKey.put("[2~", KEYS.INS); //$NON-NLS-1$
+ escapesToKey.put("[3~", KEYS.DEL); //$NON-NLS-1$
+
+ }
+}
diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/hook/TelnetHook.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/hook/TelnetHook.java
new file mode 100644
index 0000000..8c2b71e
--- /dev/null
+++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/hook/TelnetHook.java
@@ -0,0 +1,98 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial contribution
+ ******************************************************************************/
+
+package org.eclipse.virgo.osgi.console.telnet.hook;
+
+import org.eclipse.core.runtime.adaptor.EclipseStarter;
+import org.eclipse.osgi.baseadaptor.BaseAdaptor;
+import org.eclipse.osgi.baseadaptor.hooks.AdaptorHook;
+import org.eclipse.osgi.framework.internal.core.FrameworkProperties;
+import org.eclipse.osgi.framework.log.FrameworkLog;
+import org.eclipse.virgo.osgi.console.telnet.TelnetManager;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+
+import java.io.IOException;
+import java.net.URLConnection;
+import java.util.Properties;
+
+/**
+ * This adaptor hook starts the telnet implementation on the port, specified by the OSGi property osgi.console.
+ * Principally, on this port listens the Equinox shell. In order to avoid this, the value of the property (if integer)
+ * is stored, and the property is removed. In this way, when the Equinox ConsoleManager starts, it cannot retrieve any
+ * port and does not start a console on it. After that, during the start of the framework, the hook recovers the
+ * property and starts the telnet on this port.
+ * <p/>
+ * It is possible to pass not only the port, but also the host, in order to restrict the opened server socket to a
+ * particular network address on the host.
+ */
+public class TelnetHook implements AdaptorHook {
+
+ private boolean consolePortAvailable;
+
+ private String consoleValue;
+
+ private TelnetManager telnetManager = null;
+
+ public void initialize(BaseAdaptor adaptor) {
+ consoleValue = FrameworkProperties.getProperty(EclipseStarter.PROP_CONSOLE);
+ if (consoleValue == null) {
+ return;
+ }
+ if (consoleValue.contains(":")) {
+ consolePortAvailable = true;
+ FrameworkProperties.getProperties().remove(EclipseStarter.PROP_CONSOLE);
+ } else {
+ try {
+ Integer.parseInt(consoleValue);
+ consolePortAvailable = true;
+ FrameworkProperties.getProperties().remove(EclipseStarter.PROP_CONSOLE);
+ } catch (NumberFormatException ex) {
+ // do nothing
+ }
+ }
+ }
+
+ public void frameworkStart(BundleContext context) throws BundleException {
+ if (consolePortAvailable == true) {
+ FrameworkProperties.setProperty(EclipseStarter.PROP_CONSOLE, String.valueOf(consoleValue));
+ telnetManager = new TelnetManager(context);
+ telnetManager.startConsoleListener();
+ }
+ }
+
+ public void frameworkStop(BundleContext context) throws BundleException {
+ if (telnetManager != null) {
+ telnetManager.stop();
+ }
+ }
+
+ public void frameworkStopping(BundleContext context) {
+ // Do nothing - we don't care about stopping event
+ }
+
+ public void addProperties(Properties properties) {
+ // Do nothing - we don't care about addProperties event
+ }
+
+ public URLConnection mapLocationToURLConnection(String location) throws IOException {
+ return null; // not supported
+ }
+
+ public void handleRuntimeError(Throwable error) {
+ // Do nothing - we don't know how to handle runtime errors
+ }
+
+ public FrameworkLog createFrameworkLog() {
+ return null; // not supported
+ }
+
+}
diff --git a/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/hook/TelnetHookConfigurator.java b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/hook/TelnetHookConfigurator.java
new file mode 100644
index 0000000..c64c78d
--- /dev/null
+++ b/org.eclipse.virgo.osgi.console/src/main/java/org/eclipse/virgo/osgi/console/telnet/hook/TelnetHookConfigurator.java
@@ -0,0 +1,25 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial contribution
+ ******************************************************************************/
+
+package org.eclipse.virgo.osgi.console.telnet.hook;
+
+import org.eclipse.osgi.baseadaptor.HookConfigurator;
+import org.eclipse.osgi.baseadaptor.HookRegistry;
+
+import org.eclipse.virgo.osgi.console.telnet.hook.TelnetHook;
+
+public class TelnetHookConfigurator implements HookConfigurator {
+
+ public void addHooks(HookRegistry hookRegistry) {
+ hookRegistry.addAdaptorHook(new TelnetHook());
+ }
+
+}
diff --git a/org.eclipse.virgo.osgi.console/src/main/resources/hookconfigurators.properties b/org.eclipse.virgo.osgi.console/src/main/resources/hookconfigurators.properties
new file mode 100644
index 0000000..afd7017
--- /dev/null
+++ b/org.eclipse.virgo.osgi.console/src/main/resources/hookconfigurators.properties
@@ -0,0 +1 @@
+hook.configurators=org.eclipse.virgo.osgi.console.telnet.hook.TelnetHookConfigurator
\ No newline at end of file
diff --git a/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/common/ConsoleInputStreamTests.java b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/common/ConsoleInputStreamTests.java
new file mode 100644
index 0000000..7f63b14
--- /dev/null
+++ b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/common/ConsoleInputStreamTests.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial contribution
+ ******************************************************************************/
+
+package org.eclipse.virgo.osgi.console.common;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class ConsoleInputStreamTests {
+
+ private static final int DATA_LENGTH = 4;
+
+ @Test
+ public void addReadBufferTest() throws Exception {
+ ConsoleInputStream in = new ConsoleInputStream();
+ byte[] data = new byte[] { (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd' };
+ in.add(data);
+ byte[] read = new byte[DATA_LENGTH];
+ for (int i = 0; i < DATA_LENGTH; i++) {
+ in.read(read, i, 1);
+ Assert.assertEquals("Incorrect char read; position " + i + " expected: " + data[i] + ", actual: " + read[i], read[i], data[i]);
+ }
+ }
+
+ @Test
+ public void addReadTest() throws Exception {
+ ConsoleInputStream in = new ConsoleInputStream();
+ byte[] data = new byte[] { (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd' };
+ in.add(data);
+ for (int i = 0; i < DATA_LENGTH; i++) {
+ byte symbol = (byte) in.read();
+ Assert.assertEquals("Incorrect char read; position " + i + " expected: " + data[i] + ", actual: " + symbol, symbol, data[i]);
+ }
+ }
+}
diff --git a/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/common/ConsoleOutputStreamTests.java b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/common/ConsoleOutputStreamTests.java
new file mode 100644
index 0000000..5bbee2f
--- /dev/null
+++ b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/common/ConsoleOutputStreamTests.java
@@ -0,0 +1,41 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial contribution
+ ******************************************************************************/
+
+package org.eclipse.virgo.osgi.console.common;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+
+public class ConsoleOutputStreamTests {
+
+ private static final int DATA_LENGTH = 4;
+
+ @Test
+ public void testWrite() throws Exception {
+ ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
+ ConsoleOutputStream out = new ConsoleOutputStream(byteOut);
+ byte[] data = new byte[] { 'a', 'b', 'c', 'd' };
+ for (byte b : data) {
+ out.write(b);
+ }
+ out.flush();
+ byte[] res = byteOut.toByteArray();
+
+ Assert.assertNotNull("Bytes not written; result null", res);
+ Assert.assertFalse("Bytes not written; result empty", res.length == 0);
+
+ for (int i = 0; i < DATA_LENGTH; i++) {
+ Assert.assertEquals("Wrong char read. Position " + i + ", expected " + data[i] + ", read " + res[i], data[i], res[i]);
+ }
+ }
+}
diff --git a/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/common/HistoryHolderTests.java b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/common/HistoryHolderTests.java
new file mode 100644
index 0000000..1633418
--- /dev/null
+++ b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/common/HistoryHolderTests.java
@@ -0,0 +1,68 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial contribution
+ ******************************************************************************/
+
+package org.eclipse.virgo.osgi.console.common;
+
+import org.junit.Assert;
+
+import org.eclipse.virgo.osgi.console.supportability.HistoryHolder;
+import org.junit.Test;
+
+public class HistoryHolderTests {
+
+ @Test
+ public void test() {
+ HistoryHolder historyHolder = new HistoryHolder();
+ byte[] line1 = new byte[] { 'a', 'b', 'c', 'd' };
+ byte[] line2 = new byte[] { 'x', 'y', 'z' };
+ byte[] line3 = new byte[] { 'k', 'l', 'm', 'n' };
+
+ historyHolder.add(line1);
+ historyHolder.add(line2);
+ historyHolder.add(line3);
+
+ byte[] first = historyHolder.first();
+ Assert.assertEquals("Wrong length of first member", line1.length, first.length);
+ Assert.assertArrayEquals("Wrong first member", line1, first);
+
+ byte[] last = historyHolder.last();
+ Assert.assertEquals("Wrong length of last member", line3.length, last.length);
+ Assert.assertArrayEquals("Wrong last member", line3, last);
+
+ byte[] prev = historyHolder.prev();
+ Assert.assertEquals("Wrong length of previous member", line2.length, prev.length);
+ Assert.assertArrayEquals("Wrong previous member", line2, prev);
+
+ byte[] next = historyHolder.next();
+ Assert.assertEquals("Wrong length of next member", line3.length, next.length);
+ Assert.assertArrayEquals("Wrong next member", line3, next);
+
+ historyHolder.first();
+ historyHolder.add(new byte[] {});
+ byte[] current = historyHolder.prev();
+ Assert.assertEquals("Wrong length of next member", line3.length, current.length);
+ Assert.assertArrayEquals("Wrong next member", line3, current);
+
+ historyHolder.first();
+ historyHolder.add(line1);
+ current = historyHolder.prev();
+ Assert.assertEquals("Wrong length of next member", line1.length, current.length);
+ Assert.assertArrayEquals("Wrong next member", line1, current);
+ Assert.assertArrayEquals("Second line should now be first", line2, historyHolder.first());
+
+ historyHolder.reset();
+ Assert.assertNull("History should be empty", historyHolder.first());
+ Assert.assertNull("History should be empty", historyHolder.last());
+ Assert.assertNull("History should be empty", historyHolder.next());
+ Assert.assertNull("History should be empty", historyHolder.prev());
+ }
+
+}
diff --git a/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/common/SimpleByteBufferTests.java b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/common/SimpleByteBufferTests.java
new file mode 100644
index 0000000..0f1c716
--- /dev/null
+++ b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/common/SimpleByteBufferTests.java
@@ -0,0 +1,96 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial contribution
+ ******************************************************************************/
+
+package org.eclipse.virgo.osgi.console.common;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class SimpleByteBufferTests {
+
+ @Test
+ public void testBuffer() throws Exception {
+ SimpleByteBuffer buffer = new SimpleByteBuffer();
+ buffer.add('a');
+ buffer.add('b');
+ buffer.add('c');
+ buffer.add('d');
+
+ Assert.assertTrue("Wrong buffer size; expected 4, actual " + buffer.getSize(), buffer.getSize() == 4);
+
+ check(buffer, new byte[] { 'a', 'b', 'c', 'd' });
+
+ byte[] data = buffer.getCurrentData();
+ byte[] expected = new byte[] { 'a', 'b', 'c', 'd' };
+
+ Assert.assertTrue("Data not as expected: expected length " + expected.length + ", actual length " + data.length,
+ data.length == expected.length);
+
+ for (int i = 0; i < data.length; i++) {
+ Assert.assertEquals("Incorrect data read. Position " + i + ", expected " + expected[i] + ", read " + data[i], expected[i], data[i]);
+ }
+
+ buffer.insert('a');
+ buffer.insert('b');
+ buffer.insert('c');
+ buffer.insert('d');
+
+ int pos = buffer.getPos();
+ buffer.goLeft();
+ int newPos = buffer.getPos();
+ Assert.assertEquals("Error while moving left; old pos: " + pos + ", new pos: ", pos - 1, newPos);
+
+ buffer.insert('e');
+ check(buffer, new byte[] { 'a', 'b', 'c', 'e', 'd' });
+
+ buffer.goLeft();
+ buffer.delete();
+ check(buffer, new byte[] { 'a', 'b', 'c', 'd' });
+
+ pos = buffer.getPos();
+ buffer.goRight();
+ newPos = buffer.getPos();
+ Assert.assertEquals("Error while moving right; old pos: " + pos + ", new pos: ", pos + 1, newPos);
+
+ buffer.backSpace();
+ check(buffer, new byte[] { 'a', 'b', 'c' });
+
+ buffer.delAll();
+ Assert.assertTrue("Bytes in buffer not correctly deleted", (buffer.getSize() == 0) && (buffer.getPos() == 0));
+
+ buffer.set(new byte[] { 'a', 'b', 'c', 'd' });
+ check(buffer, new byte[] { 'a', 'b', 'c', 'd' });
+
+ data = buffer.copyCurrentData();
+ Assert.assertArrayEquals("Buffer copy does not work properly", new byte[] { 'a', 'b', 'c', 'd' }, data);
+
+ buffer.goLeft();
+ buffer.replace('e');
+ check(buffer, new byte[] { 'a', 'b', 'c', 'e' });
+
+ buffer.resetPos();
+ Assert.assertTrue("Resetting position does not work properly", buffer.getPos() == 0);
+
+ Assert.assertEquals("Wrong current char", 'a', buffer.getCurrentChar());
+ }
+
+ private void check(SimpleByteBuffer buffer, byte[] expected) throws Exception {
+ byte[] data = buffer.copyCurrentData();
+
+ Assert.assertTrue("Data not as expected: expected length " + expected.length + ", actual length " + data.length,
+ data.length == expected.length);
+
+ for (int i = 0; i < data.length; i++) {
+ Assert.assertEquals("Incorrect data read. Position " + i + ", expected " + expected[i] + ", read " + data[i], expected[i], data[i]);
+ }
+ }
+
+}
diff --git a/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/supportability/CommandCompleterTests.java b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/supportability/CommandCompleterTests.java
new file mode 100644
index 0000000..a320b0a
--- /dev/null
+++ b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/supportability/CommandCompleterTests.java
@@ -0,0 +1,99 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial contribution
+ ******************************************************************************/
+
+package org.eclipse.virgo.osgi.console.supportability;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.eclipse.osgi.framework.console.CommandProvider;
+import org.junit.Test;
+import org.junit.Assert;
+import org.eclipse.virgo.teststubs.osgi.framework.StubBundleContext;
+import org.eclipse.virgo.teststubs.osgi.support.ObjectClassFilter;
+
+public class CommandCompleterTests {
+
+ @Test
+ public void testCompleter() throws Exception {
+ StubBundleContext context = new StubBundleContext();
+ TestCommandProvider commandProvider = new TestCommandProvider();
+ context.registerService(CommandProvider.class.getName(), commandProvider, null);
+ context.addFilter(new ObjectClassFilter(CommandProvider.class.getName()));
+ CommandCompleter completer = new CommandCompleter(context);
+ String[] matches = completer.complete("test");
+ Set<String> setMatches = convertToSet(matches);
+ Assert.assertEquals("Incorrect number of matches:", 3, setMatches.size());
+ Assert.assertTrue("Matches does not contain test", setMatches.contains("test"));
+ Assert.assertTrue("Matches does not contain testMethod", setMatches.contains("testMethod"));
+ Assert.assertTrue("Matches does not contain testMethod1", setMatches.contains("testMethod1"));
+
+ matches = completer.complete("dum");
+ setMatches = convertToSet(matches);
+ Assert.assertEquals("Incorrect number of matches:", 2, setMatches.size());
+ Assert.assertTrue("Matches does not contain dummy", setMatches.contains("dummy"));
+ Assert.assertTrue("Matches does not contain dummyMethod", setMatches.contains("dummyMethod"));
+
+ matches = completer.complete("fake");
+ setMatches = convertToSet(matches);
+ Assert.assertEquals("Incorrect number of matches:", 2, setMatches.size());
+ Assert.assertTrue("Matches does not contain fake", setMatches.contains("fake"));
+ Assert.assertTrue("Matches does not contain fake1", setMatches.contains("fake1"));
+
+ matches = completer.complete("help");
+ Assert.assertEquals("Matches should be empty", 0, matches.length);
+ }
+
+ private Set<String> convertToSet(String[] matches) {
+ HashSet<String> set = new HashSet<String>();
+ for (String match : matches) {
+ set.add(match);
+ }
+
+ return set;
+ }
+
+ class TestCommandProvider implements CommandProvider {
+
+ @Override
+ public String getHelp() {
+ return null;
+ }
+
+ public void _test() {
+
+ }
+
+ public void _testMethod() {
+
+ }
+
+ public void _testMethod1() {
+
+ }
+
+ public void _dummy() {
+
+ }
+
+ public void _dummyMethod() {
+
+ }
+
+ public void _fake() {
+
+ }
+
+ public void _fake1() {
+
+ }
+ }
+}
diff --git a/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/supportability/ConsoleInputHandlerTests.java b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/supportability/ConsoleInputHandlerTests.java
new file mode 100644
index 0000000..80d2042
--- /dev/null
+++ b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/supportability/ConsoleInputHandlerTests.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial contribution
+ ******************************************************************************/
+
+package org.eclipse.virgo.osgi.console.supportability;
+
+import org.eclipse.virgo.osgi.console.common.ConsoleInputStream;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+
+public class ConsoleInputHandlerTests {
+
+ private static final long WAIT_TIME = 10000;
+
+ @Test
+ public void testHandler() throws Exception {
+ PipedInputStream input = new PipedInputStream();
+ PipedOutputStream output = new PipedOutputStream(input);
+ ConsoleInputStream in = new ConsoleInputStream();
+ ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
+ ConsoleInputHandler handler = new ConsoleInputHandler(input, in, byteOut, null);
+ byte[] expected = new byte[] { 'a', 'b', 'c', 'd', 'e', '\r', '\n' };
+ output.write(expected);
+ output.flush();
+ handler.start();
+
+ try {
+ Thread.sleep(WAIT_TIME);
+ } catch (Exception e) {
+ // do nothing
+ }
+
+ byte[] read = new byte[expected.length];
+ for (int i = 0; i < expected.length; i++) {
+ in.read(read, i, 4);
+ Assert.assertEquals("Incorrect char read. Position " + i + ", expected " + expected[i] + ", read " + read[i], expected[i], read[i]);
+ }
+
+ output.close();
+ input.close();
+ }
+
+}
diff --git a/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/supportability/ConsoleInputScannerTests.java b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/supportability/ConsoleInputScannerTests.java
new file mode 100644
index 0000000..a9d5c7d
--- /dev/null
+++ b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/supportability/ConsoleInputScannerTests.java
@@ -0,0 +1,256 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial contribution
+ ******************************************************************************/
+
+package org.eclipse.virgo.osgi.console.supportability;
+
+import org.eclipse.osgi.framework.console.CommandProvider;
+import org.eclipse.virgo.osgi.console.common.ConsoleInputStream;
+import org.eclipse.virgo.osgi.console.common.ConsoleOutputStream;
+import org.eclipse.virgo.osgi.console.common.KEYS;
+import org.eclipse.virgo.osgi.console.telnet.ANSITerminalTypeMappings;
+import org.eclipse.virgo.osgi.console.telnet.SCOTerminalTypeMappings;
+import org.eclipse.virgo.osgi.console.telnet.TerminalTypeMappings;
+import org.eclipse.virgo.osgi.console.telnet.VT100TerminalTypeMappings;
+import org.eclipse.virgo.osgi.console.telnet.VT220TerminalTypeMappings;
+import org.eclipse.virgo.osgi.console.telnet.VT320TerminalTypeMappings;
+import org.eclipse.virgo.teststubs.osgi.framework.StubBundleContext;
+import org.eclipse.virgo.teststubs.osgi.support.ObjectClassFilter;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+public class ConsoleInputScannerTests {
+
+ private static int BS;
+
+ private static final int LF = 10;
+
+ private static final int CR = 13;
+
+ private static final int ESC = 27;
+
+ private static int DELL;
+
+ @Test
+ public void test() throws Exception {
+ Set<TerminalTypeMappings> supportedEscapeSequences = new HashSet<TerminalTypeMappings>();
+ supportedEscapeSequences.add(new ANSITerminalTypeMappings());
+ supportedEscapeSequences.add(new VT100TerminalTypeMappings());
+ supportedEscapeSequences.add(new VT220TerminalTypeMappings());
+ supportedEscapeSequences.add(new VT320TerminalTypeMappings());
+ supportedEscapeSequences.add(new SCOTerminalTypeMappings());
+
+ for (TerminalTypeMappings ttMappings : supportedEscapeSequences) {
+ Map<String, KEYS> escapesToKey = ttMappings.getEscapesToKey();
+ Map<KEYS, byte[]> keysToEscapes = new HashMap<KEYS, byte[]>();
+ for (Entry<String, KEYS> entry : escapesToKey.entrySet()) {
+ keysToEscapes.put(entry.getValue(), entry.getKey().getBytes());
+ }
+
+ BS = ttMappings.getBackspace();
+ DELL = ttMappings.getDel();
+
+ testScan(ttMappings, keysToEscapes);
+ }
+ }
+
+ private void testScan(TerminalTypeMappings mappings, Map<KEYS, byte[]> keysToEscapes) throws Exception {
+ ConsoleInputStream in = new ConsoleInputStream();
+ ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
+ ConsoleOutputStream out = new ConsoleOutputStream(byteOut);
+ StubBundleContext context = new StubBundleContext();
+ TestCommandProvider commandProvider = new TestCommandProvider();
+ context.registerService(CommandProvider.class.getName(), commandProvider, null);
+ context.addFilter(new ObjectClassFilter(CommandProvider.class.getName()));
+ ConsoleInputScanner scanner = new ConsoleInputScanner(in, out, context);
+ scanner.setBackspace(mappings.getBackspace());
+ scanner.setCurrentEscapesToKey(mappings.getEscapesToKey());
+ scanner.setDel(mappings.getDel());
+ scanner.setEscapes(mappings.getEscapes());
+
+ byte[] line1 = new byte[] { 'a', 'b', 'c', 'd', 'e' };
+ byte[] line2 = new byte[] { 't', 'e', 's', 't' };
+ byte[] line3 = new byte[] { 'l', 'a', 's', 't' };
+
+ addLine(scanner, line1);
+ checkInpusStream(in, line1);
+
+ addLine(scanner, line2);
+ checkInpusStream(in, line2);
+
+ addLine(scanner, line3);
+ checkInpusStream(in, line3);
+
+ add(scanner, keysToEscapes.get(KEYS.UP));
+ add(scanner, keysToEscapes.get(KEYS.UP));
+ String res = byteOut.toString();
+ Assert.assertTrue("Error processing up arrow; expected test, actual " + res.substring(res.length() - 4), res.endsWith("test"));
+
+ add(scanner, keysToEscapes.get(KEYS.DOWN));
+ res = byteOut.toString();
+ Assert.assertTrue("Error processing down arrow; expected last, actual " + res.substring(res.length() - 4), res.endsWith("last"));
+
+ add(scanner, keysToEscapes.get(KEYS.PGUP));
+ res = byteOut.toString();
+ Assert.assertTrue("Error processing PageUp; expected abcde, actual " + res.substring(res.length() - 4), res.endsWith("abcde"));
+
+ add(scanner, keysToEscapes.get(KEYS.PGDN));
+ res = byteOut.toString();
+ Assert.assertTrue("Error processing PageDown; expected last, actual " + res.substring(res.length() - 4), res.endsWith("last"));
+
+ if (BS > 0) {
+ scanner.scan(BS);
+ res = byteOut.toString();
+ Assert.assertTrue("Error processing backspace; expected las, actual " + res.substring(res.length() - 3), res.endsWith("las"));
+ scanner.scan('t');
+ }
+
+ if (DELL > 0) {
+ add(scanner, keysToEscapes.get(KEYS.LEFT));
+ scanner.scan(DELL);
+ res = byteOut.toString();
+ Assert.assertTrue("Error processing del; expected las, actual " + res.substring(res.length() - 3), res.endsWith("las"));
+ scanner.scan('t');
+ }
+
+ add(scanner, keysToEscapes.get(KEYS.LEFT));
+ add(scanner, keysToEscapes.get(KEYS.LEFT));
+ add(scanner, keysToEscapes.get(KEYS.RIGHT));
+ if (DELL > 0) {
+ scanner.scan(DELL);
+ } else {
+ add(scanner, keysToEscapes.get(KEYS.DEL));
+ }
+ res = byteOut.toString();
+ Assert.assertTrue("Error processing arrows; expected las, actual " + res.substring(res.length() - 3), res.endsWith("las"));
+ scanner.scan('t');
+
+ if (keysToEscapes.get(KEYS.DEL) != null) {
+ add(scanner, keysToEscapes.get(KEYS.LEFT));
+ add(scanner, keysToEscapes.get(KEYS.DEL));
+ res = byteOut.toString();
+ Assert.assertTrue("Error processing delete; expected las, actual " + res.substring(res.length() - 3), res.endsWith("las"));
+ scanner.scan('t');
+ }
+
+ add(scanner, keysToEscapes.get(KEYS.HOME));
+ if (DELL > 0) {
+ scanner.scan(DELL);
+ } else {
+ add(scanner, keysToEscapes.get(KEYS.DEL));
+ }
+ res = byteOut.toString();
+ res = res.substring(res.length() - 6, res.length() - 3);
+ Assert.assertTrue("Error processing Home; expected ast, actual " + res, res.equals("ast"));
+ scanner.scan('l');
+
+ add(scanner, keysToEscapes.get(KEYS.END));
+ add(scanner, keysToEscapes.get(KEYS.LEFT));
+ if (DELL > 0) {
+ scanner.scan(DELL);
+ } else {
+ add(scanner, keysToEscapes.get(KEYS.DEL));
+ }
+ res = byteOut.toString();
+ Assert.assertTrue("Error processing End; expected las, actual " + res.substring(res.length() - 3), res.endsWith("las"));
+ scanner.scan('t');
+
+ add(scanner, keysToEscapes.get(KEYS.LEFT));
+ add(scanner, keysToEscapes.get(KEYS.INS));
+ scanner.scan('a');
+ res = byteOut.toString();
+ Assert.assertTrue("Error processing Ins; expected las, actual " + res.substring(res.length() - 4), res.endsWith("lasa"));
+
+ scanner.scan(CR);
+ scanner.scan(LF);
+ scanner.scan('t');
+ scanner.scan('e');
+ scanner.scan(9);
+ res = byteOut.toString();
+ Assert.assertTrue("Expected completion suggestions are not contained in the output", res.contains("test testMethod"));
+ }
+
+ private static void addLine(ConsoleInputScanner scanner, byte[] line) throws Exception {
+ for (byte b : line) {
+ try {
+ scanner.scan(b);
+ } catch (Exception e) {
+ System.out.println("Error scanning symbol " + b);
+ throw new Exception("Error scanning symbol" + b);
+ }
+ }
+
+ try {
+ scanner.scan(CR);
+ } catch (Exception e) {
+ System.out.println("Error scanning symbol " + CR);
+ throw new Exception("Error scanning symbol " + CR);
+ }
+
+ try {
+ scanner.scan(LF);
+ } catch (Exception e) {
+ System.out.println("Error scanning symbol " + LF);
+ throw new Exception("Error scanning symbol " + LF);
+ }
+ }
+
+ private void add(ConsoleInputScanner scanner, byte[] sequence) throws Exception {
+ scanner.scan(ESC);
+ for (byte b : sequence) {
+ scanner.scan(b);
+ }
+ }
+
+ private void checkInpusStream(ConsoleInputStream in, byte[] expected) throws Exception {
+ // the actual number of bytes in the stream is two more than the bytes in the array, because of the CR and LF
+ // symbols, added after the array
+ byte[] read = new byte[expected.length + 2];
+ for (int i = 0; i < expected.length; i++) {
+ in.read(read, i, 1);
+ Assert.assertEquals("Incorrect char read. Position " + i + ", expected " + expected[i] + ", read " + read[i], expected[i], read[i]);
+ }
+ in.read(read, expected.length, 1);
+ in.read(read, expected.length + 1, 1);
+ }
+
+ class TestCommandProvider implements CommandProvider {
+
+ @Override
+ public String getHelp() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public void _test() {
+
+ }
+
+ public void _testMethod() {
+
+ }
+
+ public void _dummy() {
+
+ }
+
+ public void _fake() {
+
+ }
+
+ }
+}
diff --git a/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/supportability/GrepTests.java b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/supportability/GrepTests.java
new file mode 100644
index 0000000..0cbbfe3
--- /dev/null
+++ b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/supportability/GrepTests.java
@@ -0,0 +1,93 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial contribution
+ ******************************************************************************/
+
+package org.eclipse.virgo.osgi.console.supportability;
+
+import java.io.ByteArrayOutputStream;
+
+import org.eclipse.virgo.osgi.console.common.ConsoleOutputStream;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class GrepTests {
+
+ private static final int CR = 13;
+
+ private static final int LF = 10;
+
+ @Test
+ public void testGrep() throws Exception {
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ ConsoleOutputStream consoleOut = new ConsoleOutputStream(output);
+ byte[] expression = "test".getBytes();
+ Grep grep = new Grep(expression, consoleOut);
+ grep.start();
+ byte[] line1 = new byte[] { 't', 'e', 's', 't' };
+ byte[] line2 = new byte[] { 'a', 'b', 'c', 'd' };
+ byte[] line3 = new byte[] { 't', 'e', 's', 't', 'e', 'r' };
+ byte[] line4 = new byte[] { 'k', 'l', 'm', 'n' };
+ byte[] line5 = new byte[] { 't', 'e', 's', 't', 'i', 'n', 'g' };
+ byte[] line6 = new byte[] { 'o', 's', 'g', 'i', '>' };
+
+ addLine(consoleOut, line1);
+ addLine(consoleOut, line2);
+ addLine(consoleOut, line3);
+ addLine(consoleOut, line4);
+ addLine(consoleOut, line5);
+
+ for (byte b : line6) {
+ consoleOut.write(b);
+ }
+ consoleOut.flush();
+
+ grep.join();
+
+ byte[] expectedResult = new byte[] { 't', 'e', 's', 't', CR, LF, 't', 'e', 's', 't', 'e', 'r', CR, LF, 't', 'e', 's', 't', 'i', 'n', 'g', CR,
+ LF, 'o', 's', 'g', 'i', '>', ' ' };
+ byte[] result = output.toByteArray();
+
+ Assert.assertNotNull("Bytes not written; result null", result);
+ Assert.assertFalse("Bytes not written; result empty", result.length == 0);
+ Assert.assertTrue("Bytes written to output differ in number from expected", result.length == expectedResult.length);
+
+ for (int i = 0; i < result.length; i++) {
+ Assert.assertEquals("Wrong char read. Position " + i + ", expected " + expectedResult[i] + ", read " + result[i], expectedResult[i],
+ result[i]);
+ }
+ }
+
+ private static void addLine(ConsoleOutputStream out, byte[] line) throws Exception {
+ for (byte b : line) {
+ try {
+ out.write(b);
+ } catch (Exception e) {
+ System.out.println("Error writing symbol " + b);
+ throw new Exception("Error writing symbol" + b);
+ }
+ }
+
+ try {
+ out.write(CR);
+ } catch (Exception e) {
+ System.out.println("Error writing symbol " + CR);
+ throw new Exception("Error writing symbol " + CR);
+ }
+
+ try {
+ out.write(LF);
+ } catch (Exception e) {
+ System.out.println("Error writing symbol " + LF);
+ throw new Exception("Error writing symbol " + LF);
+ }
+
+ out.flush();
+ }
+}
diff --git a/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/telnet/NegotiationFinishedCallbackTests.java b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/telnet/NegotiationFinishedCallbackTests.java
new file mode 100644
index 0000000..c065840
--- /dev/null
+++ b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/telnet/NegotiationFinishedCallbackTests.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial contribution
+ ******************************************************************************/
+
+package org.eclipse.virgo.osgi.console.telnet;
+
+import java.net.ServerSocket;
+import java.net.Socket;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class NegotiationFinishedCallbackTests {
+
+ @Test
+ public void finishTest() throws Exception {
+ ServerSocket servSocket = null;
+ Socket socketClient = null;
+ Socket socketServer = null;
+ TelnetConsoleSession consoleSession = null;
+ try {
+ servSocket = new ServerSocket(0);
+ socketClient = new Socket("localhost", servSocket.getLocalPort());
+ socketServer = servSocket.accept();
+
+ consoleSession = new TelnetConsoleSession(socketServer, null);
+ NegotiationFinishedCallback callback = new NegotiationFinishedCallback(consoleSession);
+ callback.finished();
+ Assert.assertTrue("Finished not called on console session", consoleSession.isTelnetNegotiationFinished);
+ } finally {
+ if (socketClient != null) {
+ socketClient.close();
+ }
+ if (consoleSession != null) {
+ consoleSession.doClose();
+ }
+
+ try {
+ if (socketServer != null) {
+ Assert.assertTrue("Server telnet socket is not closed.", socketServer.isClosed());
+ }
+ } finally {
+ if (servSocket != null) {
+ servSocket.close();
+ }
+ }
+ }
+ }
+}
diff --git a/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/telnet/TelnetConsoleSessionTests.java b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/telnet/TelnetConsoleSessionTests.java
new file mode 100644
index 0000000..92569b8
--- /dev/null
+++ b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/telnet/TelnetConsoleSessionTests.java
@@ -0,0 +1,129 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial contribution
+ ******************************************************************************/
+
+package org.eclipse.virgo.osgi.console.telnet;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+public class TelnetConsoleSessionTests {
+
+ private static final int TEST_CONTENT = 100;
+
+ private static final int IAC = 255;
+
+ @Test
+ public void testConsoleSession() throws Exception {
+ ServerSocket servSocket = null;
+ Socket socketClient = null;
+ Socket socketServer = null;
+ TelnetConsoleSession consoleServer = null;
+ OutputStream outClient = null;
+ OutputStream outServer = null;
+
+ try {
+ servSocket = new ServerSocket(0);
+ socketClient = new Socket("localhost", servSocket.getLocalPort());
+ socketServer = servSocket.accept();
+
+ consoleServer = new TelnetConsoleSession(socketServer, null);
+ consoleServer.start();
+
+ outClient = socketClient.getOutputStream();
+ outClient.write(TEST_CONTENT);
+ outClient.write('\n');
+ outClient.flush();
+
+ InputStream input = consoleServer.getInput();
+ int in = input.read();
+ Assert.assertTrue("Server received [" + in + "] instead of " + TEST_CONTENT + " from the telnet client.", in == TEST_CONTENT);
+
+ input = socketClient.getInputStream();
+ in = input.read();
+ // here IAC is expected, since when the output stream in TelnetConsoleSession is created, several telnet
+ // commands are written to it, each of them starting with IAC
+ Assert.assertTrue("Client receive telnet responses from the server unexpected value [" + in + "] instead of " + IAC + ".", in == IAC);
+ } finally {
+ if (socketClient != null) {
+ socketClient.close();
+ }
+ if (consoleServer != null) {
+ consoleServer.doClose();
+ }
+ if (outClient != null) {
+ outClient.close();
+ }
+ if (outServer != null) {
+ outServer.close();
+ }
+
+ try {
+ if (socketServer != null) {
+ Assert.assertTrue("Server telnet socket is not closed.", socketServer.isClosed());
+ }
+ } finally {
+ if (servSocket != null) {
+ servSocket.close();
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testConsoleSessionVoidWrapper() throws Exception {
+ ServerSocket servSocket = null;
+ Socket socketClient = null;
+ Socket socketServer = null;
+ TelnetConsoleSession consoleServer = null;
+
+ try {
+ servSocket = new ServerSocket(0);
+ socketClient = new Socket("localhost", servSocket.getLocalPort());
+ socketServer = servSocket.accept();
+
+ consoleServer = new TelnetConsoleSession(socketServer, null);
+ consoleServer.start();
+
+ OutputStream outClient = socketClient.getOutputStream();
+ outClient.write(TEST_CONTENT);
+ outClient.write('\n');
+ outClient.flush();
+
+ InputStream input = consoleServer.getInput();
+ int in = input.read();
+
+ Assert.assertTrue("Server received [" + in + "] instead of " + TEST_CONTENT + " from the telnet client.", in == TEST_CONTENT);
+ } finally {
+ if (socketClient != null) {
+ socketClient.close();
+ }
+ if (consoleServer != null) {
+ consoleServer.doClose();
+ }
+
+ try {
+ if (socketServer != null) {
+ Assert.assertTrue("Server telnet socket is not closed.", socketServer.isClosed());
+ }
+ } finally {
+ if (servSocket != null) {
+ servSocket.close();
+ }
+ }
+ }
+ }
+
+}
diff --git a/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/telnet/TelnetInputHandlerTests.java b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/telnet/TelnetInputHandlerTests.java
new file mode 100644
index 0000000..d89fec1
--- /dev/null
+++ b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/telnet/TelnetInputHandlerTests.java
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial contribution
+ ******************************************************************************/
+
+package org.eclipse.virgo.osgi.console.telnet;
+
+import org.eclipse.virgo.osgi.console.common.ConsoleInputStream;
+import org.eclipse.virgo.osgi.console.common.ConsoleOutputStream;
+import org.junit.Assert;
+import org.junit.Test;
+
+import static org.easymock.EasyMock.*;
+
+import java.io.ByteArrayOutputStream;
+import java.io.StringBufferInputStream;
+
+public class TelnetInputHandlerTests {
+
+ private static final long WAIT_TIME = 10000;
+
+ @Test
+ public void testHandler() throws Exception {
+ StringBufferInputStream input = new StringBufferInputStream("abcde");
+ ConsoleInputStream in = new ConsoleInputStream();
+ ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
+ ConsoleOutputStream out = new ConsoleOutputStream(byteOut);
+ Callback callback = createMock(Callback.class);
+ TelnetInputHandler handler = new TelnetInputHandler(input, in, out, callback);
+ handler.start();
+
+ // wait for the accept thread to start execution
+ try {
+ Thread.sleep(WAIT_TIME);
+ } catch (InterruptedException ie) {
+ // do nothing
+ }
+
+ String res = byteOut.toString();
+ Assert.assertTrue("Wrong input. Expected abcde, read " + res, res.equals("abcde"));
+ }
+
+}
diff --git a/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/telnet/TelnetInputScannerTests.java b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/telnet/TelnetInputScannerTests.java
new file mode 100644
index 0000000..feda304
--- /dev/null
+++ b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/telnet/TelnetInputScannerTests.java
@@ -0,0 +1,212 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial contribution
+ ******************************************************************************/
+
+package org.eclipse.virgo.osgi.console.telnet;
+
+import org.eclipse.virgo.osgi.console.common.ConsoleInputStream;
+import org.eclipse.virgo.osgi.console.common.ConsoleOutputStream;
+import org.eclipse.virgo.osgi.console.common.KEYS;
+import org.junit.Assert;
+import org.junit.Test;
+
+import static org.easymock.EasyMock.*;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+public class TelnetInputScannerTests {
+
+ private static final int IAC = 255;
+
+ private static final int DO = 253;
+
+ private static final int TTYPE = 24;
+
+ private static final int WILL = 251;
+
+ private static final int SB = 250;
+
+ private static final int SE = 240;
+
+ private static final int SEND = 1;
+
+ private static final int IS = 0;
+
+ protected static final byte ESC = 27;
+
+ @Test
+ public void testScan() throws Exception {
+ ConsoleInputStream in = new ConsoleInputStream();
+ ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
+ ConsoleOutputStream out = new ConsoleOutputStream(byteOut);
+ Callback callback = createMock(Callback.class);
+ TelnetInputScanner scanner = new TelnetInputScanner(in, out, callback);
+ try {
+ scanner.scan((byte) 240);
+ scanner.scan((byte) 248);
+ scanner.scan((byte) 250);
+ scanner.scan((byte) 251);
+ scanner.scan((byte) 252);
+ scanner.scan((byte) 253);
+ scanner.scan((byte) 254);
+ scanner.scan((byte) 'a');
+ scanner.scan((byte) 'b');
+ scanner.scan((byte) 'c');
+ } catch (IOException e) {
+ System.out.println("Error while scanning: " + e.getMessage());
+ e.printStackTrace();
+ throw e;
+ }
+
+ String output = byteOut.toString();
+ Assert.assertTrue("Output incorrect. Expected abc, but read " + output, output.equals("abc"));
+ }
+
+ @Test
+ public void testScanESC() throws Exception {
+ ConsoleInputStream in = new ConsoleInputStream();
+ ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
+ ConsoleOutputStream out = new ConsoleOutputStream(byteOut);
+ Callback callback = createMock(Callback.class);
+ TelnetInputScanner scanner = new TelnetInputScanner(in, out, callback);
+
+ try {
+ scanner.scan((byte) 'a');
+ scanner.scan((byte) ESC);
+ scanner.scan((byte) 'b');
+ } catch (IOException e) {
+ System.out.println("Error while scanning: " + e.getMessage());
+ e.printStackTrace();
+ throw e;
+ }
+
+ String output = byteOut.toString();
+ Assert.assertTrue("Output incorrect. Expected abc, but read " + output, output.equals("ab"));
+ }
+
+ @Test
+ public void testTTNegotiations() throws Exception {
+ Map<byte[], TerminalTypeMappings> ttMappings = new HashMap<byte[], TerminalTypeMappings>();
+ ttMappings.put(new byte[] { 'A', 'N', 'S', 'I' }, new ANSITerminalTypeMappings());
+ ttMappings.put(new byte[] { 'V', 'T', '1', '0', '0' }, new VT100TerminalTypeMappings());
+ ttMappings.put(new byte[] { 'V', 'T', '2', '2', '0' }, new VT220TerminalTypeMappings());
+ ttMappings.put(new byte[] { 'X', 'T', 'E', 'R', 'M' }, new VT220TerminalTypeMappings());
+ ttMappings.put(new byte[] { 'V', 'T', '3', '2', '0' }, new VT320TerminalTypeMappings());
+ ttMappings.put(new byte[] { 'S', 'C', 'O' }, new SCOTerminalTypeMappings());
+
+ for (byte[] ttype : ttMappings.keySet()) {
+ testTerminalTypeNegotiation(ttype, ttMappings.get(ttype));
+ }
+ }
+
+ private void testTerminalTypeNegotiation(byte[] terminalType, TerminalTypeMappings mappings) throws Exception {
+ PipedInputStream clientIn = new PipedInputStream();
+ PipedOutputStream serverOut = new PipedOutputStream(clientIn);
+
+ byte[] requestNegotiation = { (byte) IAC, (byte) DO, (byte) TTYPE };
+
+ TestCallback testCallback = new TestCallback();
+ TelnetOutputStream out = new TelnetOutputStream(serverOut);
+ TelnetInputScanner scanner = new TelnetInputScanner(new ConsoleInputStream(), out, testCallback);
+ out.write(requestNegotiation);
+ out.flush();
+
+ int read = clientIn.read();
+ Assert.assertEquals("Unexpected input ", IAC, read);
+ read = clientIn.read();
+ Assert.assertEquals("Unexpected input ", DO, read);
+ read = clientIn.read();
+ Assert.assertEquals("Unexpected input ", TTYPE, read);
+
+ scanner.scan(IAC);
+ scanner.scan(WILL);
+ scanner.scan(TTYPE);
+
+ read = clientIn.read();
+ Assert.assertEquals("Unexpected input ", IAC, read);
+ read = clientIn.read();
+ Assert.assertEquals("Unexpected input ", SB, read);
+ read = clientIn.read();
+ Assert.assertEquals("Unexpected input ", TTYPE, read);
+ read = clientIn.read();
+ Assert.assertEquals("Unexpected input ", SEND, read);
+ read = clientIn.read();
+ Assert.assertEquals("Unexpected input ", IAC, read);
+ read = clientIn.read();
+ Assert.assertEquals("Unexpected input ", SE, read);
+
+ scanner.scan(IAC);
+ scanner.scan(SB);
+ scanner.scan(TTYPE);
+ scanner.scan(IS);
+ scanner.scan('A');
+ scanner.scan('B');
+ scanner.scan('C');
+ scanner.scan('D');
+ scanner.scan('E');
+ scanner.scan(IAC);
+ scanner.scan(SE);
+
+ read = clientIn.read();
+ Assert.assertEquals("Unexpected input ", IAC, read);
+ read = clientIn.read();
+ Assert.assertEquals("Unexpected input ", SB, read);
+ read = clientIn.read();
+ Assert.assertEquals("Unexpected input ", TTYPE, read);
+ read = clientIn.read();
+ Assert.assertEquals("Unexpected input ", SEND, read);
+ read = clientIn.read();
+ Assert.assertEquals("Unexpected input ", IAC, read);
+ read = clientIn.read();
+ Assert.assertEquals("Unexpected input ", SE, read);
+
+ scanner.scan(IAC);
+ scanner.scan(SB);
+ scanner.scan(TTYPE);
+ scanner.scan(IS);
+ for (byte symbol : terminalType) {
+ scanner.scan(symbol);
+ }
+ scanner.scan(IAC);
+ scanner.scan(SE);
+
+ Assert.assertEquals("Incorrect BACKSPACE: ", mappings.getBackspace(), scanner.getBackspace());
+ Assert.assertEquals("Incorrect DELL: ", mappings.getDel(), scanner.getDel());
+
+ Map<String, KEYS> currentEscapesToKey = scanner.getCurrentEscapesToKey();
+ Map<String, KEYS> expectedEscapesToKey = mappings.getEscapesToKey();
+ for (String escape : expectedEscapesToKey.keySet()) {
+ KEYS key = expectedEscapesToKey.get(escape);
+ Assert.assertEquals("Incorrect " + key.name(), key, currentEscapesToKey.get(escape));
+ }
+
+ Assert.assertTrue("Callback not called ", testCallback.getState());
+ }
+
+ class TestCallback implements Callback {
+
+ private boolean isCalled = false;
+
+ @Override
+ public void finished() {
+ isCalled = true;
+ }
+
+ public boolean getState() {
+ return isCalled;
+ }
+ }
+
+}
diff --git a/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/telnet/TelnetManagerTests.java b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/telnet/TelnetManagerTests.java
new file mode 100644
index 0000000..cf46a24
--- /dev/null
+++ b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/telnet/TelnetManagerTests.java
@@ -0,0 +1,136 @@
+/*******************************************************************************
+ * Copyright (c) 2010 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial contribution
+ ******************************************************************************/
+
+package org.eclipse.virgo.osgi.console.telnet;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.osgi.framework.BundleContext;
+
+import java.io.OutputStream;
+import java.net.ConnectException;
+import java.net.Socket;
+
+import static org.easymock.EasyMock.*;
+
+public class TelnetManagerTests {
+
+ private static final String PROP_CONSOLE = "osgi.console";
+
+ private static final String PROP_CONSOLE_VALUE_POSITIVE = "localhost:38888";
+
+ private static final String PROP_CONSOLE_VALUE_NEGATIVE = "localhost38889";
+
+ private static final int PORT_POSITIVE_TEST = 38888;
+
+ private static final int PORT_NEGATIVE_TEST = 38889;
+
+ private static final long WAIT_TIME = 5000;
+
+ private static final int TEST_CONTENT = 100;
+
+ @Test
+ public void testTelnetManager() throws Exception {
+
+ BundleContext bundleContext = createMock(BundleContext.class);
+
+ expect(bundleContext.getProperty(PROP_CONSOLE)).andReturn(PROP_CONSOLE_VALUE_POSITIVE);
+
+ replay(bundleContext);
+
+ TelnetManager telnetMngr = new TelnetManager(bundleContext);
+ telnetMngr.startConsoleListener();
+ Socket socketClient = null;
+
+ try {
+ socketClient = new Socket("localhost", PORT_POSITIVE_TEST);
+ OutputStream outClient = socketClient.getOutputStream();
+ outClient.write(TEST_CONTENT);
+ outClient.write('\n');
+ outClient.flush();
+
+ // wait for the accept thread to finish execution
+ try {
+ Thread.sleep(WAIT_TIME);
+ } catch (InterruptedException ie) {
+ // do nothing
+ }
+ } finally {
+ if (socketClient != null) {
+ socketClient.close();
+ }
+ telnetMngr.stop();
+ }
+
+ verify(bundleContext);
+ }
+
+ @Test
+ public void testTelnetManagerNegative() throws Exception {
+ BundleContext bundleContext = createMock(BundleContext.class);
+
+ expect(bundleContext.getProperty(PROP_CONSOLE)).andReturn(PROP_CONSOLE_VALUE_NEGATIVE);
+
+ replay(bundleContext);
+
+ TelnetManager telnetMngr = new TelnetManager(bundleContext);
+ telnetMngr.startConsoleListener();
+
+ boolean serverSocketNotCreated = false;
+ try {
+ Socket socketClient = new Socket("localhost", PORT_NEGATIVE_TEST);
+ socketClient.close();
+ } catch (ConnectException ec) {
+ serverSocketNotCreated = true;
+ }
+
+ telnetMngr.stop();
+ verify(bundleContext);
+
+ Assert.assertTrue("Telnet console socket getter is created on localhost", serverSocketNotCreated);
+ }
+
+ @Test
+ public void testTelnetManagerWithoutHost() throws Exception {
+ BundleContext bundleContext = createMock(BundleContext.class);
+
+ expect(bundleContext.getProperty(PROP_CONSOLE)).andReturn(String.valueOf(PORT_POSITIVE_TEST));
+
+ replay(bundleContext);
+
+ TelnetManager telnetMngr = new TelnetManager(bundleContext);
+ telnetMngr.startConsoleListener();
+ Socket socketClient = null;
+
+ try {
+ socketClient = new Socket("localhost", PORT_POSITIVE_TEST);
+ OutputStream outClient = socketClient.getOutputStream();
+ outClient.write(TEST_CONTENT);
+ outClient.write('\n');
+ outClient.flush();
+
+ // wait for the accept thread to finish execution
+ try {
+ Thread.sleep(WAIT_TIME);
+ } catch (InterruptedException ie) {
+ // do nothing
+ }
+ } finally {
+ if (socketClient != null) {
+ socketClient.close();
+ }
+ telnetMngr.stop();
+ }
+
+ verify(bundleContext);
+ }
+
+}
diff --git a/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/telnet/TelnetOutputStreamTests.java b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/telnet/TelnetOutputStreamTests.java
new file mode 100644
index 0000000..7753e4a
--- /dev/null
+++ b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/telnet/TelnetOutputStreamTests.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial contribution
+ ******************************************************************************/
+
+package org.eclipse.virgo.osgi.console.telnet;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+
+public class TelnetOutputStreamTests {
+
+ @Test
+ public void testAutoSend() throws Exception {
+ ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
+ TelnetOutputStream out = new TelnetOutputStream(byteOut);
+ out.autoSend();
+ out.flush();
+ byte[] message = byteOut.toByteArray();
+
+ Assert.assertNotNull("Auto message not sent", message);
+ Assert.assertFalse("Auto message not sent", message.length == 0);
+ Assert.assertTrue("Error sending auto message. Expected length: " + TelnetOutputStream.autoMessage.length + ", actual length: "
+ + message.length, message.length == TelnetOutputStream.autoMessage.length);
+
+ for (int i = 0; i < message.length; i++) {
+ Assert.assertEquals("Wrong char in auto message. Position: " + i + ", expected: " + TelnetOutputStream.autoMessage[i] + ", read: "
+ + message[i], TelnetOutputStream.autoMessage[i], message[i]);
+ }
+ }
+}
diff --git a/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/telnet/hook/TelnetHookTests.java b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/telnet/hook/TelnetHookTests.java
new file mode 100644
index 0000000..5d53d2f
--- /dev/null
+++ b/org.eclipse.virgo.osgi.console/src/test/java/org/eclipse/virgo/osgi/console/telnet/hook/TelnetHookTests.java
@@ -0,0 +1,84 @@
+/*******************************************************************************
+ * Copyright (c) 2011 SAP AG
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Lazar Kirchev, SAP AG - initial contribution
+ ******************************************************************************/
+
+package org.eclipse.virgo.osgi.console.telnet.hook;
+
+import java.io.OutputStream;
+import java.net.ConnectException;
+import java.net.Socket;
+
+import org.eclipse.core.runtime.adaptor.EclipseStarter;
+import org.eclipse.osgi.baseadaptor.BaseAdaptor;
+import org.junit.Assert;
+import org.junit.Test;
+import org.osgi.framework.BundleContext;
+
+import static org.easymock.EasyMock.*;
+
+public class TelnetHookTests {
+
+ @Test
+ public void testHostAndPort() throws Exception {
+ System.setProperty(EclipseStarter.PROP_CONSOLE, "localhost:3333");
+ System.setProperty("osgi.configuration.area", ".");
+ BaseAdaptor adaptor = new BaseAdaptor(null);
+ TelnetHook telnetHook = new TelnetHook();
+ telnetHook.initialize(adaptor);
+ String consoleProp = System.getProperty(EclipseStarter.PROP_CONSOLE);
+ Assert.assertNull("Console port should not be present in the properties", consoleProp);
+
+ BundleContext bundleContext = createMock(BundleContext.class);
+
+ expect(bundleContext.getProperty(EclipseStarter.PROP_CONSOLE)).andReturn("localhost:3333");
+ replay(bundleContext);
+
+ telnetHook.frameworkStart(bundleContext);
+
+ consoleProp = System.getProperty(EclipseStarter.PROP_CONSOLE);
+ Assert.assertEquals("Console port should not be present in the properties", "localhost:3333", consoleProp);
+
+ telnetHook.frameworkStop(bundleContext);
+
+ telnetHook.frameworkStopping(bundleContext);
+ telnetHook.addProperties(null);
+ telnetHook.mapLocationToURLConnection(null);
+ telnetHook.handleRuntimeError(null);
+ Assert.assertNull(telnetHook.createFrameworkLog());
+
+ Socket socketClient = null;
+ try {
+ socketClient = new Socket("localhost", 3333);
+ OutputStream outClient = socketClient.getOutputStream();
+ outClient.write(100);
+ outClient.write('\n');
+ outClient.flush();
+ Assert.fail("Server socket should be closed, no connection should be established");
+ } catch (ConnectException ex) {
+ // this is expected
+ } finally {
+ if (socketClient != null) {
+ socketClient.close();
+ }
+ }
+
+ verify(bundleContext);
+ }
+
+ @Test
+ public void testPort() {
+ BaseAdaptor adaptor = new BaseAdaptor(null);
+ TelnetHook telnetHook = new TelnetHook();
+ System.setProperty(EclipseStarter.PROP_CONSOLE, "3333");
+ telnetHook.initialize(adaptor);
+ String consoleProp = System.getProperty(EclipseStarter.PROP_CONSOLE);
+ Assert.assertNull("Console port should not be present in the properties", consoleProp);
+ }
+}
diff --git a/org.eclipse.virgo.osgi.console/template.mf b/org.eclipse.virgo.osgi.console/template.mf
new file mode 100644
index 0000000..adf77b4
--- /dev/null
+++ b/org.eclipse.virgo.osgi.console/template.mf
@@ -0,0 +1,19 @@
+Bundle-ManifestVersion: 2
+Bundle-Name: Console
+Bundle-Vendor: SpringSource Inc.
+Bundle-SymbolicName: org.eclipse.virgo.osgi.console
+Bundle-Version: 0
+Fragment-Host: org.eclipse.osgi; extension:=framework
+Excluded-Exports:
+ *.internal.*,
+ org.eclipse.core.*,
+ org.eclipse.osgi.*,
+ org.osgi.framework.*,
+ org.osgi.service.*
+Excluded-Imports:
+ org.eclipse.core.*;version="0",
+ org.eclipse.osgi.*;version="0",
+ org.osgi.framework.*;version="0",
+ org.osgi.service.*;version="0",
+ org.osgi.util.*;version="0"
+