Add a JLine based shell

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1735995 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/gogo/jline/DEPENDENCIES b/gogo/jline/DEPENDENCIES
new file mode 100644
index 0000000..664b8ac
--- /dev/null
+++ b/gogo/jline/DEPENDENCIES
@@ -0,0 +1,20 @@
+Apache Felix Gogo Shell
+Copyright 2011 The Apache Software Foundation
+
+This software was developed at the Apache Software Foundation
+(http://www.apache.org) and may have dependencies on other
+Apache software licensed under Apache License 2.0.
+
+I. Included Third-Party Software
+
+None.
+
+II. Used Third-Party Software
+
+This product uses software developed at
+The OSGi Alliance (http://www.osgi.org/).
+Copyright (c) OSGi Alliance (2000, 2009).
+Licensed under the Apache License 2.0.
+
+III. License Summary
+- Apache License 2.0
diff --git a/gogo/jline/LICENSE b/gogo/jline/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/gogo/jline/LICENSE
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/gogo/jline/NOTICE b/gogo/jline/NOTICE
new file mode 100644
index 0000000..4007bd3
--- /dev/null
+++ b/gogo/jline/NOTICE
@@ -0,0 +1,6 @@
+Apache Felix Gogo Shell
+Copyright 2011 The Apache Software Foundation
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
+Licensed under the Apache License 2.0.
diff --git a/gogo/jline/doc/changelog.txt b/gogo/jline/doc/changelog.txt
new file mode 100644
index 0000000..0f7c8dc
--- /dev/null
+++ b/gogo/jline/doc/changelog.txt
@@ -0,0 +1,67 @@
+Changes from 0.8.0 to 0.10.0
+----------------------------
+
+** Improvement
+    * Added gosh_profile work around for issue with OSGi R4.3 API
+      ambiguity.
+
+Changes from 0.6.1 to 0.8.0
+---------------------------
+
+** Bug
+    * [FELIX-2651] - [Gogo] MOTD formatting is broken under Windows
+
+** Improvement
+    * [FELIX-2661] - [Gogo] It should be easier to start Gogo shell
+      non-interactively
+
+** New Feature
+    * [FELIX-2767] - gogo telnet IP address
+
+Changes from 0.6.0 to 0.6.1
+---------------------------
+
+** Bug
+    * [FELIX-2446] - [Gogo] The bundle context command is not used with a
+      scope in gosh_profile
+    * [FELIX-2477] - [gogo] shell procedural commands don't inherit closure
+      arguments
+
+** Improvement
+    * [FELIX-2445] - [Gogo] Default gosh_profile should be updated to use
+      system bundle to load java.lang.System
+    * [FELIX-2543] - [Gogo] Should avoid using System.getProperty() to get
+      configuration properties
+
+Gogo Shell 0.6.0
+----------------
+
+** Bug
+    * [FELIX-1473] - [gogo] The syntax does not provide a way to call methods
+      on a string
+    * [FELIX-1474] - [gogo] result of commands is implicitly written to pipe
+    * [FELIX-1493] - [gogo] automatic expansion of $args in Closure stops
+      direct access to $args list
+    * [FELIX-2337] - [gogo] no way to access array[] elements produced by
+      assignment
+    * [FELIX-2375] - [gogo] when supplied args can't be coerced, the error
+      message prints the arg values, rather than their types
+    * [FELIX-2380] - [gogo] lock contention in piped writer when reader
+      doesn't read all input
+
+** Improvement
+    * [FELIX-1487] - Support for commands on multiple lines
+    * [FELIX-2328] - [gogo] tidy-up runtime to remove optional code etc
+    * [FELIX-2339] - [gogo] add support for running scripts
+    * [FELIX-2342] - [gogo] remove old felix command adaptor
+
+** New Feature
+    * [FELIX-2363] - [Gogo] Add annotations for creating commands with
+      optional and out-of-order arguments
+
+** Task
+    * [FELIX-1670] - [gogo] launcher bundle not required
+    * [FELIX-1889] - Gogo should depend on the official OSGi jars
+    * [FELIX-2334] - [Gogo] Use org.apache.felix as Maven groupId
+    * [FELIX-2367] - [Gogo] Use org.apache.felix namespace to avoid any
+      perceived legal issues
diff --git a/gogo/jline/pom.xml b/gogo/jline/pom.xml
new file mode 100644
index 0000000..ecb67b1
--- /dev/null
+++ b/gogo/jline/pom.xml
@@ -0,0 +1,111 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied.  See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>gogo-parent</artifactId>
+        <version>0.6.0</version>
+        <relativePath>../gogo-parent/pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <packaging>bundle</packaging>
+    <name>Apache Felix Gogo JLine Shell</name>
+    <artifactId>org.apache.felix.gogo.jline</artifactId>
+    <version>0.11.0-SNAPSHOT</version>
+    <properties>
+        <!-- Skip because of NPE -->
+        <animal.sniffer.skip>true</animal.sniffer.skip>
+    </properties>
+    <dependencies>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+            <version>4.2.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+            <version>4.0.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.gogo.runtime</artifactId>
+            <version>0.16.3-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>jline</groupId>
+            <artifactId>jline</artifactId>
+            <version>3.0.0-SNAPSHOT</version>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sshd</groupId>
+            <artifactId>sshd-core</artifactId>
+            <version>1.0.0</version>
+            <optional>true</optional>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Export-Package>
+                        </Export-Package>
+                        <Import-Package>
+                            org.apache.felix.service.command; status="provisional",
+                            *
+                        </Import-Package>
+                        <Private-Package>
+			                org.apache.felix.gogo.shell,
+			                org.apache.felix.gogo.options
+			            </Private-Package>
+                        <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
+                        <Bundle-Vendor>The Apache Software Foundation</Bundle-Vendor>
+                        <Bundle-Activator>org.apache.felix.gogo.jline.Activator</Bundle-Activator>
+                        <Include-Resource>{maven-resources},META-INF/LICENSE=LICENSE,META-INF/NOTICE=NOTICE,META-INF/DEPENDENCIES=DEPENDENCIES</Include-Resource>
+                        <_versionpolicy>[$(version;==;$(@)),$(version;+;$(@)))</_versionpolicy>
+                        <_removeheaders>Private-Package,Ignore-Package,Include-Resource</_removeheaders>
+                    </instructions>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.rat</groupId>
+                <artifactId>apache-rat-plugin</artifactId>
+                <configuration>
+                    <excludes>
+                        <param>src/main/resources/motd</param>
+                    </excludes>
+                </configuration>
+            </plugin>
+            <plugin>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>1.8</source>
+                    <target>1.8</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Activator.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Activator.java
new file mode 100644
index 0000000..ca19dd2
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Activator.java
@@ -0,0 +1,182 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.jline;
+
+import java.util.Dictionary;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.felix.gogo.jline.Shell.Context;
+import org.apache.felix.gogo.jline.telnet.Telnet;
+import org.apache.felix.service.command.CommandProcessor;
+import org.apache.felix.service.command.CommandSession;
+import org.apache.felix.service.command.Converter;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.util.tracker.ServiceTracker;
+
+public class Activator implements BundleActivator {
+    private BundleContext context;
+    private ServiceTracker commandProcessorTracker;
+    private Set<ServiceRegistration> regs;
+
+    private ExecutorService executor;
+
+    public Activator() {
+        regs = new HashSet<ServiceRegistration>();
+    }
+
+    public void start(BundleContext context) throws Exception {
+        this.context = context;
+        this.commandProcessorTracker = createCommandProcessorTracker();
+        this.commandProcessorTracker.open();
+    }
+
+    public void stop(BundleContext context) throws Exception {
+        Iterator<ServiceRegistration> iterator = regs.iterator();
+        while (iterator.hasNext()) {
+            ServiceRegistration reg = iterator.next();
+            reg.unregister();
+            iterator.remove();
+        }
+
+        stopShell();
+
+        this.commandProcessorTracker.close();
+    }
+
+    private ServiceTracker createCommandProcessorTracker() {
+        return new ServiceTracker(context, CommandProcessor.class.getName(), null) {
+            @Override
+            public Object addingService(ServiceReference reference) {
+                CommandProcessor processor = (CommandProcessor) super.addingService(reference);
+                startShell(context, processor);
+                return processor;
+            }
+
+            @Override
+            public void removedService(ServiceReference reference, Object service) {
+                stopShell();
+                super.removedService(reference, service);
+            }
+        };
+    }
+
+    private void startShell(final BundleContext context, CommandProcessor processor) {
+        Dictionary<String, Object> dict = new Hashtable<String, Object>();
+        dict.put(CommandProcessor.COMMAND_SCOPE, "gogo");
+
+        // register converters
+        regs.add(context.registerService(Converter.class.getName(), new Converters(context.getBundle(0).getBundleContext()), null));
+
+        // register commands
+
+        dict.put(CommandProcessor.COMMAND_FUNCTION, Builtin.functions);
+        regs.add(context.registerService(Builtin.class.getName(), new Builtin(), dict));
+
+        dict.put(CommandProcessor.COMMAND_FUNCTION, Procedural.functions);
+        regs.add(context.registerService(Procedural.class.getName(), new Procedural(), dict));
+
+        dict.put(CommandProcessor.COMMAND_FUNCTION, Posix.functions);
+        regs.add(context.registerService(Posix.class.getName(), new Posix(), dict));
+
+        dict.put(CommandProcessor.COMMAND_FUNCTION, Telnet.functions);
+        regs.add(context.registerService(Telnet.class.getName(), new Telnet(processor), dict));
+
+        Shell shell = new Shell(new ShellContext(), processor, null);
+        dict.put(CommandProcessor.COMMAND_FUNCTION, Shell.functions);
+        regs.add(context.registerService(Shell.class.getName(), shell, dict));
+
+        // start shell on a separate thread...
+        executor = Executors.newSingleThreadExecutor(new ThreadFactory() {
+            public Thread newThread(Runnable runnable) {
+                return new Thread(runnable, "Gogo shell");
+            }
+        });
+        executor.submit(new StartShellJob(context, processor));
+    }
+
+    private void stopShell() {
+        if (executor != null && !(executor.isShutdown() || executor.isTerminated())) {
+            executor.shutdownNow();
+
+            try {
+                if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
+                    System.err.println("!!! FAILED TO STOP EXECUTOR !!!");
+                }
+            } catch (InterruptedException e) {
+                // Restore administration...
+                Thread.currentThread().interrupt();
+            }
+            executor = null;
+        }
+    }
+
+    private static class StartShellJob implements Runnable {
+        private final BundleContext context;
+        private final CommandProcessor processor;
+
+        public StartShellJob(BundleContext context, CommandProcessor processor) {
+            this.context = context;
+            this.processor = processor;
+        }
+
+        public void run() {
+            CommandSession session = processor.createSession(System.in, System.out, System.err);
+            try {
+                // wait for gosh command to be registered
+                for (int i = 0; (i < 100) && session.get("gogo:gosh") == null; ++i) {
+                    TimeUnit.MILLISECONDS.sleep(10);
+                }
+
+                String args = context.getProperty("gosh.args");
+                args = (args == null) ? "" : args;
+                session.execute("gosh --login " + args);
+            } catch (Exception e) {
+                Object loc = session.get(".location");
+                if (null == loc || !loc.toString().contains(":")) {
+                    loc = "gogo";
+                }
+
+                System.err.println(loc + ": " + e.getClass().getSimpleName() + ": " + e.getMessage());
+                e.printStackTrace();
+            } finally {
+                session.close();
+            }
+        }
+    }
+
+    private class ShellContext implements Context {
+        public String getProperty(String name) {
+            return context.getProperty(name);
+        }
+
+        public void exit() throws Exception {
+            context.getBundle(0).stop();
+        }
+    }
+}
\ No newline at end of file
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Builtin.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Builtin.java
new file mode 100644
index 0000000..a4fc3ef
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Builtin.java
@@ -0,0 +1,498 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.jline;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.StringWriter;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import org.apache.felix.service.command.CommandSession;
+import org.apache.felix.service.command.Converter;
+import org.jline.builtins.Options;
+
+/**
+ * gosh built-in commands.
+ */
+public class Builtin {
+
+    static final String[] functions = {"format", "getopt", "new", "set", "tac", "type"};
+
+    private static final String[] packages = {"java.lang", "java.io", "java.net",
+            "java.util"};
+    private final static Set<String> KEYWORDS = new HashSet<String>(
+            Arrays.asList(new String[]{"abstract", "continue", "for", "new", "switch",
+                    "assert", "default", "goto", "package", "synchronized", "boolean", "do",
+                    "if", "private", "this", "break", "double", "implements", "protected",
+                    "throw", "byte", "else", "import", "public", "throws", "case", "enum",
+                    "instanceof", "return", "transient", "catch", "extends", "int", "short",
+                    "try", "char", "final", "interface", "static", "void", "class",
+                    "finally", "long", "strictfp", "volatile", "const", "float", "native",
+                    "super", "while"}));
+
+    @SuppressWarnings("unchecked")
+    static Set<String> getCommands(CommandSession session) {
+        return (Set<String>) session.get(".commands");
+    }
+
+    public CharSequence format(CommandSession session) {
+        return format(session, session.get("_"));    // last result
+    }
+
+    public CharSequence format(CommandSession session, Object arg) {
+        CharSequence result = session.format(arg, Converter.INSPECT);
+        System.out.println(result);
+        return result;
+    }
+
+    /**
+     * script access to Options.
+     */
+    public Options getopt(List<Object> spec, Object[] args) {
+        String[] optSpec = new String[spec.size()];
+        for (int i = 0; i < optSpec.length; ++i) {
+            optSpec[i] = spec.get(i).toString();
+        }
+        return Options.compile(optSpec).parse(args);
+    }
+
+    // FIXME: the "new" command should be provided by runtime,
+    // so it can leverage same argument coercion mechanism, used to invoke methods.
+    public Object _new(CommandSession session, Object name, Object[] argv) throws Exception {
+        Class<?> clazz;
+
+        if (name instanceof Class<?>) {
+            clazz = (Class<?>) name;
+        } else {
+            clazz = loadClass(name.toString());
+        }
+
+        for (Constructor<?> c : clazz.getConstructors()) {
+            Class<?>[] types = c.getParameterTypes();
+            if (types.length != argv.length) {
+                continue;
+            }
+
+            boolean match = true;
+
+            Object[] transformed = argv.clone();
+            for (int i = 0; i < transformed.length; ++i) {
+                try {
+                    transformed[i] = session.convert(types[i], transformed[i]);
+                } catch (IllegalArgumentException e) {
+                    match = false;
+                    break;
+                }
+            }
+
+            if (!match) {
+                continue;
+            }
+
+            try {
+                return c.newInstance(transformed);
+            } catch (InvocationTargetException ite) {
+                Throwable cause = ite.getCause();
+                if (cause instanceof Exception) {
+                    throw (Exception) cause;
+                }
+                throw ite;
+            }
+        }
+
+        throw new IllegalArgumentException("can't coerce " + Arrays.asList(argv)
+                + " to any of " + Arrays.asList(clazz.getConstructors()));
+    }
+
+    private Class<?> loadClass(String name) throws ClassNotFoundException {
+        if (!name.contains(".")) {
+            for (String p : packages) {
+                String pkg = p + "." + name;
+                try {
+                    return Class.forName(pkg);
+                } catch (ClassNotFoundException e) {
+                }
+            }
+        }
+        return Class.forName(name);
+    }
+
+    public void set(CommandSession session, String[] argv) throws Exception {
+        final String[] usage = {
+                "set - show session variables",
+                "Usage: set [OPTIONS] [PREFIX]",
+                "  -? --help                show help",
+                "  -a --all                 show all variables, including those starting with .",
+                "  -x                       set xtrace option",
+                "  +x                       unset xtrace option",
+                "If PREFIX given, then only show variable(s) starting with PREFIX"};
+
+        Options opt = Options.compile(usage).parse(argv);
+
+        if (opt.isSet("help")) {
+            opt.usage(System.err);
+            return;
+        }
+
+        List<String> args = opt.args();
+        String prefix = (args.isEmpty() ? "" : args.get(0));
+
+        if (opt.isSet("x")) {
+            session.put("echo", true);
+        } else if ("+x".equals(prefix)) {
+            session.put("echo", null);
+        } else {
+            boolean all = opt.isSet("all");
+            for (String key : new TreeSet<String>(Shell.getVariables(session))) {
+                if (!key.startsWith(prefix))
+                    continue;
+
+                if (key.startsWith(".") && !(all || prefix.length() > 0))
+                    continue;
+
+                Object target = session.get(key);
+                String type = null;
+                String value = null;
+
+                if (target != null) {
+                    Class<? extends Object> clazz = target.getClass();
+                    type = clazz.getSimpleName();
+                    value = target.toString();
+                }
+
+                String trunc = value == null || value.length() < 55 ? "" : "...";
+                System.out.println(String.format("%-15.15s %-15s %.45s%s", type, key,
+                        value, trunc));
+            }
+        }
+    }
+
+    /*
+     * the following methods depend on the internals of the runtime implementation.
+     * ideally, they should be available via some API.
+     */
+
+    public Object tac(CommandSession session, String[] argv) throws IOException {
+        final String[] usage = {
+                "tac - capture stdin as String or List and optionally write to file.",
+                "Usage: tac [-al] [FILE]",
+                "  -a --append              append to FILE",
+                "  -l --list                return List<String>",
+                "  -? --help                show help"};
+
+        Options opt = Options.compile(usage).parse(argv);
+
+        if (opt.isSet("help")) {
+            opt.usage(System.err);
+            return null;
+        }
+
+        List<String> args = opt.args();
+        BufferedWriter fw = null;
+
+        if (args.size() == 1) {
+            String path = args.get(0);
+            File file = new File(Posix._pwd(session), path);
+            fw = new BufferedWriter(new FileWriter(file, opt.isSet("append")));
+        }
+
+        StringWriter sw = new StringWriter();
+        BufferedReader rdr = new BufferedReader(new InputStreamReader(System.in));
+
+        ArrayList<String> list = null;
+
+        if (opt.isSet("list")) {
+            list = new ArrayList<String>();
+        }
+
+        boolean first = true;
+        String s;
+
+        while ((s = rdr.readLine()) != null) {
+            if (list != null) {
+                list.add(s);
+            } else {
+                if (!first) {
+                    sw.write(' ');
+                }
+                first = false;
+                sw.write(s);
+            }
+
+            if (fw != null) {
+                fw.write(s);
+                fw.newLine();
+            }
+        }
+
+        if (fw != null) {
+            fw.close();
+        }
+
+        return list != null ? list : sw.toString();
+    }
+
+    // FIXME: expose API in runtime so type command doesn't have to duplicate the runtime
+    // command search strategy.
+    public boolean type(CommandSession session, String[] argv) throws Exception {
+        final String[] usage = {"type - show command type",
+                "Usage: type [OPTIONS] [name[:]]",
+                "  -a --all                 show all matches",
+                "  -? --help                show help",
+                "  -q --quiet               don't print anything, just return status",
+                "  -s --scope=NAME          list all commands in named scope",
+                "  -t --types               show full java type names"};
+
+        Options opt = Options.compile(usage).parse(argv);
+        List<String> args = opt.args();
+
+        if (opt.isSet("help")) {
+            opt.usage(System.err);
+            return true;
+        }
+
+        boolean all = opt.isSet("all");
+
+        String optScope = null;
+        if (opt.isSet("scope")) {
+            optScope = opt.get("scope");
+        }
+
+        if (args.size() == 1) {
+            String arg = args.get(0);
+            if (arg.endsWith(":")) {
+                optScope = args.remove(0);
+            }
+        }
+
+        if (optScope != null || (args.isEmpty() && all)) {
+            Set<String> snames = new TreeSet<String>();
+
+            for (String sname : (getCommands(session))) {
+                if ((optScope == null) || sname.startsWith(optScope)) {
+                    snames.add(sname);
+                }
+            }
+
+            for (String sname : snames) {
+                System.out.println(sname);
+            }
+
+            return true;
+        }
+
+        if (args.size() == 0) {
+            Map<String, Integer> scopes = new TreeMap<String, Integer>();
+
+            for (String sname : getCommands(session)) {
+                int colon = sname.indexOf(':');
+                String scope = sname.substring(0, colon);
+                Integer count = scopes.get(scope);
+                if (count == null) {
+                    count = 0;
+                }
+                scopes.put(scope, ++count);
+            }
+
+            for (Entry<String, Integer> entry : scopes.entrySet()) {
+                System.out.println(entry.getKey() + ":" + entry.getValue());
+            }
+
+            return true;
+        }
+
+        final String name = args.get(0).toLowerCase();
+
+        final int colon = name.indexOf(':');
+        final String MAIN = "_main"; // FIXME: must match Reflective.java
+
+        StringBuilder buf = new StringBuilder();
+        Set<String> cmds = new LinkedHashSet<String>();
+
+        // get all commands
+        if ((colon != -1) || (session.get(name) != null)) {
+            cmds.add(name);
+        } else if (session.get(MAIN) != null) {
+            cmds.add(MAIN);
+        } else {
+            String path = session.get("SCOPE") != null ? session.get("SCOPE").toString()
+                    : "*";
+
+            for (String s : path.split(":")) {
+                if (s.equals("*")) {
+                    for (String sname : getCommands(session)) {
+                        if (sname.endsWith(":" + name)) {
+                            cmds.add(sname);
+                            if (!all) {
+                                break;
+                            }
+                        }
+                    }
+                } else {
+                    String sname = s + ":" + name;
+                    if (session.get(sname) != null) {
+                        cmds.add(sname);
+                        if (!all) {
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+
+        for (String key : cmds) {
+            Object target = session.get(key);
+            if (target == null) {
+                continue;
+            }
+
+            CharSequence source = getClosureSource(session, key);
+
+            if (source != null) {
+                buf.append(name);
+                buf.append(" is function {");
+                buf.append(source);
+                buf.append("}");
+                continue;
+            }
+
+            for (Method m : getMethods(session, key)) {
+                StringBuilder params = new StringBuilder();
+
+                for (Class<?> type : m.getParameterTypes()) {
+                    if (params.length() > 0) {
+                        params.append(", ");
+                    }
+                    params.append(type.getSimpleName());
+                }
+
+                String rtype = m.getReturnType().getSimpleName();
+
+                if (buf.length() > 0) {
+                    buf.append("\n");
+                }
+
+                if (opt.isSet("types")) {
+                    String cname = m.getDeclaringClass().getName();
+                    buf.append(String.format("%s %s.%s(%s)", rtype, cname, m.getName(),
+                            params));
+                } else {
+                    buf.append(String.format("%s is %s %s(%s)", name, rtype, key, params));
+                }
+            }
+        }
+
+        if (buf.length() > 0) {
+            if (!opt.isSet("quiet")) {
+                System.out.println(buf);
+            }
+            return true;
+        }
+
+        if (!opt.isSet("quiet")) {
+            System.err.println("type: " + name + " not found.");
+        }
+
+        return false;
+    }
+
+    private boolean isClosure(Object target) {
+        return target.getClass().getSimpleName().equals("Closure");
+    }
+
+    private boolean isCommand(Object target) {
+        return target.getClass().getSimpleName().equals("CommandProxy");
+    }
+
+    private CharSequence getClosureSource(CommandSession session, String name)
+            throws Exception {
+        Object target = session.get(name);
+
+        if (target == null) {
+            return null;
+        }
+
+        if (!isClosure(target)) {
+            return null;
+        }
+
+        Field sourceField = target.getClass().getDeclaredField("source");
+        sourceField.setAccessible(true);
+        return (CharSequence) sourceField.get(target);
+    }
+
+    private List<Method> getMethods(CommandSession session, String scmd) throws Exception {
+        final int colon = scmd.indexOf(':');
+        final String function = colon == -1 ? scmd : scmd.substring(colon + 1);
+        final String name = KEYWORDS.contains(function) ? ("_" + function) : function;
+        final String get = "get" + function;
+        final String is = "is" + function;
+        final String set = "set" + function;
+        final String MAIN = "_main"; // FIXME: must match Reflective.java
+
+        Object target = session.get(scmd);
+        if (target == null) {
+            return null;
+        }
+
+        if (isClosure(target)) {
+            return null;
+        }
+
+        if (isCommand(target)) {
+            Method method = target.getClass().getMethod("getTarget", (Class[]) null);
+            method.setAccessible(true);
+            target = method.invoke(target, (Object[]) null);
+        }
+
+        ArrayList<Method> list = new ArrayList<Method>();
+        Class<?> tc = (target instanceof Class<?>) ? (Class<?>) target
+                : target.getClass();
+        Method[] methods = tc.getMethods();
+
+        for (Method m : methods) {
+            String mname = m.getName().toLowerCase();
+
+            if (mname.equals(name) || mname.equals(get) || mname.equals(set)
+                    || mname.equals(is) || mname.equals(MAIN)) {
+                list.add(m);
+            }
+        }
+
+        return list;
+    }
+
+}
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Converters.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Converters.java
new file mode 100644
index 0000000..7d67d45
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Converters.java
@@ -0,0 +1,243 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.jline;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Arrays;
+import java.util.Formatter;
+
+import org.apache.felix.service.command.Converter;
+import org.apache.felix.service.command.Function;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.startlevel.StartLevel;
+
+public class Converters implements Converter {
+    private final BundleContext context;
+
+    public Converters(BundleContext context) {
+        this.context = context;
+    }
+
+    private CharSequence print(Bundle bundle) {
+        // [ ID ] [STATE      ] [ SL ] symname
+        StartLevel sl = null;
+        ServiceReference ref = context.getServiceReference(StartLevel.class.getName());
+        if (ref != null) {
+            sl = (StartLevel) context.getService(ref);
+        }
+
+        if (sl == null) {
+            return String.format("%5d|%-11s|%s (%s)", bundle.getBundleId(),
+                    getState(bundle), bundle.getSymbolicName(), bundle.getVersion());
+        }
+
+        int level = sl.getBundleStartLevel(bundle);
+        context.ungetService(ref);
+
+        return String.format("%5d|%-11s|%5d|%s (%s)", bundle.getBundleId(),
+                getState(bundle), level, bundle.getSymbolicName(), bundle.getVersion());
+    }
+
+    private CharSequence print(ServiceReference ref) {
+        StringBuilder sb = new StringBuilder();
+        Formatter f = new Formatter(sb);
+
+        String spid = "";
+        Object pid = ref.getProperty("service.pid");
+        if (pid != null) {
+            spid = pid.toString();
+        }
+
+        f.format("%06d %3s %-40s %s", ref.getProperty("service.id"),
+                ref.getBundle().getBundleId(),
+                getShortNames((String[]) ref.getProperty("objectclass")), spid);
+        return sb;
+    }
+
+    private CharSequence getShortNames(String[] list) {
+        StringBuilder sb = new StringBuilder();
+        String del = "";
+        for (String s : list) {
+            sb.append(del + getShortName(s));
+            del = " | ";
+        }
+        return sb;
+    }
+
+    private CharSequence getShortName(String name) {
+        int n = name.lastIndexOf('.');
+        if (n < 0) {
+            n = 0;
+        } else {
+            n++;
+        }
+        return name.subSequence(n, name.length());
+    }
+
+    private String getState(Bundle bundle) {
+        switch (bundle.getState()) {
+            case Bundle.ACTIVE:
+                return "Active";
+
+            case Bundle.INSTALLED:
+                return "Installed";
+
+            case Bundle.RESOLVED:
+                return "Resolved";
+
+            case Bundle.STARTING:
+                return "Starting";
+
+            case Bundle.STOPPING:
+                return "Stopping";
+
+            case Bundle.UNINSTALLED:
+                return "Uninstalled ";
+        }
+        return null;
+    }
+
+    public Bundle bundle(Bundle i) {
+        return i;
+    }
+
+    public Object convert(Class<?> desiredType, final Object in) throws Exception {
+        if (desiredType == Bundle.class) {
+            return convertBundle(in);
+        }
+
+        if (desiredType == ServiceReference.class) {
+            return convertServiceReference(in);
+        }
+
+        if (desiredType == Class.class) {
+            try {
+                return Class.forName(in.toString());
+            } catch (ClassNotFoundException e) {
+                return null;
+            }
+        }
+
+        if (desiredType.isAssignableFrom(String.class) && in instanceof InputStream) {
+            return read(((InputStream) in));
+        }
+
+        if (in instanceof Function && desiredType.isInterface()
+                && desiredType.getDeclaredMethods().length == 1) {
+            return Proxy.newProxyInstance(desiredType.getClassLoader(),
+                    new Class[]{desiredType}, new InvocationHandler() {
+                        Function command = ((Function) in);
+
+                        public Object invoke(Object proxy, Method method, Object[] args)
+                                throws Throwable {
+                            return command.execute(null, Arrays.asList(args));
+                        }
+                    });
+        }
+
+        return null;
+    }
+
+    private Object convertServiceReference(Object in) throws InvalidSyntaxException {
+        String s = in.toString();
+        if (s.startsWith("(") && s.endsWith(")")) {
+            ServiceReference refs[] = context.getServiceReferences(null, String.format(
+                    "(|(service.id=%s)(service.pid=%s))", in, in));
+            if (refs != null && refs.length > 0) {
+                return refs[0];
+            }
+        }
+
+        ServiceReference refs[] = context.getServiceReferences(null, String.format(
+                "(|(service.id=%s)(service.pid=%s))", in, in));
+        if (refs != null && refs.length > 0) {
+            return refs[0];
+        }
+        return null;
+    }
+
+    private Object convertBundle(Object in) {
+        String s = in.toString();
+        try {
+            long id = Long.parseLong(s);
+            return context.getBundle(id);
+        } catch (NumberFormatException nfe) {
+            // Ignore
+        }
+
+        Bundle bundles[] = context.getBundles();
+        for (Bundle b : bundles) {
+            if (b.getLocation().equals(s)) {
+                return b;
+            }
+
+            if (b.getSymbolicName().equals(s)) {
+                return b;
+            }
+        }
+
+        return null;
+    }
+
+    public CharSequence format(Object target, int level, Converter converter)
+            throws IOException {
+        if (level == INSPECT && target instanceof InputStream) {
+            return read(((InputStream) target));
+        }
+        if (level == LINE && target instanceof Bundle) {
+            return print((Bundle) target);
+        }
+        if (level == LINE && target instanceof ServiceReference) {
+            return print((ServiceReference) target);
+        }
+        if (level == PART && target instanceof Bundle) {
+            return ((Bundle) target).getSymbolicName();
+        }
+        if (level == PART && target instanceof ServiceReference) {
+            return getShortNames((String[]) ((ServiceReference) target).getProperty("objectclass"));
+        }
+        return null;
+    }
+
+    private CharSequence read(InputStream in) throws IOException {
+        int c;
+        StringBuffer sb = new StringBuffer();
+        while ((c = in.read()) > 0) {
+            if (c >= 32 && c <= 0x7F || c == '\n' || c == '\r') {
+                sb.append((char) c);
+            } else {
+                String s = Integer.toHexString(c).toUpperCase();
+                sb.append("\\");
+                if (s.length() < 1) {
+                    sb.append(0);
+                }
+                sb.append(s);
+            }
+        }
+        return sb;
+    }
+
+}
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Highlighter.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Highlighter.java
new file mode 100644
index 0000000..1d91d46
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Highlighter.java
@@ -0,0 +1,274 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.jline;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.felix.gogo.runtime.CommandSessionImpl;
+import org.apache.felix.gogo.runtime.EOFError;
+import org.apache.felix.gogo.runtime.Parser.Program;
+import org.apache.felix.gogo.runtime.Parser.Statement;
+import org.apache.felix.gogo.runtime.SyntaxError;
+import org.apache.felix.gogo.runtime.Token;
+import org.apache.felix.service.command.CommandSession;
+import org.apache.felix.service.command.Function;
+import org.jline.reader.LineReader;
+import org.jline.reader.LineReader.RegionType;
+import org.jline.reader.impl.DefaultHighlighter;
+import org.jline.utils.AttributedString;
+import org.jline.utils.AttributedStringBuilder;
+import org.jline.utils.AttributedStyle;
+import org.jline.utils.WCWidth;
+
+public class Highlighter extends DefaultHighlighter {
+
+    private final CommandSession session;
+
+    public Highlighter(CommandSession session) {
+        this.session = session;
+    }
+
+    public AttributedString highlight(LineReader reader, String buffer) {
+        try {
+            Program program = null;
+            List<Token> tokens = null;
+            List<Statement> statements = null;
+            String repaired = buffer + " ";
+            while (program == null) {
+                try {
+                    org.apache.felix.gogo.runtime.Parser parser = new org.apache.felix.gogo.runtime.Parser(repaired);
+                    program = parser.program();
+                    tokens = parser.tokens();
+                    statements = parser.statements();
+                } catch (EOFError e) {
+                    repaired = repaired + e.repair();
+                }
+            }
+
+            int underlineStart = -1;
+            int underlineEnd = -1;
+            int negativeStart = -1;
+            int negativeEnd = -1;
+            String search = reader.getSearchTerm();
+            if (search != null && search.length() > 0) {
+                underlineStart = buffer.indexOf(search);
+                if (underlineStart >= 0) {
+                    underlineEnd = underlineStart + search.length() - 1;
+                }
+            }
+            if (reader.getRegionActive() != RegionType.NONE) {
+                negativeStart = reader.getRegionMark();
+                negativeEnd = reader.getBuffer().cursor();
+                if (negativeStart > negativeEnd) {
+                    int x = negativeEnd;
+                    negativeEnd = negativeStart;
+                    negativeStart = x;
+                }
+                if (reader.getRegionActive() == RegionType.LINE) {
+                    while (negativeStart > 0 && reader.getBuffer().atChar(negativeStart - 1) != '\n') {
+                        negativeStart--;
+                    }
+                    while (negativeEnd < reader.getBuffer().length() - 1 && reader.getBuffer().atChar(negativeEnd + 1) != '\n') {
+                        negativeEnd++;
+                    }
+                }
+            }
+
+            Type[] types = new Type[repaired.length()];
+
+            Arrays.fill(types, Type.Unknown);
+
+            int cur = 0;
+            for (Token token : tokens) {
+                // We're on the repair side, so exit now
+                if (token.start() >= buffer.length()) {
+                    break;
+                }
+                if (token.start() > cur) {
+//                    ansi.a(buffer.substring(cur, token.start()));
+                    cur = token.start();
+                }
+                // Find corresponding statement
+                Statement statement = null;
+                for (int i = statements.size() - 1; i >= 0; i--) {
+                    Statement s = statements.get(i);
+                    if (s.start() <= cur && cur < s.start() + s.length()) {
+                        statement = s;
+                        break;
+                    }
+                }
+
+                // Reserved tokens
+                Type type = Type.Unknown;
+                if (Token.eq(token, "{")
+                        || Token.eq(token, "}")
+                        || Token.eq(token, "(")
+                        || Token.eq(token, ")")
+                        || Token.eq(token, "[")
+                        || Token.eq(token, "]")
+                        || Token.eq(token, "|")
+                        || Token.eq(token, ";")
+                        || Token.eq(token, "=")) {
+                    type = Type.Reserved;
+                } else if (token.charAt(0) == '\'' || token.charAt(0) == '"') {
+                    type = Type.String;
+                } else if (token.toString().matches("^[-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?$")) {
+                    type = Type.Number;
+                } else if (token.charAt(0) == '$') {
+                    type = Type.Variable;
+                } else if (((Set) session.get(CommandSessionImpl.CONSTANTS)).contains(token.toString())
+                        || Token.eq(token, "null") || Token.eq(token, "false") || Token.eq(token, "true")) {
+                    type = Type.Constant;
+                } else {
+                    boolean isFirst = statement != null && statement.tokens().size() > 0
+                            && token == statement.tokens().get(0);
+                    boolean isThirdWithNext = statement != null && statement.tokens().size() > 3
+                            && token == statement.tokens().get(2);
+                    boolean isAssign = statement != null && statement.tokens().size() > 1
+                            && Token.eq(statement.tokens().get(1), "=");
+                    if (isFirst && isAssign) {
+                        type = Type.VariableName;
+                    }
+                    if (isFirst && !isAssign || isAssign && isThirdWithNext) {
+                        Object v = session.get(Shell.resolve(session, token.toString()));
+                        type = (v instanceof Function) ? Type.Function : Type.BadFunction;
+                    }
+                }
+                Arrays.fill(types, token.start(), Math.min(token.start() + token.length(), types.length), type);
+                /*
+                String valid;
+                if (token.start() + token.length() <= buffer.length()) {
+                    valid = token.toString();
+                } else {
+                    valid = token.subSequence(0, buffer.length() - token.start()).toString();
+                }
+                switch (type) {
+                    case Reserved:
+                        ansi.fg(Color.MAGENTA).a(valid).fg(Color.DEFAULT);
+                        break;
+                    case String:
+                    case Number:
+                    case Constant:
+                        ansi.fg(Color.GREEN).a(valid).fg(Color.DEFAULT);
+                        break;
+                    case Variable:
+                    case VariableName:
+                        ansi.fg(Color.CYAN).a(valid).fg(Color.DEFAULT);
+                        break;
+                    case Function:
+                        ansi.fg(Color.BLUE).a(valid).fg(Color.DEFAULT);
+                        break;
+                    case BadFunction:
+                        ansi.fg(Color.RED).a(valid).fg(Color.DEFAULT);
+                        break;
+                    default:
+                        ansi.a(valid);
+                        break;
+                }
+                */
+                cur = Math.min(token.start() + token.length(), buffer.length());
+            }
+
+            if (buffer.length() < repaired.length()) {
+                Arrays.fill(types, buffer.length(), repaired.length(), Type.Repair);
+            }
+
+            AttributedStringBuilder sb = new AttributedStringBuilder();
+            Type prevType = Type.Unknown;
+            for (int i = 0; i < repaired.length(); i++) {
+                if (i == underlineStart) {
+                    sb.style(sb.style().underline());
+                }
+                if (i == negativeStart) {
+                    sb.style(sb.style().inverse());
+                }
+                if (types[i] != prevType) {
+                    prevType = types[i];
+                    switch (prevType) {
+                        case Reserved:
+                            sb.style(sb.style().foreground(AttributedStyle.MAGENTA));
+                            break;
+                        case String:
+                        case Number:
+                        case Constant:
+                            sb.style(sb.style().foreground(AttributedStyle.GREEN));
+                            break;
+                        case Variable:
+                        case VariableName:
+                            sb.style(sb.style().foreground(AttributedStyle.CYAN));
+                            break;
+                        case Function:
+                            sb.style(sb.style().foreground(AttributedStyle.BLUE));
+                            break;
+                        case BadFunction:
+                            sb.style(sb.style().foreground(AttributedStyle.RED));
+                            break;
+                        case Repair:
+                            sb.style(sb.style().foreground(AttributedStyle.BLACK + AttributedStyle.BRIGHT));
+                            break;
+                        default:
+                            sb.style(sb.style().foregroundDefault());
+                            break;
+                    }
+                }
+                char c = repaired.charAt(i);
+                if (c == '\t' || c == '\n') {
+                    sb.append(c);
+                } else if (c < 32) {
+                    sb.style(sb.style().inverseNeg())
+                            .append('^')
+                            .append((char) (c + '@'))
+                            .style(sb.style().inverseNeg());
+                } else {
+                    int w = WCWidth.wcwidth(c);
+                    if (w > 0) {
+                        sb.append(c);
+                    }
+                }
+                if (i == underlineEnd) {
+                    sb.style(sb.style().underlineOff());
+                }
+                if (i == negativeEnd) {
+                    sb.style(sb.style().inverseOff());
+                }
+            }
+
+            return sb.toAttributedString();
+        } catch (SyntaxError e) {
+            return super.highlight(reader, buffer);
+        }
+    }
+
+    enum Type {
+        Reserved,
+        String,
+        Number,
+        Variable,
+        VariableName,
+        Function,
+        BadFunction,
+        Value,
+        Constant,
+        Unknown,
+        Repair
+    }
+
+}
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/JLineCommands.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/JLineCommands.java
new file mode 100644
index 0000000..498e12e
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/JLineCommands.java
@@ -0,0 +1,266 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.jline;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.felix.gogo.jline.Shell.Context;
+import org.apache.felix.gogo.runtime.CommandSessionImpl;
+import org.apache.felix.service.command.CommandProcessor;
+import org.apache.felix.service.command.CommandSession;
+import org.apache.felix.service.command.Function;
+import org.jline.builtins.Completers.DirectoriesCompleter;
+import org.jline.builtins.Completers.FilesCompleter;
+import org.jline.builtins.Options;
+import org.jline.reader.Candidate;
+import org.jline.reader.LineReader;
+import org.jline.reader.ParsedLine;
+import org.jline.reader.Widget;
+import org.jline.terminal.Attributes;
+import org.jline.terminal.Terminal;
+import org.jline.utils.InfoCmp.Capability;
+
+public class JLineCommands {
+
+    public static final String[] functions = {
+            "keymap", "setopt", "unsetopt", "complete", "history",
+            "less", "watch", "nano", "widget", "tmux",
+            "__files", "__directories", "__usage_completion"
+    };
+
+    private final CommandProcessor processor;
+
+    private final org.jline.builtins.Commands commands = new org.jline.builtins.Commands();
+
+    public JLineCommands(CommandProcessor processor) {
+        this.processor = processor;
+    }
+
+    public void tmux(final CommandSession session, String[] argv) throws Exception {
+        commands.tmux(Shell.getTerminal(session),
+                System.out, System.err,
+                () -> session.get(".tmux"),
+                t -> session.put(".tmux", t),
+                c -> startShell(session, c), argv);
+    }
+
+    private void startShell(CommandSession session, Terminal terminal) {
+        new Thread(() -> runShell(session, terminal), terminal.getName() + " shell").start();
+    }
+
+    private void runShell(CommandSession session, Terminal terminal) {
+        InputStream in = terminal.input();
+        PrintStream out = new PrintStream(terminal.output());
+        CommandSession newSession = processor.createSession(in, out, out);
+        newSession.put(Shell.VAR_TERMINAL, terminal);
+        newSession.put(".tmux", session.get(".tmux"));
+        Context context = new Context() {
+            public String getProperty(String name) {
+                return System.getProperty(name);
+            }
+            public void exit() throws Exception {
+                terminal.close();
+            }
+        };
+        try {
+            new Shell(context, processor, terminal).gosh(newSession, new String[]{"--login"});
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            try {
+                terminal.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    public void nano(final CommandSession session, String[] argv) throws Exception {
+        commands.nano(Shell.getTerminal(session), System.out, System.err, Shell.cwd(session), argv);
+    }
+
+    public void watch(final CommandSession session, String[] argv) throws IOException, InterruptedException {
+        final String[] usage = {
+                "watch - watches & refreshes the output of a command",
+                "Usage: watch [OPTIONS] COMMAND",
+                "  -? --help                    Show help",
+                "  -n --interval                Interval between executions of the command in seconds",
+                "  -a --append                  The output should be appended but not clear the console"
+        };
+        final Options opt = Options.compile(usage).parse(argv);
+        if (opt.isSet("help")) {
+            opt.usage(System.err);
+            return;
+        }
+        List<String> args = opt.args();
+        if (args.isEmpty()) {
+            System.err.println("Argument expected");
+            return;
+        }
+        ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
+        final Terminal terminal = Shell.getTerminal(session);
+        final CommandProcessor processor = Shell.getProcessor(session);
+        try {
+            int interval = 1;
+            if (opt.isSet("interval")) {
+                interval = opt.getNumber("interval");
+                if (interval < 1) {
+                    interval = 1;
+                }
+            }
+            final String cmd = String.join(" ", args);
+            Runnable task = () -> {
+                ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                InputStream is = new ByteArrayInputStream(new byte[0]);
+                PrintStream os = new PrintStream(baos);
+                if (opt.isSet("append") || !terminal.puts(Capability.clear_screen)) {
+                    terminal.writer().println();
+                }
+                try {
+                    CommandSession ns = processor.createSession(is, os, os);
+                    Set<String> vars = Shell.getCommands(session);
+                    for (String n : vars) {
+                        ns.put(n, session.get(n));
+                    }
+                    ns.execute(cmd);
+                } catch (Throwable t) {
+                    t.printStackTrace(os);
+                }
+                os.flush();
+                terminal.writer().print(baos.toString());
+                terminal.writer().flush();
+            };
+            executorService.scheduleAtFixedRate(task, 0, interval, TimeUnit.SECONDS);
+            Attributes attr = terminal.enterRawMode();
+            terminal.reader().read();
+            terminal.setAttributes(attr);
+        } finally {
+            executorService.shutdownNow();
+        }
+    }
+
+    public void less(CommandSession session, String[] argv) throws IOException, InterruptedException {
+        commands.less(Shell.getTerminal(session), System.out, System.err, Shell.cwd(session), argv);
+    }
+
+    public void history(CommandSession session, String[] argv) throws IOException {
+        commands.history(Shell.getReader(session), System.out, System.err, argv);
+    }
+
+    public void complete(CommandSession session, String[] argv) {
+        commands.complete(Shell.getReader(session), System.out, System.err, Shell.getCompletions(session), argv);
+    }
+
+    public void widget(final CommandSession session, String[] argv) throws Exception {
+        java.util.function.Function<String, Widget> creator = func -> () -> {
+            try {
+                session.execute(func);
+            } catch (Exception e) {
+                // TODO: log exception ?
+                return false;
+            }
+            return true;
+        };
+        commands.widget(Shell.getReader(session), System.out, System.err, creator, argv);
+    }
+
+    public void keymap(CommandSession session, String[] argv) {
+        commands.keymap(Shell.getReader(session), System.out, System.err, argv);
+    }
+
+    public void setopt(CommandSession session, String[] argv) {
+        commands.setopt(Shell.getReader(session), System.out, System.err, argv);
+    }
+
+    public void unsetopt(CommandSession session, String[] argv) {
+        commands.unsetopt(Shell.getReader(session), System.out, System.err, argv);
+    }
+
+    public List<Candidate> __files(CommandSession session) {
+        ParsedLine line = Shell.getParsedLine(session);
+        LineReader reader = Shell.getReader(session);
+        List<Candidate> candidates = new ArrayList<>();
+        new FilesCompleter(new File(Shell.cwd(session))).complete(reader, line, candidates);
+        return candidates;
+    }
+
+    public List<Candidate> __directories(CommandSession session) {
+        ParsedLine line = Shell.getParsedLine(session);
+        LineReader reader = Shell.getReader(session);
+        List<Candidate> candidates = new ArrayList<>();
+        new DirectoriesCompleter(new File(Shell.cwd(session))).complete(reader, line, candidates);
+        return candidates;
+    }
+
+    public void __usage_completion(CommandSession session, String command) throws Exception {
+        Object func = session.get(command.contains(":") ? command : "*:" + command);
+        if (func instanceof Function) {
+            ByteArrayInputStream bais = new ByteArrayInputStream(new byte[0]);
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            ByteArrayOutputStream baes = new ByteArrayOutputStream();
+            CommandSession ts = ((CommandSessionImpl) session).processor().createSession(bais, new PrintStream(baos), new PrintStream(baes));
+            ts.execute(command + " --help");
+
+            String regex = "(?x)\\s*" + "(?:-([^-]))?" +  // 1: short-opt-1
+                    "(?:,?\\s*-(\\w))?" +                 // 2: short-opt-2
+                    "(?:,?\\s*--(\\w[\\w-]*)(=\\w+)?)?" + // 3: long-opt-1 and 4:arg-1
+                    "(?:,?\\s*--(\\w[\\w-]*))?" +         // 5: long-opt-2
+                    ".*?(?:\\(default=(.*)\\))?\\s*" +    // 6: default
+                    "(.*)";                               // 7: description
+            Pattern pattern = Pattern.compile(regex);
+            for (String l : baes.toString().split("\n")) {
+                Matcher matcher = pattern.matcher(l);
+                if (matcher.matches()) {
+                    List<String> args = new ArrayList<>();
+                    if (matcher.group(1) != null) {
+                        args.add("--short-option");
+                        args.add(matcher.group(1));
+                    }
+                    if (matcher.group(3) != null) {
+                        args.add("--long-option");
+                        args.add(matcher.group(1));
+                    }
+                    if (matcher.group(4) != null) {
+                        args.add("--argument");
+                        args.add("");
+                    }
+                    if (matcher.group(7) != null) {
+                        args.add("--description");
+                        args.add(matcher.group(7));
+                    }
+                    complete(session, args.toArray(new String[args.size()]));
+                }
+            }
+        }
+    }
+
+}
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/JLineCompletionEnvironment.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/JLineCompletionEnvironment.java
new file mode 100644
index 0000000..93b31ef
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/JLineCompletionEnvironment.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.jline;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.felix.service.command.CommandSession;
+import org.jline.builtins.Completers.CompletionData;
+import org.jline.builtins.Completers.CompletionEnvironment;
+import org.jline.reader.LineReader;
+import org.jline.reader.ParsedLine;
+
+public class JLineCompletionEnvironment implements CompletionEnvironment {
+
+    private final CommandSession session;
+
+    public JLineCompletionEnvironment(CommandSession session) {
+        this.session = session;
+    }
+
+    public Map<String, List<CompletionData>> getCompletions() {
+        return Shell.getCompletions(session);
+    }
+
+    public Set<String> getCommands() {
+        return Shell.getCommands(session);
+    }
+
+    public String resolveCommand(String command) {
+        return Shell.resolve(session, command);
+    }
+
+    public String commandName(String command) {
+        int idx = command.indexOf(':');
+        return idx >= 0 ? command.substring(idx + 1) : command;
+    }
+
+    public Object evaluate(LineReader reader, ParsedLine line, String func) throws Exception {
+        session.put(Shell.VAR_COMMAND_LINE, line);
+        return session.execute(func);
+    }
+}
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Main.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Main.java
new file mode 100644
index 0000000..8d78f6a
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Main.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.jline;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintStream;
+
+import org.apache.felix.gogo.jline.Shell.Context;
+import org.apache.felix.gogo.jline.ssh.Ssh;
+import org.apache.felix.gogo.jline.telnet.Telnet;
+import org.apache.felix.gogo.runtime.CommandProcessorImpl;
+import org.apache.felix.gogo.runtime.threadio.ThreadIOImpl;
+import org.apache.felix.service.command.CommandSession;
+import org.jline.terminal.Terminal;
+import org.jline.terminal.TerminalBuilder;
+
+public class Main {
+
+    public static void main(String[] args) throws IOException {
+        InputStream sin = System.in;
+        PrintStream sout = System.out;
+        PrintStream serr = System.err;
+
+        try (Terminal terminal = TerminalBuilder.builder()
+                .name("gogo")
+                .type(System.getenv("TERM"))
+                .system(true)
+                .streams(sin, sout)
+                .build()) {
+            ThreadIOImpl tio = new ThreadIOImpl();
+            tio.start();
+            try {
+                CommandProcessorImpl processor = new CommandProcessorImpl(tio);
+                Context context = new MyContext();
+                Shell shell = new Shell(context, processor, terminal);
+                processor.addCommand("gogo", processor, "addCommand");
+                processor.addCommand("gogo", processor, "removeCommand");
+                processor.addCommand("gogo", processor, "eval");
+                register(processor, new Builtin(), Builtin.functions);
+                register(processor, new Procedural(), Procedural.functions);
+                register(processor, new Posix(), Posix.functions);
+                register(processor, shell, Shell.functions);
+                register(processor, new JLineCommands(processor), JLineCommands.functions);
+                try {
+                    register(processor, new Telnet(processor), Telnet.functions);
+                } catch (Throwable t) {
+                    // ignore
+                }
+                try {
+                    register(processor, new Ssh(processor), Ssh.functions);
+                } catch (Throwable t) {
+                    // ignore
+                }
+                PrintStream pout = new PrintStream(terminal.output());
+                CommandSession session = processor.createSession(terminal.input(), pout, pout);
+                session.put(Shell.VAR_CONTEXT, context);
+                session.put(Shell.VAR_TERMINAL, terminal);
+                try {
+                    String[] argv = new String[args.length + 1];
+                    argv[0] = "--login";
+                    System.arraycopy(args, 0, argv, 1, args.length);
+                    shell.gosh(session, argv);
+                } catch (Exception e) {
+                    Object loc = session.get(".location");
+                    if (null == loc || !loc.toString().contains(":")) {
+                        loc = "gogo";
+                    }
+
+                    System.err.println(loc + ": " + e.getClass().getSimpleName() + ": " + e.getMessage());
+                    e.printStackTrace();
+                } finally {
+                    session.close();
+                }
+            } finally {
+                tio.stop();
+            }
+        }
+    }
+
+    static void register(CommandProcessorImpl processor, Object target, String[] functions) {
+        for (String function : functions) {
+            processor.addCommand("gogo", target, function);
+        }
+    }
+
+    private static class MyContext implements Context {
+
+        public String getProperty(String name) {
+            return System.getProperty(name);
+        }
+
+        public void exit() throws Exception {
+            System.exit(0);
+        }
+    }
+}
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ParsedLineImpl.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ParsedLineImpl.java
new file mode 100644
index 0000000..22f0cdc
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ParsedLineImpl.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.jline;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.felix.gogo.runtime.Parser.Program;
+import org.apache.felix.gogo.runtime.Token;
+import org.jline.reader.ParsedLine;
+
+public class ParsedLineImpl implements ParsedLine {
+
+    private final Program program;
+    private final String source;
+    private final int cursor;
+    private final List<String> tokens;
+    private final int wordIndex;
+    private final int wordCursor;
+
+    public ParsedLineImpl(Program program, Token line, int cursor, List<Token> tokens) {
+        this.program = program;
+        this.source = line.toString();
+        this.cursor = cursor - line.start();
+        this.tokens = new ArrayList<String>();
+        for (Token token : tokens) {
+            this.tokens.add(token.toString());
+        }
+        int wi = tokens.size();
+        int wc = 0;
+        if (cursor >= 0) {
+            for (int i = 0; i < tokens.size(); i++) {
+                Token t = tokens.get(i);
+                if (t.start() > cursor) {
+                    wi = i;
+                    wc = 0;
+                    this.tokens.add(i, "");
+                    break;
+                }
+                if (t.start() + t.length() >= cursor) {
+                    wi = i;
+                    wc = cursor - t.start();
+                    break;
+                }
+            }
+        }
+        if (wi == tokens.size()) {
+            this.tokens.add("");
+        }
+        wordIndex = wi;
+        wordCursor = wc;
+    }
+
+    public String word() {
+        return tokens.get(wordIndex());
+    }
+
+    public int wordCursor() {
+        return wordCursor;
+    }
+
+    public int wordIndex() {
+        return wordIndex;
+    }
+
+    public List<String> words() {
+        return tokens;
+    }
+
+    public String line() {
+        return source;
+    }
+
+    public int cursor() {
+        return cursor;
+    }
+
+    public Program program() {
+        return program;
+    }
+}
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Parser.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Parser.java
new file mode 100644
index 0000000..4864252
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Parser.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.jline;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.felix.gogo.runtime.EOFError;
+import org.apache.felix.gogo.runtime.Parser.Program;
+import org.apache.felix.gogo.runtime.Parser.Statement;
+import org.apache.felix.gogo.runtime.SyntaxError;
+import org.apache.felix.gogo.runtime.Token;
+import org.jline.reader.ParsedLine;
+
+public class Parser implements org.jline.reader.Parser {
+
+    public ParsedLine parse(String line, int cursor) throws org.jline.reader.SyntaxError {
+        try {
+            return doParse(line, cursor);
+        } catch (EOFError e) {
+            throw new org.jline.reader.EOFError(e.line(), e.column(), e.getMessage(), e.missing());
+        } catch (SyntaxError e) {
+            throw new org.jline.reader.SyntaxError(e.line(), e.column(), e.getMessage());
+        }
+    }
+
+    private ParsedLine doParse(CharSequence line, int cursor) throws SyntaxError {
+        org.apache.felix.gogo.runtime.Parser parser = new org.apache.felix.gogo.runtime.Parser(line);
+        Program program = parser.program();
+        List<Statement> statements = parser.statements();
+        // Find corresponding statement
+        Statement statement = null;
+        for (int i = statements.size() - 1; i >= 0; i--) {
+            Statement s = statements.get(i);
+            if (s.start() <= cursor) {
+                boolean isOk = true;
+                // check if there are only spaces after the previous statement
+                if (s.start() + s.length() < cursor) {
+                    for (int j = s.start() + s.length(); isOk && j < cursor; j++) {
+                        isOk = Character.isWhitespace(line.charAt(j));
+                    }
+                }
+                statement = s;
+                break;
+            }
+        }
+        if (statement != null) {
+            return new ParsedLineImpl(program, statement, cursor, statement.tokens());
+        } else {
+            // TODO:
+            return new ParsedLineImpl(program, program, cursor, Collections.<Token>singletonList(program));
+        }
+    }
+
+}
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Posix.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Posix.java
new file mode 100644
index 0000000..05c4908
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Posix.java
@@ -0,0 +1,816 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.jline;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.felix.service.command.CommandSession;
+import org.jline.builtins.Options;
+
+/**
+ * Posix-like utilities.
+ *
+ * @see <a href="http://www.opengroup.org/onlinepubs/009695399/utilities/contents.html">
+ * http://www.opengroup.org/onlinepubs/009695399/utilities/contents.html</a>
+ */
+public class Posix {
+    static final String[] functions = {"cat", "echo", "grep", "sort", "sleep", "cd", "pwd", "ls"};
+
+    static final String CWD = "_cwd";
+
+    public static File _pwd(CommandSession session) {
+        try {
+            File cwd = (File) session.get(CWD);
+            if (cwd == null) {
+                cwd = new File(".").getCanonicalFile();
+                session.put(CWD, cwd);
+            }
+            return cwd;
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static void sort(CommandSession session, String[] argv) throws IOException {
+        final String[] usage = {
+                "sort -  writes sorted standard input to standard output.",
+                "Usage: sort [OPTIONS] [FILES]",
+                "  -? --help                    show help",
+                "  -f --ignore-case             fold lower case to upper case characters",
+                "  -r --reverse                 reverse the result of comparisons",
+                "  -u --unique                  output only the first of an equal run",
+                "  -t --field-separator=SEP     use SEP instead of non-blank to blank transition",
+                "  -b --ignore-leading-blanks   ignore leading blancks",
+                "     --numeric-sort            compare according to string numerical value",
+                "  -k --key=KEY                 fields to use for sorting separated by whitespaces"};
+
+        Options opt = Options.compile(usage).parse(argv);
+
+        if (opt.isSet("help")) {
+            opt.usage(System.err);
+            return;
+        }
+
+        List<String> args = opt.args();
+
+        List<String> lines = new ArrayList<String>();
+        if (!args.isEmpty()) {
+            for (String filename : args) {
+                BufferedReader reader = new BufferedReader(new InputStreamReader(
+                        Shell.cwd(session).resolve(filename).toURL().openStream()));
+                try {
+                    read(reader, lines);
+                } finally {
+                    reader.close();
+                }
+            }
+        } else {
+            BufferedReader r = new BufferedReader(new InputStreamReader(System.in));
+            read(r, lines);
+        }
+
+        String separator = opt.get("field-separator");
+        boolean caseInsensitive = opt.isSet("ignore-case");
+        boolean reverse = opt.isSet("reverse");
+        boolean ignoreBlanks = opt.isSet("ignore-leading-blanks");
+        boolean numeric = opt.isSet("numeric-sort");
+        boolean unique = opt.isSet("unique");
+        List<String> sortFields = opt.getList("key");
+
+        char sep = (separator == null || separator.length() == 0) ? '\0' : separator.charAt(0);
+        Collections.sort(lines, new SortComparator(caseInsensitive, reverse, ignoreBlanks, numeric, sep, sortFields));
+        String last = null;
+        for (String s : lines) {
+            if (!unique || last == null || !s.equals(last)) {
+                System.out.println(s);
+            }
+            last = s;
+        }
+    }
+
+    protected static void read(BufferedReader r, List<String> lines) throws IOException {
+        for (String s = r.readLine(); s != null; s = r.readLine()) {
+            lines.add(s);
+        }
+    }
+
+    public static List<String> parseSubstring(String value) {
+        List<String> pieces = new ArrayList<String>();
+        StringBuilder ss = new StringBuilder();
+        // int kind = SIMPLE; // assume until proven otherwise
+        boolean wasStar = false; // indicates last piece was a star
+        boolean leftstar = false; // track if the initial piece is a star
+        boolean rightstar = false; // track if the final piece is a star
+
+        int idx = 0;
+
+        // We assume (sub)strings can contain leading and trailing blanks
+        boolean escaped = false;
+        loop:
+        for (; ; ) {
+            if (idx >= value.length()) {
+                if (wasStar) {
+                    // insert last piece as "" to handle trailing star
+                    rightstar = true;
+                } else {
+                    pieces.add(ss.toString());
+                    // accumulate the last piece
+                    // note that in the case of
+                    // (cn=); this might be
+                    // the string "" (!=null)
+                }
+                ss.setLength(0);
+                break loop;
+            }
+
+            // Read the next character and account for escapes.
+            char c = value.charAt(idx++);
+            if (!escaped && ((c == '(') || (c == ')'))) {
+                throw new IllegalArgumentException(
+                        "Illegal value: " + value);
+            } else if (!escaped && (c == '*')) {
+                if (wasStar) {
+                    // encountered two successive stars;
+                    // I assume this is illegal
+                    throw new IllegalArgumentException("Invalid filter string: " + value);
+                }
+                if (ss.length() > 0) {
+                    pieces.add(ss.toString()); // accumulate the pieces
+                    // between '*' occurrences
+                }
+                ss.setLength(0);
+                // if this is a leading star, then track it
+                if (pieces.size() == 0) {
+                    leftstar = true;
+                }
+                wasStar = true;
+            } else if (!escaped && (c == '\\')) {
+                escaped = true;
+            } else {
+                escaped = false;
+                wasStar = false;
+                ss.append(c);
+            }
+        }
+        if (leftstar || rightstar || pieces.size() > 1) {
+            // insert leading and/or trailing "" to anchor ends
+            if (rightstar) {
+                pieces.add("");
+            }
+            if (leftstar) {
+                pieces.add(0, "");
+            }
+        }
+        return pieces;
+    }
+
+    public static boolean compareSubstring(List<String> pieces, String s) {
+        // Walk the pieces to match the string
+        // There are implicit stars between each piece,
+        // and the first and last pieces might be "" to anchor the match.
+        // assert (pieces.length > 1)
+        // minimal case is <string>*<string>
+
+        boolean result = true;
+        int len = pieces.size();
+
+        int index = 0;
+
+        loop:
+        for (int i = 0; i < len; i++) {
+            String piece = pieces.get(i);
+
+            // If this is the first piece, then make sure the
+            // string starts with it.
+            if (i == 0) {
+                if (!s.startsWith(piece)) {
+                    result = false;
+                    break loop;
+                }
+            }
+
+            // If this is the last piece, then make sure the
+            // string ends with it.
+            if (i == len - 1) {
+                if (s.endsWith(piece)) {
+                    result = true;
+                } else {
+                    result = false;
+                }
+                break loop;
+            }
+
+            // If this is neither the first or last piece, then
+            // make sure the string contains it.
+            if ((i > 0) && (i < (len - 1))) {
+                index = s.indexOf(piece, index);
+                if (index < 0) {
+                    result = false;
+                    break loop;
+                }
+            }
+
+            // Move string index beyond the matching piece.
+            index += piece.length();
+        }
+
+        return result;
+    }
+
+    private static void cat(final BufferedReader reader, boolean displayLineNumbers) throws IOException {
+        String line;
+        int lineno = 1;
+        try {
+            while ((line = reader.readLine()) != null) {
+                if (displayLineNumbers) {
+                    System.out.print(String.format("%6d  ", lineno++));
+                }
+                System.out.println(line);
+            }
+        } finally {
+            reader.close();
+        }
+    }
+
+    private static <T> void addAll(List<? super T> list, T[] array) {
+        if (array != null) {
+            Collections.addAll(list, array);
+        }
+    }
+
+    public File pwd(CommandSession session, String[] argv) throws IOException {
+        final String[] usage = {
+                "pwd - get current directory",
+                "Usage: pwd [OPTIONS]",
+                "  -? --help                show help"
+        };
+        Options opt = Options.compile(usage).parse(argv);
+        if (opt.isSet("help")) {
+            opt.usage(System.err);
+            return null;
+        }
+        if (!opt.args().isEmpty()) {
+            System.err.println("usage: pwd");
+            return null;
+        }
+        File cwd = (File) session.get(CWD);
+        if (cwd == null) {
+            cwd = new File(".").getCanonicalFile();
+            session.put(CWD, cwd);
+        }
+        return cwd;
+    }
+
+    public File cd(CommandSession session, String[] argv)
+            throws IOException {
+        final String[] usage = {
+                "cd - get current directory",
+                "Usage: cd [OPTIONS] DIRECTORY",
+                "  -? --help                show help"
+        };
+        Options opt = Options.compile(usage).parse(argv);
+        if (opt.isSet("help")) {
+            opt.usage(System.err);
+            return null;
+        }
+        if (opt.args().size() != 1) {
+            System.err.println("usage: cd DIRECTORY");
+            return null;
+        }
+        File cwd = pwd(session, new String[0]);
+        String dir = opt.args().get(0);
+
+        URI curUri = cwd.toURI();
+        URI newUri = curUri.resolve(dir);
+
+        cwd = new File(newUri);
+        if (!cwd.exists()) {
+            throw new IOException("Directory does not exist");
+        } else if (!cwd.isDirectory()) {
+            throw new IOException("Target is not a directory");
+        }
+        session.put(CWD, cwd.getCanonicalFile());
+        return cwd;
+    }
+
+    public Collection<File> ls(CommandSession session, String[] argv) throws IOException {
+        final String[] usage = {
+                "ls - list files",
+                "Usage: ls [OPTIONS] PATTERNS...",
+                "  -? --help                show help"
+        };
+        Options opt = Options.compile(usage).parse(argv);
+        if (opt.isSet("help")) {
+            opt.usage(System.err);
+            return null;
+        }
+        if (opt.args().isEmpty()) {
+            opt.args().add("*");
+        }
+        opt.args().remove(0);
+        List<File> files = new ArrayList<File>();
+        for (String pattern : opt.args()) {
+            pattern = ((pattern == null) || (pattern.length() == 0)) ? "." : pattern;
+            pattern = ((pattern.charAt(0) != File.separatorChar) && (pattern.charAt(0) != '.'))
+                    ? "./" + pattern : pattern;
+            int idx = pattern.lastIndexOf(File.separatorChar);
+            String parent = (idx < 0) ? "." : pattern.substring(0, idx + 1);
+            String target = (idx < 0) ? pattern : pattern.substring(idx + 1);
+
+            File actualParent = ((parent.charAt(0) == File.separatorChar)
+                    ? new File(parent)
+                    : new File(pwd(session, new String[0]), parent)).getCanonicalFile();
+
+            idx = target.indexOf(File.separatorChar, idx);
+            boolean isWildcarded = (target.indexOf('*', idx) >= 0);
+            if (isWildcarded) {
+                if (!actualParent.exists()) {
+                    throw new IOException("File does not exist");
+                }
+                final List<String> pieces = parseSubstring(target);
+                addAll(files, actualParent.listFiles(new FileFilter() {
+                    public boolean accept(File pathname) {
+                        return compareSubstring(pieces, pathname.getName());
+                    }
+                }));
+            } else {
+                File actualTarget = new File(actualParent, target).getCanonicalFile();
+                if (!actualTarget.exists()) {
+                    throw new IOException("File does not exist");
+                }
+                if (actualTarget.isDirectory()) {
+                    addAll(files, actualTarget.listFiles());
+                } else {
+                    files.add(actualTarget);
+                }
+            }
+        }
+        return files;
+    }
+
+    public void cat(CommandSession session, String[] argv) throws Exception {
+        final String[] usage = {
+                "cat - concatenate and print FILES",
+                "Usage: cat [OPTIONS] [FILES]",
+                "  -? --help                show help",
+                "  -n                       number the output lines, starting at 1"
+        };
+
+        Options opt = Options.compile(usage).parse(argv);
+
+        if (opt.isSet("help")) {
+            opt.usage(System.err);
+            return;
+        }
+
+        List<String> args = opt.args();
+        if (args.isEmpty()) {
+            args = Collections.singletonList("-");
+        }
+
+        URI cwd = Shell.cwd(session);
+        for (String arg : args) {
+            InputStream is;
+            if ("-".equals(arg)) {
+                is = System.in;
+
+            } else {
+                is = cwd.resolve(arg).toURL().openStream();
+            }
+            cat(new BufferedReader(new InputStreamReader(is)), opt.isSet("n"));
+        }
+    }
+
+    public void echo(Object[] argv) {
+        final String[] usage = {
+                "echo - echoes or prints ARGUMENT to standard output",
+                "Usage: echo [OPTIONS] [ARGUMENTS]",
+                "  -? --help                show help",
+                "  -n                       no trailing new line"
+        };
+
+        Options opt = Options.compile(usage).parse(argv);
+
+        if (opt.isSet("help")) {
+            opt.usage(System.err);
+            return;
+        }
+
+        List<String> args = opt.args();
+        StringBuilder buf = new StringBuilder();
+        if (args != null) {
+            for (String arg : args) {
+                if (buf.length() > 0)
+                    buf.append(' ');
+                buf.append(arg);
+            }
+        }
+        if (opt.isSet("n")) {
+            System.out.print(buf);
+        } else {
+            System.out.println(buf);
+        }
+    }
+
+    public boolean grep(CommandSession session, String[] argv) throws IOException {
+        final String[] usage = {
+                "grep -  search for PATTERN in each FILE or standard input.",
+                "Usage: grep [OPTIONS] PATTERN [FILES]",
+                "  -? --help                show help",
+                "  -i --ignore-case         ignore case distinctions",
+                "  -n --line-number         prefix each line with line number within its input file",
+                "  -q --quiet, --silent     suppress all normal output",
+                "  -v --invert-match        select non-matching lines"};
+
+        Options opt = Options.compile(usage).parse(argv);
+
+        if (opt.isSet("help")) {
+            opt.usage(System.err);
+            return true;
+        }
+
+        List<String> args = opt.args();
+
+        if (args.isEmpty()) {
+            throw opt.usageError("no pattern supplied.");
+        }
+
+        String regex = args.remove(0);
+        if (opt.isSet("ignore-case")) {
+            regex = "(?i)" + regex;
+        }
+
+        if (args.isEmpty()) {
+            args.add(null);
+        }
+
+        StringBuilder buf = new StringBuilder();
+
+        if (args.size() > 1) {
+            buf.append("%1$s:");
+        }
+
+        if (opt.isSet("line-number")) {
+            buf.append("%2$s:");
+        }
+
+        buf.append("%3$s");
+        String format = buf.toString();
+
+        Pattern pattern = Pattern.compile(regex);
+        boolean status = true;
+        boolean match = false;
+
+        for (String arg : args) {
+            InputStream in = null;
+
+            try {
+                URI cwd = Shell.cwd(session);
+                in = (arg == null) ? System.in : cwd.resolve(arg).toURL().openStream();
+
+                BufferedReader rdr = new BufferedReader(new InputStreamReader(in));
+                int line = 0;
+                String s;
+                while ((s = rdr.readLine()) != null) {
+                    line++;
+                    Matcher matcher = pattern.matcher(s);
+                    if (!(matcher.find() ^ !opt.isSet("invert-match"))) {
+                        match = true;
+                        if (opt.isSet("quiet"))
+                            break;
+
+                        System.out.println(String.format(format, arg, line, s));
+                    }
+                }
+
+                if (match && opt.isSet("quiet")) {
+                    break;
+                }
+            } catch (IOException e) {
+                System.err.println("grep: " + e.getMessage());
+                status = false;
+            } finally {
+                if (arg != null && in != null) {
+                    in.close();
+                }
+            }
+        }
+
+        return match && status;
+    }
+
+    public void sleep(String[] argv) throws InterruptedException {
+        final String[] usage = {
+                "sleep -  suspend execution for an interval of time",
+                "Usage: sleep seconds",
+                "  -? --help                    show help"};
+
+        Options opt = Options.compile(usage).parse(argv);
+
+        if (opt.isSet("help")) {
+            opt.usage(System.err);
+            return;
+        }
+
+        List<String> args = opt.args();
+        if (args.size() != 1) {
+            System.err.println("usage: sleep seconds");
+        } else {
+            int s = Integer.parseInt(args.get(0));
+            Thread.sleep(s * 1000);
+        }
+    }
+
+    public static class SortComparator implements Comparator<String> {
+
+        private static Pattern fpPattern;
+
+        static {
+            final String Digits = "(\\p{Digit}+)";
+            final String HexDigits = "(\\p{XDigit}+)";
+            final String Exp = "[eE][+-]?" + Digits;
+            final String fpRegex = "([\\x00-\\x20]*[+-]?(NaN|Infinity|(((" + Digits + "(\\.)?(" + Digits + "?)(" + Exp + ")?)|(\\.(" + Digits + ")(" + Exp + ")?)|(((0[xX]" + HexDigits + "(\\.)?)|(0[xX]" + HexDigits + "?(\\.)" + HexDigits + "))[pP][+-]?" + Digits + "))" + "[fFdD]?))[\\x00-\\x20]*)(.*)";
+            fpPattern = Pattern.compile(fpRegex);
+        }
+
+        private boolean caseInsensitive;
+        private boolean reverse;
+        private boolean ignoreBlanks;
+        private boolean numeric;
+        private char separator;
+        private List<Key> sortKeys;
+
+        public SortComparator(boolean caseInsensitive,
+                              boolean reverse,
+                              boolean ignoreBlanks,
+                              boolean numeric,
+                              char separator,
+                              List<String> sortFields) {
+            this.caseInsensitive = caseInsensitive;
+            this.reverse = reverse;
+            this.separator = separator;
+            this.ignoreBlanks = ignoreBlanks;
+            this.numeric = numeric;
+            if (sortFields == null || sortFields.size() == 0) {
+                sortFields = new ArrayList<String>();
+                sortFields.add("1");
+            }
+            sortKeys = new ArrayList<Key>();
+            for (String f : sortFields) {
+                sortKeys.add(new Key(f));
+            }
+        }
+
+        public int compare(String o1, String o2) {
+            int res = 0;
+
+            List<Integer> fi1 = getFieldIndexes(o1);
+            List<Integer> fi2 = getFieldIndexes(o2);
+            for (Key key : sortKeys) {
+                int[] k1 = getSortKey(o1, fi1, key);
+                int[] k2 = getSortKey(o2, fi2, key);
+                if (key.numeric) {
+                    Double d1 = getDouble(o1, k1[0], k1[1]);
+                    Double d2 = getDouble(o2, k2[0], k2[1]);
+                    res = d1.compareTo(d2);
+                } else {
+                    res = compareRegion(o1, k1[0], k1[1], o2, k2[0], k2[1], key.caseInsensitive);
+                }
+                if (res != 0) {
+                    if (key.reverse) {
+                        res = -res;
+                    }
+                    break;
+                }
+            }
+            return res;
+        }
+
+        protected Double getDouble(String s, int start, int end) {
+            Matcher m = fpPattern.matcher(s.substring(start, end));
+            m.find();
+            return new Double(s.substring(0, m.end(1)));
+        }
+
+        protected int compareRegion(String s1, int start1, int end1, String s2, int start2, int end2, boolean caseInsensitive) {
+            int n1 = end1, n2 = end2;
+            for (int i1 = start1, i2 = start2; i1 < end1 && i2 < n2; i1++, i2++) {
+                char c1 = s1.charAt(i1);
+                char c2 = s2.charAt(i2);
+                if (c1 != c2) {
+                    if (caseInsensitive) {
+                        c1 = Character.toUpperCase(c1);
+                        c2 = Character.toUpperCase(c2);
+                        if (c1 != c2) {
+                            c1 = Character.toLowerCase(c1);
+                            c2 = Character.toLowerCase(c2);
+                            if (c1 != c2) {
+                                return c1 - c2;
+                            }
+                        }
+                    } else {
+                        return c1 - c2;
+                    }
+                }
+            }
+            return n1 - n2;
+        }
+
+        protected int[] getSortKey(String str, List<Integer> fields, Key key) {
+            int start;
+            int end;
+            if (key.startField * 2 <= fields.size()) {
+                start = fields.get((key.startField - 1) * 2);
+                if (key.ignoreBlanksStart) {
+                    while (start < fields.get((key.startField - 1) * 2 + 1) && Character.isWhitespace(str.charAt(start))) {
+                        start++;
+                    }
+                }
+                if (key.startChar > 0) {
+                    start = Math.min(start + key.startChar - 1, fields.get((key.startField - 1) * 2 + 1));
+                }
+            } else {
+                start = 0;
+            }
+            if (key.endField > 0 && key.endField * 2 <= fields.size()) {
+                end = fields.get((key.endField - 1) * 2);
+                if (key.ignoreBlanksEnd) {
+                    while (end < fields.get((key.endField - 1) * 2 + 1) && Character.isWhitespace(str.charAt(end))) {
+                        end++;
+                    }
+                }
+                if (key.endChar > 0) {
+                    end = Math.min(end + key.endChar - 1, fields.get((key.endField - 1) * 2 + 1));
+                }
+            } else {
+                end = str.length();
+            }
+            return new int[]{start, end};
+        }
+
+        protected List<Integer> getFieldIndexes(String o) {
+            List<Integer> fields = new ArrayList<Integer>();
+            if (o.length() > 0) {
+                if (separator == '\0') {
+                    int i = 0;
+                    fields.add(0);
+                    for (int idx = 1; idx < o.length(); idx++) {
+                        if (Character.isWhitespace(o.charAt(idx)) && !Character.isWhitespace(o.charAt(idx - 1))) {
+                            fields.add(idx - 1);
+                            fields.add(idx);
+                        }
+                    }
+                    fields.add(o.length() - 1);
+                } else {
+                    int last = -1;
+                    for (int idx = o.indexOf(separator); idx >= 0; idx = o.indexOf(separator, idx + 1)) {
+                        if (last >= 0) {
+                            fields.add(last);
+                            fields.add(idx - 1);
+                        } else if (idx > 0) {
+                            fields.add(0);
+                            fields.add(idx - 1);
+                        }
+                        last = idx + 1;
+                    }
+                    if (last < o.length()) {
+                        fields.add(last < 0 ? 0 : last);
+                        fields.add(o.length() - 1);
+                    }
+                }
+            }
+            return fields;
+        }
+
+        public class Key {
+            int startField;
+            int startChar;
+            int endField;
+            int endChar;
+            boolean ignoreBlanksStart;
+            boolean ignoreBlanksEnd;
+            boolean caseInsensitive;
+            boolean reverse;
+            boolean numeric;
+
+            public Key(String str) {
+                boolean modifiers = false;
+                boolean startPart = true;
+                boolean inField = true;
+                boolean inChar = false;
+                for (char c : str.toCharArray()) {
+                    switch (c) {
+                        case '0':
+                        case '1':
+                        case '2':
+                        case '3':
+                        case '4':
+                        case '5':
+                        case '6':
+                        case '7':
+                        case '8':
+                        case '9':
+                            if (!inField && !inChar) {
+                                throw new IllegalArgumentException("Bad field syntax: " + str);
+                            }
+                            if (startPart) {
+                                if (inChar) {
+                                    startChar = startChar * 10 + (c - '0');
+                                } else {
+                                    startField = startField * 10 + (c - '0');
+                                }
+                            } else {
+                                if (inChar) {
+                                    endChar = endChar * 10 + (c - '0');
+                                } else {
+                                    endField = endField * 10 + (c - '0');
+                                }
+                            }
+                            break;
+                        case '.':
+                            if (!inField) {
+                                throw new IllegalArgumentException("Bad field syntax: " + str);
+                            }
+                            inField = false;
+                            inChar = true;
+                            break;
+                        case 'n':
+                            inField = false;
+                            inChar = false;
+                            modifiers = true;
+                            numeric = true;
+                            break;
+                        case 'f':
+                            inField = false;
+                            inChar = false;
+                            modifiers = true;
+                            caseInsensitive = true;
+                            break;
+                        case 'r':
+                            inField = false;
+                            inChar = false;
+                            modifiers = true;
+                            reverse = true;
+                            break;
+                        case 'b':
+                            inField = false;
+                            inChar = false;
+                            modifiers = true;
+                            if (startPart) {
+                                ignoreBlanksStart = true;
+                            } else {
+                                ignoreBlanksEnd = true;
+                            }
+                            break;
+                        case ',':
+                            inField = true;
+                            inChar = false;
+                            startPart = false;
+                            break;
+                        default:
+                            throw new IllegalArgumentException("Bad field syntax: " + str);
+                    }
+                }
+                if (!modifiers) {
+                    ignoreBlanksStart = ignoreBlanksEnd = SortComparator.this.ignoreBlanks;
+                    reverse = SortComparator.this.reverse;
+                    caseInsensitive = SortComparator.this.caseInsensitive;
+                    numeric = SortComparator.this.numeric;
+                }
+                if (startField < 1) {
+                    throw new IllegalArgumentException("Bad field syntax: " + str);
+                }
+            }
+        }
+    }
+
+}
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Procedural.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Procedural.java
new file mode 100644
index 0000000..0311f1b
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Procedural.java
@@ -0,0 +1,149 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.jline;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.felix.service.command.CommandSession;
+import org.apache.felix.service.command.Function;
+
+public class Procedural {
+    static final String[] functions = {"each", "if", "not", "throw", "try", "until",
+            "while"};
+
+    public List<Object> each(CommandSession session, Collection<Object> list,
+                             Function closure) throws Exception {
+        List<Object> args = new ArrayList<Object>();
+        List<Object> results = new ArrayList<Object>();
+        args.add(null);
+
+        for (Object x : list) {
+            checkInterrupt();
+            args.set(0, x);
+            results.add(closure.execute(session, args));
+        }
+
+        return results;
+    }
+
+    public Object _if(CommandSession session, Function[] fns) throws Exception {
+        int length = fns.length;
+        if (length < 2) {
+            throw new IllegalArgumentException(
+                    "Usage: if {condition} {if-action} ... {else-action}");
+        }
+
+        for (int i = 0; i < length; ++i) {
+            if (i == length - 1 || isTrue(fns[i++].execute(session, null))) {
+                return fns[i].execute(session, null);
+            }
+        }
+
+        return null;
+    }
+
+    public boolean not(CommandSession session, Function condition) throws Exception {
+        if (null == condition) {
+            return true;
+        }
+
+        return !isTrue(condition.execute(session, null));
+    }
+
+    // Reflective.coerce() prefers to construct a new Throwable(String)
+    // than to call this method directly.
+    public void _throw(String message) {
+        throw new IllegalArgumentException(message);
+    }
+
+    public void _throw(Exception e) throws Exception {
+        throw e;
+    }
+
+    public void _throw(CommandSession session) throws Throwable {
+        Object exception = session.get("exception");
+        if (exception instanceof Throwable)
+            throw (Throwable) exception;
+        else
+            throw new IllegalArgumentException("exception not set or not Throwable.");
+    }
+
+    public Object _try(CommandSession session, Function func) throws Exception {
+        try {
+            return func.execute(session, null);
+        } catch (Exception e) {
+            session.put("exception", e);
+            return null;
+        }
+    }
+
+    public Object _try(CommandSession session, Function func, Function error)
+            throws Exception {
+        try {
+            return func.execute(session, null);
+        } catch (Exception e) {
+            session.put("exception", e);
+            return error.execute(session, null);
+        }
+    }
+
+    public void _while(CommandSession session, Function condition, Function ifTrue)
+            throws Exception {
+        while (isTrue(condition.execute(session, null))) {
+            ifTrue.execute(session, null);
+        }
+    }
+
+    public void until(CommandSession session, Function condition, Function ifTrue)
+            throws Exception {
+        while (!isTrue(condition.execute(session, null))) {
+            ifTrue.execute(session, null);
+        }
+    }
+
+    private boolean isTrue(Object result) throws InterruptedException {
+        checkInterrupt();
+
+        if (result == null)
+            return false;
+
+        if (result instanceof Boolean)
+            return ((Boolean) result).booleanValue();
+
+        if (result instanceof Number) {
+            if (0 == ((Number) result).intValue())
+                return false;
+        }
+
+        if ("".equals(result))
+            return false;
+
+        if ("0".equals(result))
+            return false;
+
+        return true;
+    }
+
+    private void checkInterrupt() throws InterruptedException {
+        if (Thread.currentThread().isInterrupted())
+            throw new InterruptedException("loop interrupted");
+    }
+}
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Shell.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Shell.java
new file mode 100644
index 0000000..d92fed6
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Shell.java
@@ -0,0 +1,537 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.jline;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLConnection;
+import java.nio.CharBuffer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TreeMap;
+
+import org.apache.felix.gogo.runtime.Closure;
+import org.apache.felix.gogo.runtime.CommandProxy;
+import org.apache.felix.gogo.runtime.CommandSessionImpl;
+import org.apache.felix.gogo.runtime.Expander;
+import org.apache.felix.gogo.runtime.Reflective;
+import org.apache.felix.service.command.CommandProcessor;
+import org.apache.felix.service.command.CommandSession;
+import org.apache.felix.service.command.Converter;
+import org.apache.felix.service.command.Descriptor;
+import org.apache.felix.service.command.Function;
+import org.apache.felix.service.command.Parameter;
+import org.jline.builtins.Completers.CompletionData;
+import org.jline.builtins.Options;
+import org.jline.reader.EndOfFileException;
+import org.jline.reader.LineReader;
+import org.jline.reader.LineReaderBuilder;
+import org.jline.reader.ParsedLine;
+import org.jline.reader.UserInterruptException;
+import org.jline.reader.impl.history.history.FileHistory;
+import org.jline.terminal.Terminal;
+
+public class Shell {
+
+    public static final String VAR_COMPLETIONS = ".completions";
+    public static final String VAR_COMMAND_LINE = ".commandLine";
+    public static final String VAR_READER = ".reader";
+    public static final String VAR_SESSION = ".session";
+    public static final String VAR_PROCESSOR = ".processor";
+    public static final String VAR_TERMINAL = ".terminal";
+    public static final String VAR_EXCEPTION = "exception";
+    public static final String VAR_LOCATION = ".location";
+    public static final String VAR_PROMPT = "prompt";
+    public static final String VAR_RPROMPT = "rprompt";
+    public static final String VAR_SCOPE = "SCOPE";
+    public static final String VAR_CONTEXT = org.apache.felix.gogo.runtime.activator.Activator.CONTEXT;
+
+    static final String[] functions = {"gosh", "sh", "source", "help"};
+
+    private final URI baseURI;
+    private final String profile;
+    private final Context context;
+    private final CommandProcessor processor;
+
+    public Shell(Context context, CommandProcessor processor, Terminal terminal) {
+        this(context, processor, terminal, null);
+    }
+
+    public Shell(Context context, CommandProcessor processor, Terminal terminal, String profile) {
+        this.context = context;
+        this.processor = processor;
+        String baseDir = context.getProperty("gosh.home");
+        baseDir = (baseDir == null) ? context.getProperty("user.dir") : baseDir;
+        this.baseURI = new File(baseDir).toURI();
+        this.profile = profile != null ? profile : "gosh_profile";
+    }
+
+    public static Terminal getTerminal(CommandSession session) {
+        return (Terminal) session.get(VAR_TERMINAL);
+    }
+
+    public static LineReader getReader(CommandSession session) {
+        return (LineReader) session.get(VAR_READER);
+    }
+
+    public static CommandProcessor getProcessor(CommandSession session) {
+        return (CommandProcessor) session.get(VAR_PROCESSOR);
+    }
+
+    @SuppressWarnings("unchecked")
+    public static Map<String, List<CompletionData>> getCompletions(CommandSession session) {
+        return (Map) session.get(VAR_COMPLETIONS);
+    }
+
+    @SuppressWarnings("unchecked")
+    public static Set<String> getCommands(CommandSession session) {
+        return (Set<String>) session.get(CommandSessionImpl.COMMANDS);
+    }
+
+    public static ParsedLine getParsedLine(CommandSession session) {
+        return (ParsedLine) session.get(VAR_COMMAND_LINE);
+    }
+
+    public static String getPrompt(CommandSession session) {
+        return expand(session, VAR_PROMPT, "gl! ");
+    }
+
+    public static String getRPrompt(CommandSession session) {
+        return expand(session, VAR_RPROMPT, null);
+    }
+
+    public static String expand(CommandSession session, String name, String def) {
+        Object prompt = session.get(name);
+        if (prompt != null) {
+            try {
+                Object o = Expander.expand(
+                        prompt.toString(),
+                        new Closure((CommandSessionImpl) session, null, null));
+                if (o != null) {
+                    return o.toString();
+                }
+            } catch (Exception e) {
+                // ignore
+            }
+        }
+        return def;
+    }
+
+    public static String resolve(CommandSession session, String command) {
+        String resolved = command;
+        if (command.indexOf(':') < 0) {
+            Set<String> commands = getCommands(session);
+            Object path = session.get(VAR_SCOPE);
+            String scopePath = (null == path ? "*" : path.toString());
+            for (String scope : scopePath.split(":")) {
+                for (String entry : commands) {
+                    if ("*".equals(scope) && entry.endsWith(":" + command)
+                            || entry.equals(scope + ":" + command)) {
+                        resolved = entry;
+                        break;
+                    }
+                }
+            }
+        }
+        return resolved;
+    }
+
+    public static CharSequence readScript(URI script) throws Exception {
+        URLConnection conn = script.toURL().openConnection();
+        int length = conn.getContentLength();
+
+        if (length == -1) {
+            System.err.println("eek! unknown Contentlength for: " + script);
+            length = 10240;
+        }
+
+        InputStream in = conn.getInputStream();
+        CharBuffer cbuf = CharBuffer.allocate(length);
+        Reader reader = new InputStreamReader(in);
+        reader.read(cbuf);
+        in.close();
+        cbuf.rewind();
+
+        return cbuf;
+    }
+
+    @SuppressWarnings("unchecked")
+    static Set<String> getVariables(CommandSession session) {
+        return (Set<String>) session.get(".variables");
+    }
+
+    static URI cwd(CommandSession session) {
+        return Posix._pwd(session).toURI(); // _cwd is set by felixcommands:cd
+    }
+
+    private static <T extends Annotation> T findAnnotation(Annotation[] anns,
+                                                           Class<T> clazz) {
+        for (int i = 0; (anns != null) && (i < anns.length); i++) {
+            if (clazz.isInstance(anns[i])) {
+                return clazz.cast(anns[i]);
+            }
+        }
+        return null;
+    }
+
+    public Object gosh(final CommandSession session, String[] argv) throws Exception {
+        final String[] usage = {
+                "gosh - execute script with arguments in a new session",
+                "  args are available as session variables $1..$9 and $args.",
+                "Usage: gosh [OPTIONS] [script-file [args..]]",
+                "  -c --command             pass all remaining args to sub-shell",
+                "     --nointeractive       don't start interactive session",
+                "     --login               login shell (same session, reads etc/gosh_profile)",
+                "  -s --noshutdown          don't shutdown framework when script completes",
+                "  -x --xtrace              echo commands before execution",
+                "  -? --help                show help",
+                "If no script-file, an interactive shell is started, type $D to exit."};
+
+        Options opt = Options.compile(usage).setOptionsFirst(true).parse(argv);
+        List<String> args = opt.args();
+
+        boolean login = opt.isSet("login");
+        boolean interactive = !opt.isSet("nointeractive");
+
+        if (opt.isSet("help")) {
+            opt.usage(System.err);
+            if (login && !opt.isSet("noshutdown")) {
+                shutdown();
+            }
+            return null;
+        }
+
+        if (opt.isSet("command") && args.isEmpty()) {
+            throw opt.usageError("option --command requires argument(s)");
+        }
+
+        CommandSession newSession = (login ? session : processor.createSession(
+                session.getKeyboard(), session.getConsole(), System.err));
+
+        if (opt.isSet("xtrace")) {
+            newSession.put("echo", true);
+        }
+
+        // export variables starting with upper-case to newSession
+        for (String key : getVariables(session)) {
+            if (key.matches("[.]?[A-Z].*")) {
+                newSession.put(key, session.get(key));
+            }
+        }
+
+        Terminal terminal = getTerminal(session);
+        newSession.put(Shell.VAR_CONTEXT, context);
+        newSession.put(Shell.VAR_TERMINAL, terminal);
+        newSession.put(Shell.VAR_PROCESSOR, processor);
+        newSession.put(Shell.VAR_SESSION, session);
+        newSession.put("#TERM", (Function) (s, arguments) -> terminal.getType());
+        newSession.put("#COLUMNS", (Function) (s, arguments) -> terminal.getWidth());
+        newSession.put("#LINES", (Function) (s, arguments) -> terminal.getHeight());
+
+        LineReader reader = null;
+        if (args.isEmpty() && interactive) {
+            reader = LineReaderBuilder.builder()
+                    .terminal(terminal)
+                    .variables(((CommandSessionImpl) newSession).getVariables())
+                    .completer(new org.jline.builtins.Completers.Completer(new JLineCompletionEnvironment(newSession)))
+                    .highlighter(new Highlighter(session))
+                    .history(new FileHistory(new File(System.getProperty("user.home"), ".gogo.history")))
+                    .parser(new Parser())
+                    .build();
+            newSession.put(Shell.VAR_READER, reader);
+            newSession.put(Shell.VAR_COMPLETIONS, new HashMap());
+        }
+
+        if (login || interactive) {
+            URI uri = baseURI.resolve("etc/" + profile);
+            if (!new File(uri).exists()) {
+                URL url = getClass().getResource("/ext/" + profile);
+                if (url == null) {
+                    url = getClass().getResource("/" + profile);
+                }
+                uri = (url == null) ? null : url.toURI();
+            }
+            if (uri != null) {
+                source(newSession, uri.toString());
+            }
+        }
+
+        Object result = null;
+
+        if (args.isEmpty()) {
+            if (interactive) {
+                while (true) {
+                    try {
+                        reader.readLine(Shell.getPrompt(session), Shell.getRPrompt(session), null, null);
+                        ParsedLine parsedLine = reader.getParsedLine();
+                        if (parsedLine == null) {
+                            throw new EndOfFileException();
+                        }
+                        try {
+                            result = session.execute(((ParsedLineImpl) parsedLine).program());
+                            session.put("_", result); // set $_ to last result
+
+                            if (result != null && !Boolean.FALSE.equals(session.get(".Gogo.format"))) {
+                                System.out.println(session.format(result, Converter.INSPECT));
+                            }
+                        } catch (Exception e) {
+                            session.put(Shell.VAR_EXCEPTION, e);
+                            Object loc = session.get(Shell.VAR_LOCATION);
+
+                            if (null == loc || !loc.toString().contains(":")) {
+                                loc = "gogo";
+                            }
+                            System.out.println(loc + ": " + e.getClass().getSimpleName() + ": "
+                                    + e.getMessage());
+                        }
+
+                    } catch (UserInterruptException e) {
+                        // continue;
+                    } catch (EndOfFileException e) {
+                        try {
+                            reader.getHistory().flush();
+                        } catch (IOException e1) {
+                            e.addSuppressed(e1);
+                        }
+                        break;
+                    }
+                }
+            }
+        } else {
+            CharSequence program;
+
+            if (opt.isSet("command")) {
+                StringBuilder buf = new StringBuilder();
+                for (String arg : args) {
+                    if (buf.length() > 0) {
+                        buf.append(' ');
+                    }
+                    buf.append(arg);
+                }
+                program = buf;
+            } else {
+                URI script = cwd(session).resolve(args.remove(0));
+
+                // set script arguments
+                newSession.put("0", script);
+                newSession.put("args", args);
+
+                for (int i = 0; i < args.size(); ++i) {
+                    newSession.put(String.valueOf(i + 1), args.get(i));
+                }
+
+                program = readScript(script);
+            }
+
+            result = newSession.execute(program);
+        }
+
+        if (login && interactive && !opt.isSet("noshutdown")) {
+            System.out.println("gosh: stopping framework");
+            shutdown();
+        }
+
+        return result;
+    }
+
+    public Object sh(final CommandSession session, String[] argv) throws Exception {
+        return gosh(session, argv);
+    }
+
+    private void shutdown() throws Exception {
+        context.exit();
+    }
+
+    public Object source(CommandSession session, String script) throws Exception {
+        URI uri = cwd(session).resolve(script);
+        session.put("0", uri);
+        try {
+            return session.execute(readScript(uri));
+        } finally {
+            session.put("0", null); // API doesn't support remove
+        }
+    }
+
+    private Map<String, List<Method>> getReflectionCommands(CommandSession session) {
+        Map<String, List<Method>> commands = new TreeMap<String, List<Method>>();
+        Set<String> names = getCommands(session);
+        for (String name : names) {
+            Function function = (Function) session.get(name);
+            if (function instanceof CommandProxy) {
+                Object target = ((CommandProxy) function).getTarget();
+                List<Method> methods = new ArrayList<>();
+                String func = name.substring(name.indexOf(':') + 1).toLowerCase();
+                List<String> funcs = new ArrayList<>();
+                funcs.add("is" + func);
+                funcs.add("get" + func);
+                funcs.add("set" + func);
+                if (Reflective.KEYWORDS.contains(func)) {
+                    funcs.add("_" + func);
+                } else {
+                    funcs.add(func);
+                }
+                for (Method method : target.getClass().getMethods()) {
+                    if (funcs.contains(method.getName().toLowerCase())) {
+                        methods.add(method);
+                    }
+                }
+                commands.put(name, methods);
+                ((CommandProxy) function).ungetTarget();
+            }
+        }
+        return commands;
+    }
+
+    @Descriptor("displays available commands")
+    public void help(CommandSession session) {
+        Map<String, List<Method>> commands = getReflectionCommands(session);
+        for (String name : commands.keySet()) {
+            System.out.println(name);
+        }
+    }
+
+    @Descriptor("displays information about a specific command")
+    public void help(CommandSession session, @Descriptor("target command") String name) {
+        Map<String, List<Method>> commands = getReflectionCommands(session);
+
+        List<Method> methods = null;
+
+        // If the specified command doesn't have a scope, then
+        // search for matching methods by ignoring the scope.
+        int scopeIdx = name.indexOf(':');
+        if (scopeIdx < 0) {
+            for (Entry<String, List<Method>> entry : commands.entrySet()) {
+                String k = entry.getKey().substring(entry.getKey().indexOf(':') + 1);
+                if (name.equals(k)) {
+                    name = entry.getKey();
+                    methods = entry.getValue();
+                    break;
+                }
+            }
+        }
+        // Otherwise directly look up matching methods.
+        else {
+            methods = commands.get(name);
+        }
+
+        if ((methods != null) && (methods.size() > 0)) {
+            for (Method m : methods) {
+                Descriptor d = m.getAnnotation(Descriptor.class);
+                if (d == null) {
+                    System.out.println("\n" + m.getName());
+                } else {
+                    System.out.println("\n" + m.getName() + " - " + d.value());
+                }
+
+                System.out.println("   scope: " + name.substring(0, name.indexOf(':')));
+
+                // Get flags and options.
+                Class<?>[] paramTypes = m.getParameterTypes();
+                Map<String, Parameter> flags = new TreeMap<>();
+                Map<String, String> flagDescs = new TreeMap<>();
+                Map<String, Parameter> options = new TreeMap<>();
+                Map<String, String> optionDescs = new TreeMap<>();
+                List<String> params = new ArrayList<String>();
+                Annotation[][] anns = m.getParameterAnnotations();
+                for (int paramIdx = 0; paramIdx < anns.length; paramIdx++) {
+                    Class<?> paramType = m.getParameterTypes()[paramIdx];
+                    if (paramType == CommandSession.class) {
+                        /* Do not bother the user with a CommandSession. */
+                        continue;
+                    }
+                    Parameter p = findAnnotation(anns[paramIdx], Parameter.class);
+                    d = findAnnotation(anns[paramIdx], Descriptor.class);
+                    if (p != null) {
+                        if (p.presentValue().equals(Parameter.UNSPECIFIED)) {
+                            options.put(p.names()[0], p);
+                            if (d != null) {
+                                optionDescs.put(p.names()[0], d.value());
+                            }
+                        } else {
+                            flags.put(p.names()[0], p);
+                            if (d != null) {
+                                flagDescs.put(p.names()[0], d.value());
+                            }
+                        }
+                    } else if (d != null) {
+                        params.add(paramTypes[paramIdx].getSimpleName());
+                        params.add(d.value());
+                    } else {
+                        params.add(paramTypes[paramIdx].getSimpleName());
+                        params.add("");
+                    }
+                }
+
+                // Print flags and options.
+                if (flags.size() > 0) {
+                    System.out.println("   flags:");
+                    for (Entry<String, Parameter> entry : flags.entrySet()) {
+                        // Print all aliases.
+                        String[] names = entry.getValue().names();
+                        System.out.print("      " + names[0]);
+                        for (int aliasIdx = 1; aliasIdx < names.length; aliasIdx++) {
+                            System.out.print(", " + names[aliasIdx]);
+                        }
+                        System.out.println("   " + flagDescs.get(entry.getKey()));
+                    }
+                }
+                if (options.size() > 0) {
+                    System.out.println("   options:");
+                    for (Entry<String, Parameter> entry : options.entrySet()) {
+                        // Print all aliases.
+                        String[] names = entry.getValue().names();
+                        System.out.print("      " + names[0]);
+                        for (int aliasIdx = 1; aliasIdx < names.length; aliasIdx++) {
+                            System.out.print(", " + names[aliasIdx]);
+                        }
+                        System.out.println("   "
+                                + optionDescs.get(entry.getKey())
+                                + ((entry.getValue().absentValue() == null) ? ""
+                                : " [optional]"));
+                    }
+                }
+                if (params.size() > 0) {
+                    System.out.println("   parameters:");
+                    for (Iterator<String> it = params.iterator(); it.hasNext(); ) {
+                        System.out.println("      " + it.next() + "   " + it.next());
+                    }
+                }
+            }
+        }
+    }
+
+    public interface Context {
+        String getProperty(String name);
+
+        void exit() throws Exception;
+    }
+
+}
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ssh/ShellCommand.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ssh/ShellCommand.java
new file mode 100644
index 0000000..a679a60
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ssh/ShellCommand.java
@@ -0,0 +1,145 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.jline.ssh;
+
+import java.io.CharArrayWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.io.Reader;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.felix.service.command.CommandProcessor;
+import org.apache.felix.service.command.CommandSession;
+import org.apache.sshd.server.Command;
+import org.apache.sshd.server.Environment;
+import org.apache.sshd.server.ExitCallback;
+import org.apache.sshd.server.SessionAware;
+import org.apache.sshd.server.session.ServerSession;
+
+public class ShellCommand implements Command, Runnable, SessionAware {
+
+    public static final String SHELL_INIT_SCRIPT = "karaf.shell.init.script";
+    public static final String EXEC_INIT_SCRIPT = "karaf.exec.init.script";
+
+    private static final Logger LOGGER = Logger.getLogger(ShellCommand.class.getName());
+
+    private String command;
+    private InputStream in;
+    private OutputStream out;
+    private OutputStream err;
+    private ExitCallback callback;
+    private ServerSession session;
+    private CommandProcessor processor;
+    private Environment env;
+
+    public ShellCommand(CommandProcessor processor, String command) {
+        this.processor = processor;
+        this.command = command;
+    }
+
+    public void setInputStream(InputStream in) {
+        this.in = in;
+    }
+
+    public void setOutputStream(OutputStream out) {
+        this.out = out;
+    }
+
+    public void setErrorStream(OutputStream err) {
+        this.err = err;
+    }
+
+    public void setExitCallback(ExitCallback callback) {
+        this.callback = callback;
+    }
+
+    public void setSession(ServerSession session) {
+        this.session = session;
+    }
+
+    public void start(final Environment env) throws IOException {
+        this.env = env;
+        new Thread(this).start();
+    }
+
+    public void run() {
+        int exitStatus = 0;
+        try {
+            final CommandSession session = processor.createSession(in, new PrintStream(out), new PrintStream(err));
+            for (Map.Entry<String, String> e : env.getEnv().entrySet()) {
+                session.put(e.getKey(), e.getValue());
+            }
+            try {
+                String scriptFileName = System.getProperty(EXEC_INIT_SCRIPT);
+                if (scriptFileName == null) {
+                    scriptFileName = System.getProperty(SHELL_INIT_SCRIPT);
+                }
+                executeScript(scriptFileName, session);
+                session.execute(command);
+            } catch (Throwable t) {
+                exitStatus = 1;
+                t.printStackTrace();
+            }
+        } catch (Exception e) {
+            exitStatus = 1;
+            LOGGER.log(Level.SEVERE, "Unable to start shell", e);
+        } finally {
+            ShellFactoryImpl.close(in, out, err);
+            callback.onExit(exitStatus);
+        }
+    }
+
+    public void destroy() {
+    }
+
+    private void executeScript(String scriptFileName, CommandSession session) {
+        if (scriptFileName != null) {
+            Reader r = null;
+            try {
+                File scriptFile = new File(scriptFileName);
+                r = new InputStreamReader(new FileInputStream(scriptFile));
+                CharArrayWriter w = new CharArrayWriter();
+                int n;
+                char[] buf = new char[8192];
+                while ((n = r.read(buf)) > 0) {
+                    w.write(buf, 0, n);
+                }
+                session.execute(new String(w.toCharArray()));
+            } catch (Exception e) {
+                LOGGER.log(Level.FINE, "Error in initialization script", e);
+            } finally {
+                if (r != null) {
+                    try {
+                        r.close();
+                    } catch (IOException e) {
+                        // Ignore
+                    }
+                }
+            }
+        }
+    }
+
+}
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ssh/ShellCommandFactory.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ssh/ShellCommandFactory.java
new file mode 100644
index 0000000..d0a0a45
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ssh/ShellCommandFactory.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.jline.ssh;
+
+import org.apache.felix.service.command.CommandProcessor;
+import org.apache.sshd.server.Command;
+import org.apache.sshd.server.CommandFactory;
+
+public class ShellCommandFactory implements CommandFactory {
+
+    private CommandProcessor processor;
+
+    public ShellCommandFactory(CommandProcessor processor) {
+        this.processor = processor;
+    }
+
+    public Command createCommand(String command) {
+        return new ShellCommand(processor, command);
+    }
+
+}
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ssh/ShellFactoryImpl.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ssh/ShellFactoryImpl.java
new file mode 100644
index 0000000..ad0cc8d
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ssh/ShellFactoryImpl.java
@@ -0,0 +1,277 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.jline.ssh;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.util.Map;
+
+import org.apache.felix.gogo.jline.Shell;
+import org.apache.felix.gogo.jline.Shell.Context;
+import org.apache.felix.service.command.CommandProcessor;
+import org.apache.felix.service.command.CommandSession;
+import org.apache.sshd.common.Factory;
+import org.apache.sshd.common.channel.PtyMode;
+import org.apache.sshd.server.Command;
+import org.apache.sshd.server.Environment;
+import org.apache.sshd.server.ExitCallback;
+import org.apache.sshd.server.SessionAware;
+import org.apache.sshd.server.Signal;
+import org.apache.sshd.server.SignalListener;
+import org.apache.sshd.server.session.ServerSession;
+import org.jline.terminal.Attributes;
+import org.jline.terminal.Attributes.ControlChar;
+import org.jline.terminal.Attributes.InputFlag;
+import org.jline.terminal.Attributes.LocalFlag;
+import org.jline.terminal.Attributes.OutputFlag;
+import org.jline.terminal.Size;
+import org.jline.terminal.Terminal;
+import org.jline.terminal.TerminalBuilder;
+
+/**
+ * SSHD {@link org.apache.sshd.server.Command} factory which provides access to
+ * Shell.
+ */
+public class ShellFactoryImpl implements Factory<Command> {
+    private final CommandProcessor processor;
+
+    public ShellFactoryImpl(CommandProcessor processor) {
+        this.processor = processor;
+    }
+
+    private static void flush(OutputStream... streams) {
+        for (OutputStream s : streams) {
+            try {
+                s.flush();
+            } catch (IOException e) {
+                // Ignore
+            }
+        }
+    }
+
+    static void close(Closeable... closeables) {
+        for (Closeable c : closeables) {
+            try {
+                c.close();
+            } catch (IOException e) {
+                // Ignore
+            }
+        }
+    }
+
+    public Command create() {
+        return new ShellImpl();
+    }
+
+    public class ShellImpl implements Command, SessionAware {
+        private InputStream in;
+
+        private OutputStream out;
+
+        private OutputStream err;
+
+        private ExitCallback callback;
+
+        private ServerSession session;
+
+        private boolean closed;
+
+        public void setInputStream(final InputStream in) {
+            this.in = in;
+        }
+
+        public void setOutputStream(final OutputStream out) {
+            this.out = out;
+        }
+
+        public void setErrorStream(final OutputStream err) {
+            this.err = err;
+        }
+
+        public void setExitCallback(ExitCallback callback) {
+            this.callback = callback;
+        }
+
+        public void setSession(ServerSession session) {
+            this.session = session;
+        }
+
+        public void start(final Environment env) throws IOException {
+            try {
+                new Thread() {
+                    public void run() {
+                        try {
+                            ShellImpl.this.run(env);
+                        } catch (Throwable t) {
+                            t.printStackTrace();
+                        }
+                    }
+                }.start();
+            } catch (Exception e) {
+                throw (IOException) new IOException("Unable to start shell").initCause(e);
+            }
+        }
+
+        public void run(Environment env) throws Exception {
+            try {
+                Terminal terminal = TerminalBuilder.builder()
+                        .name("gogo")
+                        .type(env.getEnv().get("TERM"))
+                        .system(false)
+                        .streams(in, out)
+                        .build();
+                terminal.setSize(new Size(Integer.parseInt(env.getEnv().get("COLUMNS")),
+                        Integer.parseInt(env.getEnv().get("LINES"))));
+                Attributes attr = terminal.getAttributes();
+                for (Map.Entry<PtyMode, Integer> e : env.getPtyModes().entrySet()) {
+                    switch (e.getKey()) {
+                        case VINTR:
+                            attr.setControlChar(ControlChar.VINTR, e.getValue());
+                            break;
+                        case VQUIT:
+                            attr.setControlChar(ControlChar.VQUIT, e.getValue());
+                            break;
+                        case VERASE:
+                            attr.setControlChar(ControlChar.VERASE, e.getValue());
+                            break;
+                        case VKILL:
+                            attr.setControlChar(ControlChar.VKILL, e.getValue());
+                            break;
+                        case VEOF:
+                            attr.setControlChar(ControlChar.VEOF, e.getValue());
+                            break;
+                        case VEOL:
+                            attr.setControlChar(ControlChar.VEOL, e.getValue());
+                            break;
+                        case VEOL2:
+                            attr.setControlChar(ControlChar.VEOL2, e.getValue());
+                            break;
+                        case VSTART:
+                            attr.setControlChar(ControlChar.VSTART, e.getValue());
+                            break;
+                        case VSTOP:
+                            attr.setControlChar(ControlChar.VSTOP, e.getValue());
+                            break;
+                        case VSUSP:
+                            attr.setControlChar(ControlChar.VSUSP, e.getValue());
+                            break;
+                        case VDSUSP:
+                            attr.setControlChar(ControlChar.VDSUSP, e.getValue());
+                            break;
+                        case VREPRINT:
+                            attr.setControlChar(ControlChar.VREPRINT, e.getValue());
+                            break;
+                        case VWERASE:
+                            attr.setControlChar(ControlChar.VWERASE, e.getValue());
+                            break;
+                        case VLNEXT:
+                            attr.setControlChar(ControlChar.VLNEXT, e.getValue());
+                            break;
+                        /*
+                        case VFLUSH:
+                            attr.setControlChar(ControlChar.VMIN, e.getValue());
+                            break;
+                        case VSWTCH:
+                            attr.setControlChar(ControlChar.VTIME, e.getValue());
+                            break;
+                        */
+                        case VSTATUS:
+                            attr.setControlChar(ControlChar.VSTATUS, e.getValue());
+                            break;
+                        case VDISCARD:
+                            attr.setControlChar(ControlChar.VDISCARD, e.getValue());
+                            break;
+                        case ECHO:
+                            attr.setLocalFlag(LocalFlag.ECHO, e.getValue() != 0);
+                            break;
+                        case ICANON:
+                            attr.setLocalFlag(LocalFlag.ICANON, e.getValue() != 0);
+                            break;
+                        case ISIG:
+                            attr.setLocalFlag(LocalFlag.ISIG, e.getValue() != 0);
+                            break;
+                        case ICRNL:
+                            attr.setInputFlag(InputFlag.ICRNL, e.getValue() != 0);
+                            break;
+                        case INLCR:
+                            attr.setInputFlag(InputFlag.INLCR, e.getValue() != 0);
+                            break;
+                        case IGNCR:
+                            attr.setInputFlag(InputFlag.IGNCR, e.getValue() != 0);
+                            break;
+                        case OCRNL:
+                            attr.setOutputFlag(OutputFlag.OCRNL, e.getValue() != 0);
+                            break;
+                        case ONLCR:
+                            attr.setOutputFlag(OutputFlag.ONLCR, e.getValue() != 0);
+                            break;
+                        case ONLRET:
+                            attr.setOutputFlag(OutputFlag.ONLRET, e.getValue() != 0);
+                            break;
+                        case OPOST:
+                            attr.setOutputFlag(OutputFlag.OPOST, e.getValue() != 0);
+                            break;
+                    }
+                }
+                terminal.setAttributes(attr);
+                PrintStream pout = new PrintStream(terminal.output());
+                final CommandSession session = processor.createSession(terminal.input(), pout, pout);
+                for (Map.Entry<String, String> e : env.getEnv().entrySet()) {
+                    session.put(e.getKey(), e.getValue());
+                }
+                env.addSignalListener(new SignalListener() {
+                    @Override
+                    public void signal(Signal signal) {
+                        terminal.setSize(new Size(Integer.parseInt(env.getEnv().get("COLUMNS")),
+                                Integer.parseInt(env.getEnv().get("LINES"))));
+                        terminal.raise(Terminal.Signal.WINCH);
+                    }
+                }, Signal.WINCH);
+                Context context = new Context() {
+                    @Override
+                    public String getProperty(String name) {
+                        return System.getProperty(name);
+                    }
+
+                    @Override
+                    public void exit() throws Exception {
+                        destroy();
+                    }
+                };
+                new Shell(context, processor, terminal).gosh(session, new String[]{"--login"});
+            } catch (Throwable t) {
+                t.printStackTrace();
+            }
+        }
+
+        public void destroy() {
+            if (!closed) {
+                closed = true;
+                ShellFactoryImpl.flush(out, err);
+                ShellFactoryImpl.close(in, out, err);
+                callback.onExit(0);
+            }
+        }
+
+    }
+
+}
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ssh/Ssh.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ssh/Ssh.java
new file mode 100644
index 0000000..72e7ac1
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ssh/Ssh.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.jline.ssh;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.felix.service.command.CommandProcessor;
+import org.apache.felix.service.command.CommandSession;
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.server.Command;
+import org.apache.sshd.server.ServerBuilder;
+import org.apache.sshd.server.SshServer;
+import org.apache.sshd.server.command.ScpCommandFactory;
+import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
+import org.apache.sshd.server.subsystem.sftp.SftpSubsystemFactory;
+import org.jline.builtins.Options;
+
+public class Ssh {
+
+    public static final String[] functions = {"sshd"};
+
+    private static final int defaultPort = 2022;
+
+    private final CommandProcessor processor;
+    private SshServer server;
+    private Object context;
+    private int port;
+    private String ip;
+
+    public Ssh(CommandProcessor processor) {
+        this.processor = processor;
+    }
+
+    public void sshd(CommandSession session, String[] argv) throws IOException {
+        final String[] usage = {"sshd - start an ssh server",
+                "Usage: sshd [-i ip] [-p port] start | stop | status",
+                "  -i --ip=INTERFACE        listen interface (default=127.0.0.1)",
+                "  -p --port=PORT           listen port (default=" + defaultPort + ")",
+                "  -? --help                show help"};
+
+        Options opt = Options.compile(usage).parse(argv);
+        List<String> args = opt.args();
+
+        if (opt.isSet("help") || args.isEmpty()) {
+            opt.usage(System.err);
+            return;
+        }
+
+        String command = args.get(0);
+
+        if ("start".equals(command)) {
+            if (server != null) {
+                throw new IllegalStateException("sshd is already running on port " + port);
+            }
+            ip = opt.get("ip");
+            port = opt.getNumber("port");
+            context = session.get(org.apache.felix.gogo.runtime.activator.Activator.CONTEXT);
+            start();
+            status();
+        } else if ("stop".equals(command)) {
+            if (server == null) {
+                throw new IllegalStateException("sshd is not running.");
+            }
+            stop();
+        } else if ("status".equals(command)) {
+            status();
+        } else {
+            throw opt.usageError("bad command: " + command);
+        }
+
+    }
+
+    private void status() {
+        if (server != null) {
+            System.out.println("sshd is running on " + ip + ":" + port);
+        } else {
+            System.out.println("sshd is not running.");
+        }
+    }
+
+    private void start() throws IOException {
+        server = ServerBuilder.builder().build();
+        server.setPort(port);
+        server.setHost(ip);
+        server.setShellFactory(new ShellFactoryImpl(processor));
+        server.setCommandFactory(new ScpCommandFactory.Builder().withDelegate(new ShellCommandFactory(processor)).build());
+        server.setSubsystemFactories(Collections.<NamedFactory<Command>>singletonList(
+                new SftpSubsystemFactory.Builder().build()
+        ));
+        server.setKeyPairProvider(new SimpleGeneratorHostKeyProvider());
+        server.start();
+    }
+
+    private void stop() throws IOException {
+        server.stop();
+    }
+}
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/BootException.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/BootException.java
new file mode 100644
index 0000000..a29a824
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/BootException.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/***
+ * Java TelnetD library (embeddable telnet daemon)
+ * Copyright (c) 2000-2005 Dieter Wimberger
+ * All rights reserved.
+ * <p/>
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * <p/>
+ * Neither the name of the author nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ * <p/>
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS ``AS
+ * IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ ***/
+
+package org.apache.felix.gogo.jline.telnet;
+
+/**
+ * Class that implements a BootException.<br>
+ * This exception will flag a broken boot process,
+ * which expresses startup failure and unavailabilty
+ * of telnet service for the container application.
+ *
+ * @author Dieter Wimberger
+ * @version 2.0 (16/07/2006)
+ */
+public class BootException extends Exception {
+
+    /**
+     * Constructor method for a BootException.<br>
+     *
+     * @param msg String that contains an understandable failure message.
+     */
+    public BootException(String msg) {
+        super(msg);
+    }//constructor
+
+}//class BootException
\ No newline at end of file
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/Connection.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/Connection.java
new file mode 100644
index 0000000..b5f90d3
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/Connection.java
@@ -0,0 +1,257 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/***
+ * Java TelnetD library (embeddable telnet daemon)
+ * Copyright (c) 2000-2005 Dieter Wimberger
+ * All rights reserved.
+ * <p/>
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * <p/>
+ * Neither the name of the author nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ * <p/>
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS ``AS
+ * IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ ***/
+
+package org.apache.felix.gogo.jline.telnet;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Class that implements a connection with this telnet daemon.<br>
+ * It is derived from java.lang.Thread, which reflects the architecture
+ * constraint of one thread per connection. This might seem a waste of
+ * resources, but as a matter of fact sharing threads would require a
+ * far more complex imlementation, due to the fact that telnet is not a
+ * stateless protocol (i.e. alive throughout a session of multiple requests
+ * and responses).<br>
+ * Each Connection instance is created by the listeners ConnectionManager
+ * instance, making it part of a threadgroup and passing in an associated
+ * ConnectionData instance, that holds vital information about the connection.
+ * Be sure to take a look at their documention.<br>
+ * <p/>
+ * Once the thread has started and is running, it will get a login
+ * shell instance from the ShellManager and run passing its own reference.
+ *
+ * @author Dieter Wimberger
+ * @version 2.0 (16/07/2006)
+ * @see ConnectionManager
+ * @see ConnectionData
+ */
+public abstract class Connection
+        extends Thread {
+
+    private static final Logger LOG = Logger.getLogger(Connection.class.getName());
+    private static int number;            //unique number for a thread in the thread group
+    private boolean dead;
+    private List<ConnectionListener> listeners;
+
+    //Associations
+    private ConnectionData connectionData;    //associated information
+
+    /**
+     * Constructs a TelnetConnection by invoking its parent constructor
+     * and setting of various members.<br>
+     * Subsequently instantiates the whole i/o subsystem, negotiating
+     * telnet protocol level options etc.<br>
+     *
+     * @param tcg ThreadGroup that this instance is running in.
+     * @param cd  ConnectionData instance containing all vital information
+     *            of this connection.
+     * @see ConnectionData
+     */
+    public Connection(ThreadGroup tcg, ConnectionData cd) {
+        super(tcg, ("Connection" + (++number)));
+
+        connectionData = cd;
+        //init the connection listeners for events
+        //(there should actually be only one or two)
+        listeners = new CopyOnWriteArrayList<ConnectionListener>();
+        dead = false;
+    }//constructor
+
+    /**
+     * Method overloaded to implement following behaviour:
+     * <ol>
+     * <li> On first entry, retrieve an instance of the configured
+     * login shell from the ShellManager and run it.
+     * <li> Handle a shell switch or close down disgracefully when
+     * problems (i.e. unhandled unchecked exceptions) occur in the
+     * running shell.
+     * </ol>
+     */
+    public void run() {
+        try {
+            doRun();
+
+        } catch (Exception ex) {
+            LOG.log(Level.SEVERE, "run()", ex); //Handle properly
+        } finally {
+            //call close if not dead already
+            if (!dead) {
+                close();
+            }
+        }
+        LOG.log(Level.FINE, "run():: Returning from " + this.toString());
+    }//run
+
+    protected abstract void doRun() throws Exception;
+
+    protected abstract void doClose() throws Exception;
+
+    /**
+     * Method to access the associated connection data.
+     *
+     * @return ConnectionData associated with the Connection instance.
+     * @see ConnectionData
+     */
+    public ConnectionData getConnectionData() {
+        return connectionData;
+    }//getConnectionData
+
+    /**
+     * Closes the connection and its underlying i/o and network
+     * resources.<br>
+     */
+    public synchronized void close() {
+        if (dead) {
+            return;
+        } else {
+            try {
+                //connection dead
+                dead = true;
+                //close i/o
+                doClose();
+            } catch (Exception ex) {
+                LOG.log(Level.SEVERE, "close()", ex);
+                //handle
+            }
+            try {
+                //close socket
+                connectionData.getSocket().close();
+            } catch (Exception ex) {
+                LOG.log(Level.SEVERE, "close()", ex);
+                //handle
+            }
+            try {
+                //register closed connection in ConnectionManager
+                connectionData.getManager().registerClosedConnection(this);
+            } catch (Exception ex) {
+                LOG.log(Level.SEVERE, "close()", ex);
+                //handle
+            }
+            try {
+                //try to interrupt it
+                interrupt();
+            } catch (Exception ex) {
+                LOG.log(Level.SEVERE, "close()", ex);
+                //handle
+            }
+
+
+            LOG.log(Level.FINE, "Closed " + this.toString() + " and inactive.");
+        }
+    }//close
+
+    /**
+     * Returns if a connection has been closed.<br>
+     *
+     * @return the state of the connection.
+     */
+    public boolean isActive() {
+        return !dead;
+    }//isClosed
+
+    /****** Event handling ****************/
+
+    /**
+     * Method that registers a ConnectionListener with the
+     * Connection instance.
+     *
+     * @param cl ConnectionListener to be registered.
+     * @see ConnectionListener
+     */
+    public void addConnectionListener(ConnectionListener cl) {
+        listeners.add(cl);
+    }//addConnectionListener
+
+    /**
+     * Method that removes a ConnectionListener from the
+     * Connection instance.
+     *
+     * @param cl ConnectionListener to be removed.
+     * @see ConnectionListener
+     */
+    public void removeConnectionListener(ConnectionListener cl) {
+        listeners.remove(cl);
+    }//removeConnectionListener
+
+
+    /**
+     * Method called by the io subsystem to pass on a
+     * "low-level" event. It will be properly delegated to
+     * all registered listeners.
+     *
+     * @param ce ConnectionEvent to be processed.
+     * @see ConnectionEvent
+     */
+    public void processConnectionEvent(ConnectionEvent ce) {
+        for (ConnectionListener cl : listeners) {
+            switch (ce.getType()) {
+                case CONNECTION_IDLE:
+                    cl.connectionIdle(ce);
+                    break;
+                case CONNECTION_TIMEDOUT:
+                    cl.connectionTimedOut(ce);
+                    break;
+                case CONNECTION_LOGOUTREQUEST:
+                    cl.connectionLogoutRequest(ce);
+                    break;
+                case CONNECTION_BREAK:
+                    cl.connectionSentBreak(ce);
+                    break;
+                case CONNECTION_TERMINAL_GEOMETRY_CHANGED:
+                    cl.connectionTerminalGeometryChanged(ce);
+            }
+        }
+    }//processConnectionEvent
+
+}//class Connection
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/ConnectionData.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/ConnectionData.java
new file mode 100644
index 0000000..ca7288b
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/ConnectionData.java
@@ -0,0 +1,449 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/***
+ * Java TelnetD library (embeddable telnet daemon)
+ * Copyright (c) 2000-2005 Dieter Wimberger
+ * All rights reserved.
+ * <p/>
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * <p/>
+ * Neither the name of the author nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ * <p/>
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS ``AS
+ * IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ ***/
+
+package org.apache.felix.gogo.jline.telnet;
+
+import java.net.InetAddress;
+import java.net.Socket;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * An utility class that is used to store and allow retrieval
+ * of all data associated with a connection.
+ *
+ * @author Dieter Wimberger
+ * @version 2.0 (16/07/2006)
+ * @see Connection
+ */
+public class ConnectionData {
+
+    //Associations
+    private ConnectionManager connectionManager;    //the connection's ConnectionManager
+    private Socket socket;                            //the connection's socket
+    private InetAddress address;                    //the connection's IP Address Object
+    private Map<String, String> environment;        //the environment
+
+    //Members
+    private String hostName;                        //cache for the hostname
+    private String hostAddress;                        //cache for the host ip
+    private int port;                                //port of the connection
+    private Locale locale;                            //locale of the connection
+    private long lastActivity;                        //timestamp for the last activity
+    private boolean warned;                            //warned flag
+    private String negotiatedTerminalType;            //negotiated TerminalType as String
+    private int[] terminalGeometry;                    //negotiated terminal geometry
+    private boolean terminalGeometryChanged = true;    //flag for changes in the terminal geometry
+    private String loginShell;                      //the login shell
+    private boolean lineMode = false;
+
+    /**
+     * Constructs a ConnectionData instance storing vital
+     * information about a connection.
+     *
+     * @param sock Socket of the inbound connection.
+     */
+    public ConnectionData(Socket sock, ConnectionManager cm) {
+        socket = sock;
+        connectionManager = cm;
+        address = sock.getInetAddress();
+        setHostName();
+        setHostAddress();
+        setLocale();
+        port = sock.getPort();
+        //this will set a default geometry and terminal type for the terminal
+        terminalGeometry = new int[2];
+        terminalGeometry[0] = 80;    //width
+        terminalGeometry[1] = 25;    //height
+        negotiatedTerminalType = "default";
+        environment = new HashMap<String, String>(20);
+        //this will stamp the first activity for validity :)
+        activity();
+    }//ConnectionData
+
+
+    /**
+     * Returns a reference to the ConnectionManager the
+     * connection is associated with.
+     *
+     * @return Reference to the associated ConnectionManager.
+     * @see ConnectionManager
+     */
+    public ConnectionManager getManager() {
+        return connectionManager;
+    }//getManager
+
+    /**
+     * Returns a reference to the socket the Connection
+     * is associated with.
+     *
+     * @return Reference to the associated Socket.
+     * @see java.net.Socket
+     */
+    public Socket getSocket() {
+        return socket;
+    }//getSocket
+
+    /**
+     * Returns the remote port to which the socket is connected.
+     *
+     * @return String that contains the remote port number to which the socket is connected.
+     */
+    public int getPort() {
+        return port;
+    }//getPort
+
+    /**
+     * Returns the fully qualified host name for the connection's IP address.<br>
+     * The name is cached on creation for performance reasons. Subsequent calls
+     * will not result in resolve queries.
+     *
+     * @return String that contains the fully qualified host name for this address.
+     */
+    public String getHostName() {
+        return hostName;
+    }//getHostName
+
+    /**
+     * Returns the IP address of the connection.
+     *
+     * @return String that contains the connection's IP address.<br>
+     *         The format "%d.%d.%d.%d" is well known, where %d goes from zero to 255.
+     */
+    public String getHostAddress() {
+        return hostAddress;
+    }//getHostAddress
+
+    /**
+     * Returns the InetAddress object associated with the connection.
+     *
+     * @return InetAddress associated with the connection.
+     */
+    public InetAddress getInetAddress() {
+        return address;
+    }//getInetAddress
+
+    /**
+     * Returns the Locale object associated with the connection
+     * by carrying out a simple domain match. <br>
+     * This can either be effective, if your users are really
+     * home in the country they are connecting from,
+     * or ineffective if they are on the move getting connected
+     * from anywhere in the world.<br>
+     * <br>
+     * Yet this gives the chance of capturing a default locale
+     * and starting from some point. On application context
+     * this can be by far better handled, so be aware that
+     * it makes sense to spend some thoughts on that thing when you
+     * build your application.
+     *
+     * @return the Locale object "guessed" for the connection based
+     *         on its host name.
+     */
+    public Locale getLocale() {
+        return locale;
+    }//getLocale
+
+
+    /**
+     * Returns a timestamp of the last activity that happened on
+     * the associated connection.
+     *
+     * @return the timestamp as a long representing the difference,
+     *         measured in milliseconds, between the current time and
+     *         midnight, January 1, 1970 UTC.
+     */
+    public long getLastActivity() {
+        return lastActivity;
+    }//getLastActivity
+
+
+    /**
+     * Sets a new timestamp to the actual time in millis
+     * retrieved from the System. This will remove an idle warning
+     * flag if it has been set. Note that you can use this behaviour
+     * to implement your own complex idle timespan policies within
+     * the context of your application.<br>
+     * The check frequency of the ConnectionManager should just be set
+     * according to the lowest time to warning and time to disconnect
+     * requirements.
+     */
+    public void activity() {
+        warned = false;
+        lastActivity = System.currentTimeMillis();
+    }//setLastActivity
+
+    /**
+     * Returns the state of the idle warning flag, which
+     * will be true if a warning has been issued, and false
+     * if not.
+     *
+     * @return the state of the idle warning flag.
+     */
+    public boolean isWarned() {
+        return warned;
+    }//isWarned
+
+    /**
+     * Sets the state of the idle warning flag.<br>
+     * Note that this method will also update the
+     * the timestamp if the idle warning flag is removed,
+     * which means its kind of a second way to achieve the
+     * same thing as with the activity method.
+     *
+     * @param bool true if a warning is to be issued,
+     *             false if to be removed.
+     * @see #activity()
+     */
+    public void setWarned(boolean bool) {
+        warned = bool;
+        if (!bool) {
+            lastActivity = System.currentTimeMillis();
+        }
+    }//setWarned
+
+    /**
+     * Sets the terminal geometry data.<br>
+     * <em>This method should not be called explicitly
+     * by the application (i.e. the its here for the io subsystem).</em><br>
+     * A call will set the terminal geometry changed flag.
+     *
+     * @param width  of the terminal in columns.
+     * @param height of the terminal in rows.
+     */
+    public void setTerminalGeometry(int width, int height) {
+        terminalGeometry[0] = width;
+        terminalGeometry[1] = height;
+        terminalGeometryChanged = true;
+    }//setTerminalGeometry
+
+    /**
+     * Returns the terminal geometry in an array of two integers.
+     * <ul>
+     * <li>index 0: Width in columns.
+     * <li>index 1: Height in rows.
+     * </ul>
+     * A call will reset the terminal geometry changed flag.
+     *
+     * @return integer array containing width and height.
+     */
+    public int[] getTerminalGeometry() {
+        //we toggle the flag because the change should now be known
+        if (terminalGeometryChanged) terminalGeometryChanged = false;
+        return terminalGeometry;
+    }//getTerminalGeometry
+
+    /**
+     * Returns the width of the terminal in columns for convenience.
+     *
+     * @return the number of columns.
+     */
+    public int getTerminalColumns() {
+        return terminalGeometry[0];
+    }//getTerminalColumns
+
+    /**
+     * Returns the height of the terminal in rows for convenience.
+     *
+     * @return the number of rows.
+     */
+    public int getTerminalRows() {
+        return terminalGeometry[1];
+    }//getTerminalRows
+
+    /**
+     * Returns the state of the terminal geometry changed flag,
+     * which will be true if it has been set, and false
+     * if not.
+     *
+     * @return the state of the terminal geometry changed flag.
+     */
+    public boolean isTerminalGeometryChanged() {
+        return terminalGeometryChanged;
+    }//isTerminalGeometryChanged
+
+    /**
+     * Returns the terminal type that has been negotiated
+     * between the telnet client and the telnet server, in
+     * of a String.<br>
+     *
+     * @return the negotiated terminal type as String.
+     */
+    public String getNegotiatedTerminalType() {
+        return negotiatedTerminalType;
+    }//getNegotiatedTerminalType
+
+    /**
+     * Sets the terminal type that has been negotiated
+     * between telnet client and telnet server, in form of
+     * a String.<br>
+     * <p/>
+     * <em>This method should not be called explicitly
+     * by the application (i.e. the its here for the io subsystem).</em><br>
+     *
+     * @param termtype the negotiated terminal type as String.
+     */
+    public void setNegotiatedTerminalType(String termtype) {
+        negotiatedTerminalType = termtype;
+    }//setNegotiatedTerminalType
+
+    /**
+     * Returns the hashmap for storing and
+     * retrieving environment variables to be passed
+     * between shells.
+     *
+     * @return a <tt>HashMap</tt> instance.
+     */
+    public Map<String, String> getEnvironment() {
+        return environment;
+    }//getEnvironment
+
+    /**
+     * Returns the login shell name.
+     *
+     * @return the shell name as string.
+     */
+    public String getLoginShell() {
+        return loginShell;
+    }//getLoginShell
+
+    /**
+     * Sets the login shell name.
+     *
+     * @param s the shell name as string.
+     */
+    public void setLoginShell(String s) {
+        loginShell = s;
+    }//setLoginShell
+
+    /**
+     * Tests if in line mode.
+     *
+     * @return true if in line mode, false otherwise
+     */
+    public boolean isLineMode() {
+        return lineMode;
+    }//isLineMode
+
+    /**
+     * Sets the line mode flag for the connection.
+     * Note that the setting will only be used at
+     * startup at the moment.
+     *
+     * @param b true if to be initialized in linemode,
+     *          false otherwise.
+     */
+    public void setLineMode(boolean b) {
+        lineMode = b;
+    }//setLineMode
+
+    /**
+     * Mutator for HostName cache
+     */
+    private void setHostName() {
+        hostName = address.getHostName();
+    }//setHostName
+
+    /**
+     * Mutator for HostAddress cache
+     */
+    private void setHostAddress() {
+        hostAddress = address.getHostAddress();
+    }//setHostAddress
+
+    /**
+     * Mutator for Locale
+     * Sets a Locale derived from the hostname,
+     * or the default which is Locale.ENGLISH if something
+     * goes wrong.
+     * The localhost represents a problem for example :)
+     */
+    private void setLocale() {
+        String country = getHostName();
+        try {
+            country = country.substring(country.lastIndexOf(".") + 1);
+            if (country.equals("at")) {
+                locale = new Locale("de", "AT");
+            } else if (country.equals("de")) {
+                locale = new Locale("de", "DE");
+            } else if (country.equals("mx")) {
+                locale = new Locale("es", "MX");
+            } else if (country.equals("es")) {
+                locale = new Locale("es", "ES");
+            } else if (country.equals("it")) {
+                locale = Locale.ITALY;
+            } else if (country.equals("fr")) {
+                locale = Locale.FRANCE;
+            } else if (country.equals("uk")) {
+                locale = new Locale("en", "GB");
+            } else if (country.equals("arpa")) {
+                locale = Locale.US;
+            } else if (country.equals("com")) {
+                locale = Locale.US;
+            } else if (country.equals("edu")) {
+                locale = Locale.US;
+            } else if (country.equals("gov")) {
+                locale = Locale.US;
+            } else if (country.equals("org")) {
+                locale = Locale.US;
+            } else if (country.equals("mil")) {
+                locale = Locale.US;
+            } else {
+                //default to english
+                locale = Locale.ENGLISH;
+            }
+        } catch (Exception ex) {
+            //default to english
+            locale = Locale.ENGLISH;
+        }
+    }//setLocale
+
+}//class ConnectionData
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/ConnectionEvent.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/ConnectionEvent.java
new file mode 100644
index 0000000..3a75a4d
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/ConnectionEvent.java
@@ -0,0 +1,137 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/***
+ * Java TelnetD library (embeddable telnet daemon)
+ * Copyright (c) 2000-2005 Dieter Wimberger
+ * All rights reserved.
+ * <p/>
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * <p/>
+ * Neither the name of the author nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ * <p/>
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS ``AS
+ * IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ ***/
+
+package org.apache.felix.gogo.jline.telnet;
+
+/**
+ * Class implmenting a ConnectionEvent.<br>
+ * These events are used to communicate things that are
+ * supposed to be handled within the application context.
+ * These events are processed by the Connection instance
+ * calling upon its registered listeners.
+ *
+ * @author Dieter Wimberger
+ * @version 2.0 (16/07/2006)
+ * @see Connection
+ * @see ConnectionListener
+ */
+public class ConnectionEvent {
+
+    private final Connection source;
+    private final Type type;
+    /**
+     * Constructs a new instance of a ConnectionEvent
+     * with a given source (Connection) and a given type.
+     *
+     * @param source Connection that represents the source of this event.
+     * @param type int that contains one of the defined event types.
+     */
+    public ConnectionEvent(Connection source, Type type) {
+        this.type = type;
+        this.source = source;
+    }//constructor
+
+    /**
+     * Accessor method returning the source of the
+     * ConnectionEvent instance.
+     *
+     * @return Connection representing the source.
+     */
+    public Connection getSource() {
+        return source;
+    }//getSource
+
+    /**
+     * Method that helps identifying the type.
+     *
+     * @return Event type.
+     */
+    public Type getType() {
+        return type;
+    }//getType
+
+    public enum Type {
+        /**
+         * Defines the connection idle event type.<br>
+         * It occurs if a connection has been idle exceeding
+         * the configured time to warning.
+         */
+        CONNECTION_IDLE,
+
+        /**
+         * Defines the connection timed out event type.<br>
+         * It occurs if a connection has been idle exceeding
+         * the configured time to warning and the configured time
+         * to timedout.
+         */
+        CONNECTION_TIMEDOUT,
+
+        /**
+         * Defines the connection requested logout event type.<br>
+         * It occurs if a connection requested disgraceful logout by
+         * sending a <Ctrl>-<D> key combination.
+         */
+        CONNECTION_LOGOUTREQUEST,
+
+        /**
+         * Defines the connection sent break event type.<br>
+         * It occurs when the connection sent a NVT BREAK.
+         */
+        CONNECTION_BREAK,
+
+        /**
+         * Defines the connection geometry event type.
+         * It occurs when the connection sent a NAWS.
+         */
+        CONNECTION_TERMINAL_GEOMETRY_CHANGED;
+    }
+
+
+}//class ConnectionEvent
\ No newline at end of file
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/ConnectionFilter.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/ConnectionFilter.java
new file mode 100644
index 0000000..e948166
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/ConnectionFilter.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/***
+ * Java TelnetD library (embeddable telnet daemon)
+ * Copyright (c) 2000-2005 Dieter Wimberger
+ * All rights reserved.
+ * <p/>
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * <p/>
+ * Neither the name of the author nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ * <p/>
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS ``AS
+ * IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ ***/
+
+package org.apache.felix.gogo.jline.telnet;
+
+import java.net.InetAddress;
+
+/**
+ * Interface defining a generic IP level connection
+ * filter.<br>
+ * Due to the fact that this task depends heavily on
+ * application context, I chose a very generic way
+ * of applying IP level connection filtering.
+ * <br><br>
+ * Implementations should consider following issues:
+ * <ul>
+ * <li>performance
+ * <li>administration (maybe via an admin shell)
+ * <li>logging denials
+ * </ul>
+ *
+ * @author Dieter Wimberger
+ * @version 2.0 (16/07/2006)
+ */
+public interface ConnectionFilter {
+
+    /**
+     * Tests if a given ip address is allowed to connect.
+     *
+     * @param ip the address to be tested.
+     * @return true if allowed to connect, false otherwise.
+     */
+    boolean isAllowed(InetAddress ip);
+
+}//interface ConnectionFilter
\ No newline at end of file
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/ConnectionListener.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/ConnectionListener.java
new file mode 100644
index 0000000..38ed90b
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/ConnectionListener.java
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/***
+ * Java TelnetD library (embeddable telnet daemon)
+ * Copyright (c) 2000-2005 Dieter Wimberger
+ * All rights reserved.
+ * <p/>
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * <p/>
+ * Neither the name of the author nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ * <p/>
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS ``AS
+ * IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ ***/
+
+package org.apache.felix.gogo.jline.telnet;
+
+
+/**
+ * Interface to be implemented if a class wants to
+ * qualify as a ConnectionListener.<br>
+ * Note that a Shell is per contract also forced to
+ * implement this interface.
+ *
+ * @author Dieter Wimberger
+ * @version 2.0 (16/07/2006)
+ * @see ConnectionEvent
+ */
+public interface ConnectionListener {
+
+    /**
+     * Called when a CONNECTION_IDLE event occured.
+     *
+     * @param ce ConnectionEvent instance.
+     * @see ConnectionEvent.Type#CONNECTION_IDLE
+     */
+    void connectionIdle(ConnectionEvent ce);
+
+    /**
+     * Called when a CONNECTION_TIMEDOUT event occured.
+     *
+     * @param ce ConnectionEvent instance.
+     * @see ConnectionEvent.Type#CONNECTION_TIMEDOUT
+     */
+    void connectionTimedOut(ConnectionEvent ce);
+
+    /**
+     * Called when a CONNECTION_LOGOUTREQUEST occured.
+     *
+     * @param ce ConnectionEvent instance.
+     * @see ConnectionEvent.Type#CONNECTION_LOGOUTREQUEST
+     */
+    void connectionLogoutRequest(ConnectionEvent ce);
+
+    /**
+     * Called when a CONNECTION_BREAK event occured.
+     *
+     * @param ce ConnectionEvent instance.
+     * @see ConnectionEvent.Type#CONNECTION_BREAK
+     */
+    void connectionSentBreak(ConnectionEvent ce);
+
+    /**
+     * Called when a CONNECTION_TERMINAL_GEOMETRY_CHANGED event occured.
+     *
+     * @param ce ConnectionEvent instance.
+     * @see ConnectionEvent.Type#CONNECTION_TERMINAL_GEOMETRY_CHANGED
+     */
+    void connectionTerminalGeometryChanged(ConnectionEvent ce);
+
+}//interface ConnectionListener
\ No newline at end of file
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/ConnectionManager.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/ConnectionManager.java
new file mode 100644
index 0000000..d169a49
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/ConnectionManager.java
@@ -0,0 +1,393 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/***
+ * Java TelnetD library (embeddable telnet daemon)
+ * Copyright (c) 2000-2005 Dieter Wimberger
+ * All rights reserved.
+ * <p/>
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * <p/>
+ * Neither the name of the author nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ * <p/>
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS ``AS
+ * IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ ***/
+
+package org.apache.felix.gogo.jline.telnet;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Stack;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Class that takes care for active and queued connection.
+ * Housekeeping is done also for connections that were just broken
+ * off, or exceeded their timeout. 
+ *
+ * @author Dieter Wimberger
+ * @version 2.0 (16/07/2006)
+ */
+public abstract class ConnectionManager implements Runnable {
+
+    private static Logger LOG = Logger.getLogger(ConnectionManager.class.getName());
+    private final List<Connection> openConnections;
+    private Thread thread;
+    private ThreadGroup threadGroup; //ThreadGroup all connections run in
+    private Stack<Connection> closedConnections;
+    private ConnectionFilter connectionFilter; //reference to the connection filter
+    private int maxConnections; //maximum allowed connections stored from the properties
+    private int warningTimeout; //time to idle warning
+    private int disconnectTimeout; //time to idle diconnection
+    private int housekeepingInterval; //interval for managing cleanups
+    private String loginShell;
+    private boolean lineMode = false;
+    private boolean stopping = false;
+
+    public ConnectionManager() {
+        threadGroup = new ThreadGroup(toString() + "Connections");
+        closedConnections = new Stack<Connection>();
+        openConnections = Collections.synchronizedList(new ArrayList<Connection>(100));
+    }
+
+    public ConnectionManager(int con, int timew, int timedis, int hoke, ConnectionFilter filter, String lsh, boolean lm) {
+        this();
+        connectionFilter = filter;
+        loginShell = lsh;
+        lineMode = lm;
+        maxConnections = con;
+        warningTimeout = timew;
+        disconnectTimeout = timedis;
+        housekeepingInterval = hoke;
+    }//constructor
+
+    /**
+     * Gets the active ConnectionFilter instance or
+     * returns null if no filter is set.
+     *
+     * @return the managers ConnectionFilter.
+     */
+    public ConnectionFilter getConnectionFilter() {
+        return connectionFilter;
+    }//getConnectionFilter
+
+    /**
+     * Set a connection filter for this
+     * ConnectionManager instance. The filter is used to handle
+     * IP level allow/deny of incoming connections.
+     *
+     * @param filter ConnectionFilter instance.
+     */
+    public void setConnectionFilter(ConnectionFilter filter) {
+        connectionFilter = filter;
+    }//setConnectionFilter
+
+    /**
+     * Returns the number of open connections.
+     * @return the number of open connections as <tt>int</tt>.
+     */
+    public int openConnectionCount() {
+        return openConnections.size();
+    }//openConnectionCount
+
+    /**
+     * Returns the {@link Connection} at the given index.
+     * @param idx
+     * @return
+     */
+    public Connection getConnection(int idx) {
+        synchronized (openConnections) {
+            return openConnections.get(idx);
+        }
+    }//getConnection
+
+    /**
+     * Get all {@link Connection} instances with the given
+     * <tt>InetAddress</tt>.
+     *
+     * @return all {@link Connection} instances with the given
+     *         <tt>InetAddress</tt>.
+     */
+    public Connection[] getConnectionsByAdddress(InetAddress addr) {
+        ArrayList<Connection> l = new ArrayList<Connection>();
+        synchronized (openConnections) {
+            for (Connection connection : openConnections) {
+                if (connection.getConnectionData().getInetAddress().equals(addr)) {
+                    l.add(connection);
+                }
+            }
+        }
+        Connection[] conns = new Connection[l.size()];
+        return l.toArray(conns);
+    }//getConnectionsByAddress
+
+    /**
+     * Starts this <tt>ConnectionManager</tt>.
+     */
+    public void start() {
+        thread = new Thread(this);
+        thread.start();
+    }//start
+
+    /**
+     * Stops this <tt>ConnectionManager</tt>.
+     */
+    public void stop() {
+        LOG.log(Level.FINE, "stop()::" + this.toString());
+        stopping = true;
+        //wait for thread to die
+        try {
+            if (thread != null) {
+                thread.join();
+            }
+        } catch (InterruptedException iex) {
+            LOG.log(Level.SEVERE, "stop()", iex);
+        }
+        synchronized (openConnections) {
+            for (Connection tc : openConnections) {
+                try {
+                    //maybe write a disgrace to the socket?
+                    tc.close();
+                } catch (Exception exc) {
+                    LOG.log(Level.SEVERE, "stop()", exc);
+                }
+            }
+            openConnections.clear();
+        }
+        LOG.log(Level.FINE, "stop():: Stopped " + this.toString());
+    }//stop
+
+    /**
+     * Method that that tries to connect an incoming request.
+     * Properly  queueing.
+     *
+     * @param insock Socket thats representing the incoming connection.
+     */
+    public void makeConnection(Socket insock) {
+        LOG.log(Level.FINE, "makeConnection()::" + insock.toString());
+        if (connectionFilter == null || connectionFilter.isAllowed(insock.getInetAddress())) {
+            //we create the connection data object at this point to
+            //store certain information there.
+            ConnectionData newCD = new ConnectionData(insock, this);
+            newCD.setLoginShell(loginShell);
+            newCD.setLineMode(lineMode);
+            if (openConnections.size() < maxConnections) {
+                //create a new Connection instance
+                Connection con = createConnection(threadGroup, newCD);
+                //log the newly created connection
+                Object[] args = {openConnections.size() + 1};
+                LOG.info(MessageFormat.format("connection #{0,number,integer} made.", args));
+                //register it for being managed
+                synchronized (openConnections) {
+                    openConnections.add(con);
+                }
+                //start it
+                con.start();
+            }
+        } else {
+            LOG.info("makeConnection():: Active Filter blocked incoming connection.");
+            try {
+                insock.close();
+            } catch (IOException ex) {
+                //do nothing or log.
+            }
+        }
+    }//makeConnection
+
+    protected abstract Connection createConnection(ThreadGroup threadGroup, ConnectionData newCD);
+
+    /**
+     * Periodically does following work:
+     * <ul>
+     * <li> cleaning up died connections.
+     * <li> checking managed connections if they are working properly.
+     * <li> checking the open connections.
+     * </ul>
+     */
+    public void run() {
+        //housekeep connections
+        try {
+            do {
+                //clean up and close all broken connections
+                //cleanupBroken();
+                //clean up closed connections
+                cleanupClosed();
+                //check all active connections
+                checkOpenConnections();
+                //sleep interval
+                Thread.sleep(housekeepingInterval);
+            } while (!stopping);
+
+        } catch (Exception e) {
+            LOG.log(Level.SEVERE, "run()", e);
+        }
+        LOG.log(Level.FINE, "run():: Ran out " + this.toString());
+    }//run
+
+    /*
+    private void cleanupBroken() {
+      //cleanup loop
+      while (!m_BrokenConnections.isEmpty()) {
+        Connection nextOne = (Connection) m_BrokenConnections.pop();
+        log.info("cleanupBroken():: Closing broken connection " + nextOne.toString());
+        //fire logoff event for shell site cleanup , beware could hog the daemon thread
+        nextOne.processConnectionEvent(new ConnectionEvent(nextOne, ConnectionEvent.CONNECTION_BROKEN));
+        //close the connection, will be automatically registered as closed
+        nextOne.close();
+      }
+    }//cleanupBroken
+    */
+    private void cleanupClosed() {
+        if (stopping) {
+            return;
+        }
+        //cleanup loop
+        while (!closedConnections.isEmpty()) {
+            Connection nextOne = closedConnections.pop();
+            LOG.info("cleanupClosed():: Removing closed connection " + nextOne.toString());
+            synchronized (openConnections) {
+                openConnections.remove(nextOne);
+            }
+        }
+    }//cleanupBroken
+
+    private void checkOpenConnections() {
+        if (stopping) {
+            return;
+        }
+        //do routine checks on active connections
+        synchronized (openConnections) {
+            for (Connection conn : openConnections) {
+                ConnectionData cd = conn.getConnectionData();
+                //check if it is dead and remove it.
+                if (!conn.isActive()) {
+                    registerClosedConnection(conn);
+                    continue;
+                }
+                /* Timeouts check */
+                //first we caculate the inactivity time
+                long inactivity = System.currentTimeMillis() - cd.getLastActivity();
+                //now we check for warning and disconnection
+                if (inactivity > warningTimeout) {
+                    //..and for disconnect
+                    if (inactivity > (disconnectTimeout + warningTimeout)) {
+                        //this connection needs to be disconnected :)
+                        LOG.log(Level.FINE, "checkOpenConnections():" + conn.toString() + " exceeded total timeout.");
+                        //fire logoff event for shell site cleanup , beware could hog the daemon thread
+                        conn.processConnectionEvent(new ConnectionEvent(conn, ConnectionEvent.Type.CONNECTION_TIMEDOUT));
+                        //conn.close();
+                    } else {
+                        //this connection needs to be warned :)
+                        if (!cd.isWarned()) {
+                            LOG.log(Level.FINE, "checkOpenConnections():" + conn.toString() + " exceeded warning timeout.");
+                            cd.setWarned(true);
+                            //warning event is fired but beware this could hog the daemon thread!!
+                            conn.processConnectionEvent(new ConnectionEvent(conn, ConnectionEvent.Type.CONNECTION_IDLE));
+                        }
+                    }
+                }
+            }
+            /* end Timeouts check */
+        }
+    }//checkConnections
+
+    public void registerClosedConnection(Connection con) {
+        if (stopping) {
+            return;
+        }
+        if (!closedConnections.contains(con)) {
+            LOG.log(Level.FINE, "registerClosedConnection()::" + con.toString());
+            closedConnections.push(con);
+        }
+    }//unregister
+
+    public int getDisconnectTimeout() {
+        return disconnectTimeout;
+    }
+
+    public void setDisconnectTimeout(int disconnectTimeout) {
+        this.disconnectTimeout = disconnectTimeout;
+    }
+
+    public int getHousekeepingInterval() {
+        return housekeepingInterval;
+    }
+
+    public void setHousekeepingInterval(int housekeepingInterval) {
+        this.housekeepingInterval = housekeepingInterval;
+    }
+
+    public boolean isLineMode() {
+        return lineMode;
+    }
+
+    public void setLineMode(boolean lineMode) {
+        this.lineMode = lineMode;
+    }
+
+    public String getLoginShell() {
+        return loginShell;
+    }
+
+    public void setLoginShell(String loginShell) {
+        this.loginShell = loginShell;
+    }
+
+    public int getMaxConnections() {
+        return maxConnections;
+    }
+
+    public void setMaxConnections(int maxConnections) {
+        this.maxConnections = maxConnections;
+    }
+
+    public int getWarningTimeout() {
+        return warningTimeout;
+    }
+
+    public void setWarningTimeout(int warningTimeout) {
+        this.warningTimeout = warningTimeout;
+    }
+
+}//class ConnectionManager
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/PortListener.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/PortListener.java
new file mode 100644
index 0000000..1682db0
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/PortListener.java
@@ -0,0 +1,222 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/***
+ * Java TelnetD library (embeddable telnet daemon)
+ * Copyright (c) 2000-2005 Dieter Wimberger
+ * All rights reserved.
+ * <p/>
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * <p/>
+ * Neither the name of the author nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ * <p/>
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS ``AS
+ * IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ ***/
+
+package org.apache.felix.gogo.jline.telnet;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketException;
+import java.text.MessageFormat;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Class that implements a <tt>PortListener</tt>.<br>
+ * If available, it accepts incoming connections and passes them
+ * to an associated <tt>ConnectionManager</tt>.
+ *
+ * @author Dieter Wimberger
+ * @version 2.0 (16/07/2006)
+ * @see ConnectionManager
+ */
+public class PortListener
+        implements Runnable {
+
+    private static final Logger LOG = Logger.getLogger(PortListener.class.getName());
+    private static final String logmsg =
+            "Listening to Port {0,number,integer} with a connectivity queue size of {1,number,integer}.";
+    private String name;
+    private int port;                                        //port number running on
+    private int floodProtection;                        //flooding protection
+    private ServerSocket serverSocket = null; //server socket
+    private Thread thread;
+    private ConnectionManager connectionManager;    //connection management thread
+    private boolean stopping = false;
+    private boolean available;                    //Flag for availability
+
+    /**
+     * Constructs a PortListener instance.<br>
+     *
+     * @param port      int that specifies the port number of the server socket.
+     * @param floodprot that specifies the server socket queue size.
+     */
+    public PortListener(String name, int port, int floodprot) {
+        this.name = name;
+        available = false;
+        this.port = port;
+        floodProtection = floodprot;
+    }//constructor
+
+    /**
+     * Returns the name of this <tt>PortListener</tt>.
+     *
+     * @return the name as <tt>String</tt>.
+     */
+    public String getName() {
+        return name;
+    }//getName
+
+    /**
+     * Tests if this <tt>PortListener</tt> is available.
+     *
+     * @return true if available, false otherwise.
+     */
+    public boolean isAvailable() {
+        return available;
+    }//isAvailable
+
+    /**
+     * Sets the availability flag of this <tt>PortListener</tt>.
+     *
+     * @param b true if to be available, false otherwise.
+     */
+    public void setAvailable(boolean b) {
+        available = b;
+    }//setAvailable
+
+    /**
+     * Starts this <tt>PortListener</tt>.
+     */
+    public void start() {
+        LOG.log(Level.FINE, "start()");
+        thread = new Thread(this);
+        thread.start();
+        available = true;
+    }//start
+
+    /**
+     * Stops this <tt>PortListener</tt>, and returns
+     * when everything was stopped successfully.
+     */
+    public void stop() {
+        LOG.log(Level.FINE, "stop()::" + this.toString());
+        //flag stop
+        stopping = true;
+        available = false;
+        //take down all connections
+        connectionManager.stop();
+
+        //close server socket
+        try {
+            serverSocket.close();
+        } catch (IOException ex) {
+            LOG.log(Level.SEVERE, "stop()", ex);
+        }
+
+        //wait for thread to die
+        try {
+            thread.join();
+        } catch (InterruptedException iex) {
+            LOG.log(Level.SEVERE, "stop()", iex);
+        }
+
+        LOG.info("stop()::Stopped " + this.toString());
+    }//stop
+
+    /**
+     * Listen constantly to a server socket and handles incoming connections
+     * through the associated {a:link ConnectionManager}.
+     *
+     * @see ConnectionManager
+     */
+    public void run() {
+        try {
+            /*
+                A server socket is opened with a connectivity queue of a size specified
+                in int floodProtection.  Concurrent login handling under normal circumstances
+                should be handled properly, but denial of service attacks via massive parallel
+                program logins should be prevented with this.
+            */
+            serverSocket = new ServerSocket(port, floodProtection);
+
+            //log entry
+            LOG.info(MessageFormat.format(logmsg, port, floodProtection));
+
+            do {
+                try {
+                    Socket s = serverSocket.accept();
+                    if (available) {
+                        connectionManager.makeConnection(s);
+                    } else {
+                        //just shut down the socket
+                        s.close();
+                    }
+                } catch (SocketException ex) {
+                    if (stopping) {
+                        //server socket was closed blocked in accept
+                        LOG.log(Level.FINE, "run(): ServerSocket closed by stop()");
+                    } else {
+                        LOG.log(Level.SEVERE, "run()", ex);
+                    }
+                }
+            } while (!stopping);
+
+        } catch (IOException e) {
+            LOG.log(Level.SEVERE, "run()", e);
+        }
+        LOG.log(Level.FINE, "run(): returning.");
+    }//run
+
+    /**
+     * Returns reference to ConnectionManager instance associated
+     * with the PortListener.
+     *
+     * @return the associated ConnectionManager.
+     */
+    public ConnectionManager getConnectionManager() {
+        return connectionManager;
+    }//getConnectionManager
+
+    public void setConnectionManager(ConnectionManager connectionManager) {
+        this.connectionManager = connectionManager;
+    }
+
+}//class PortListener
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/Telnet.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/Telnet.java
new file mode 100644
index 0000000..7bba28b
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/Telnet.java
@@ -0,0 +1,202 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.jline.telnet;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.util.List;
+
+import org.apache.felix.gogo.jline.Shell;
+import org.apache.felix.gogo.jline.Shell.Context;
+import org.apache.felix.service.command.CommandProcessor;
+import org.apache.felix.service.command.CommandSession;
+import org.jline.builtins.Options;
+import org.jline.terminal.Size;
+import org.jline.terminal.Terminal;
+import org.jline.terminal.Terminal.Signal;
+import org.jline.terminal.TerminalBuilder;
+
+/*
+ * a very simple Telnet server.
+ * real remote access should be via ssh.
+ */
+public class Telnet {
+    public static final String[] functions = {"telnetd"};
+
+    private static final int defaultPort = 2019;
+    private final CommandProcessor processor;
+    private PortListener portListener;
+    private int port;
+    private String ip;
+
+    public Telnet(CommandProcessor procesor) {
+        this.processor = procesor;
+    }
+
+    public void telnetd(CommandSession session, String[] argv) throws IOException {
+        final String[] usage = {"telnetd - start simple telnet server",
+                "Usage: telnetd [-i ip] [-p port] start | stop | status",
+                "  -i --ip=INTERFACE        listen interface (default=127.0.0.1)",
+                "  -p --port=PORT           listen port (default=" + defaultPort + ")",
+                "  -? --help                show help"};
+
+        Options opt = Options.compile(usage).parse(argv);
+        List<String> args = opt.args();
+
+        if (opt.isSet("help") || args.isEmpty()) {
+            opt.usage(System.err);
+            return;
+        }
+
+        String command = args.get(0);
+
+        if ("start".equals(command)) {
+            if (portListener != null) {
+                throw new IllegalStateException("telnetd is already running on port " + port);
+            }
+            ip = opt.get("ip");
+            port = opt.getNumber("port");
+            start(session);
+            status();
+        } else if ("stop".equals(command)) {
+            if (portListener == null) {
+                throw new IllegalStateException("telnetd is not running.");
+            }
+            stop();
+        } else if ("status".equals(command)) {
+            status();
+        } else {
+            throw opt.usageError("bad command: " + command);
+        }
+    }
+
+    private void status() {
+        if (portListener != null) {
+            System.out.println("telnetd is running on " + ip + ":" + port);
+        } else {
+            System.out.println("telnetd is not running.");
+        }
+    }
+
+    private void start(CommandSession session) throws IOException {
+        ConnectionManager connectionManager = new ConnectionManager(1000, 5 * 60 * 1000, 5 * 60 * 1000, 60 * 1000, null, null, false) {
+            @Override
+            protected Connection createConnection(ThreadGroup threadGroup, ConnectionData newCD) {
+                return new Connection(threadGroup, newCD) {
+                    TelnetIO telnetIO;
+
+                    @Override
+                    protected void doRun() throws Exception {
+                        telnetIO = new TelnetIO();
+                        telnetIO.setConnection(this);
+                        telnetIO.initIO();
+
+                        InputStream in = new InputStream() {
+                            @Override
+                            public int read() throws IOException {
+                                return telnetIO.read();
+                            }
+                            @Override
+                            public int read(byte[] b, int off, int len) throws IOException {
+                                int r = read();
+                                if (r >= 0) {
+                                    b[off] = (byte) r;
+                                    return 1;
+                                } else {
+                                    return -1;
+                                }
+                            }
+                        };
+                        PrintStream out = new PrintStream(new OutputStream() {
+                            @Override
+                            public void write(int b) throws IOException {
+                                telnetIO.write(b);
+                            }
+                            @Override
+                            public void flush() throws IOException {
+                                telnetIO.flush();
+                            }
+                        });
+                        Terminal terminal = TerminalBuilder.builder()
+                                .type(getConnectionData().getNegotiatedTerminalType().toLowerCase())
+                                .streams(in, out)
+                                .system(false)
+                                .name("telnet")
+                                .build();
+                        terminal.setSize(new Size(getConnectionData().getTerminalColumns(), getConnectionData().getTerminalRows()));
+                        terminal.setAttributes(Shell.getTerminal(session).getAttributes());
+                        addConnectionListener(new ConnectionListener() {
+                            @Override
+                            public void connectionIdle(ConnectionEvent ce) {
+                            }
+
+                            @Override
+                            public void connectionTimedOut(ConnectionEvent ce) {
+                            }
+
+                            @Override
+                            public void connectionLogoutRequest(ConnectionEvent ce) {
+                            }
+
+                            @Override
+                            public void connectionSentBreak(ConnectionEvent ce) {
+                            }
+
+                            @Override
+                            public void connectionTerminalGeometryChanged(ConnectionEvent ce) {
+                                terminal.setSize(new Size(getConnectionData().getTerminalColumns(), getConnectionData().getTerminalRows()));
+                                terminal.raise(Signal.WINCH);
+                            }
+                        });
+                        PrintStream pout = new PrintStream(terminal.output());
+                        CommandSession session = processor.createSession(terminal.input(), pout, pout);
+                        Context context = new Context() {
+                            @Override
+                            public String getProperty(String name) {
+                                return System.getProperty(name);
+                            }
+                            @Override
+                            public void exit() throws Exception {
+                                close();
+                            }
+                        };
+                        new Shell(context, processor, terminal).gosh(session, new String[]{"--login"});
+                    }
+
+                    @Override
+                    protected void doClose() throws Exception {
+                        telnetIO.closeOutput();
+                        telnetIO.closeInput();
+                    }
+                };
+            }
+        };
+        portListener = new PortListener("gogo", port, 10);
+        portListener.setConnectionManager(connectionManager);
+        portListener.start();
+    }
+
+    private void stop() throws IOException {
+        portListener.stop();
+        portListener = null;
+    }
+
+}
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/TelnetIO.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/TelnetIO.java
new file mode 100644
index 0000000..540db98
--- /dev/null
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/TelnetIO.java
@@ -0,0 +1,1548 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/***
+ * Java TelnetD library (embeddable telnet daemon)
+ * Copyright (c) 2000-2005 Dieter Wimberger
+ * All rights reserved.
+ * <p/>
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * <p/>
+ * Neither the name of the author nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ * <p/>
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS ``AS
+ * IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ ***/
+
+package org.apache.felix.gogo.jline.telnet;
+
+import java.io.BufferedOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.felix.gogo.jline.telnet.ConnectionEvent.Type;
+
+/**
+ * Class that represents the TelnetIO implementation. It contains
+ * an inner IACHandler class to handle the telnet protocol level
+ * communication.
+ * <p/>
+ * Although supposed to work full-duplex, we only process the telnet protocol
+ * layer communication in case of reading requests from the higher levels.
+ * This is the only way to meet the one thread per connection requirement.
+ * </p>
+ * <p/>
+ * The output is done via byte-oriented streams, definately suitable for the
+ * telnet protocol. The format of the  output is UTF-8 (Unicode), which is a
+ * standard and supported by any telnet client, including the ones included
+ * in Microsoft OS's.
+ * </p>
+ * <em>Notes:</em>
+ * <ul>
+ * <li>The underlying output is buffered, to ensure that all bytes written
+ * are send, the flush() method has to be called.
+ * <li>This low-level routines ensure nice multithreading behaviour on I/O.
+ * Neither large outputs, nor input sequences excuted by the connection thread
+ * can hog the system.
+ * </ul>
+ *
+ * @author Dieter Wimberger
+ * @version 2.0 (16/07/2006)
+ */
+public class TelnetIO {
+
+    /**
+     * Interpret As Command
+     */
+    protected static final int IAC = 255;
+    /**
+     * Go Ahead <BR> Newer Telnets do not make use of this option
+     * that allows a specific communication mode.
+     */
+    protected static final int GA = 249;
+    /**
+     * Negotiation: Will do option
+     */
+    protected static final int WILL = 251;
+    /**
+     * Negotiation: Wont do option
+     */
+    protected static final int WONT = 252;
+    /**
+     * Negotiation: Do option
+     */
+    protected static final int DO = 253;
+    /**
+     * Negotiation:  Dont do option
+     */
+    protected static final int DONT = 254;
+    /**
+     * Marks start of a subnegotiation.
+     */
+    protected static final int SB = 250;
+    /**
+     * Marks end of subnegotiation.
+     */
+    protected static final int SE = 240;
+    /**
+     * No operation
+     */
+    protected static final int NOP = 241;
+    /**
+     * Data mark its the data part of a SYNCH which helps to clean up the buffers between
+     * Telnet Server &lt;-&gt; Telnet Client. <BR>
+     * It should work like this we send a TCP urgent package and &lt;IAC&gt; &lt;DM&gt; the receiver
+     * should get the urgent package (SYNCH) and just discard everything until he receives
+     * our &lt;IAC&gt; &lt;DM&gt;.<BR>
+     * <EM>Remark</EM>:
+     * <OL>
+     * <LI>can we send a TCP urgent package?
+     * <LI>can we make use of the thing at all?
+     * </OL>
+     */
+    protected static final int DM = 242;
+    /**
+     * Break
+     */
+    protected static final int BRK = 243;
+    /**
+     * Interrupt Process
+     */
+    protected static final int IP = 244;
+    /**
+     * Abort Output
+     */
+    protected static final int AO = 245;
+
+    /**** Implementation of OutputStream ****************************************************/
+    /**
+     * Are You There
+     */
+    protected static final int AYT = 246;
+    /**
+     * Erase Char
+     */
+    protected static final int EC = 247;
+    /**
+     * Erase Line
+     */
+    protected static final int EL = 248;
+    /**
+     * Telnet Option: ECHO
+     */
+    protected static final int ECHO = 1;
+    /**
+     * Telnet Option: SUPress Go Ahead<br>
+     * This will be negotiated, all new telnet protocol implementations are
+     * recommended to do this.
+     */
+    protected static final int SUPGA = 3;
+    /**
+     * Telnet Option: Negotiate About Window Size<br>
+     * <ul>
+     * <li>Server request is IAC DO NAWS
+     * <li>Client response contains subnegotiation with data (columns, rows).
+     * </ul>
+     */
+    protected static final int NAWS = 31;
+    /**
+     * Telnet Option: Terminal TYPE <br>
+     * <ul>
+     * <li>Server request contains subnegotiation SEND
+     * <li>Client response contains subnegotiation with data IS,terminal type string
+     * </ul>
+     */
+    protected static final int TTYPE = 24;
+    /**
+     * TTYPE subnegotiation: IS
+     */
+    protected static final int IS = 0;
+    /**
+     * TTYPE subnegotiation: SEND
+     */
+    protected static final int SEND = 1;
+
+    /**** End implementation of OutputStream ***********************************************/
+
+
+    /**** Implementation of InputStream ****************************************************/
+    /**
+     * Telnet Option: Logout<br>
+     * This allows nice goodbye to time-outed or unwanted clients.
+     */
+    protected static final int LOGOUT = 18;
+    /**
+     * Telnet Option: Linemode
+     * <p/>
+     * The infamous line mode option.
+     */
+    protected static final int LINEMODE = 34;
+    protected static final int LM_MODE = 1;
+    protected static final int LM_EDIT = 1;
+    protected static final int LM_TRAPSIG = 2;
+
+    /**** Implementation of InputStream ****************************************************/
+
+
+    /****
+     * Following methods implement init/request/answer procedures for telnet
+     * protocol level communication.
+     */
+    protected static final int LM_MODEACK = 4;
+    protected static final int LM_FORWARDMASK = 2;
+    protected static final int LM_SLC = 3;
+    protected static final int LM_SLC_NOSUPPORT = 0;
+    protected static final int LM_SLC_DEFAULT = 3;
+
+
+    /**** End telnet protocol level communication methods *******************************/
+    protected static final int LM_SLC_VALUE = 2;
+
+
+    /** Constants declaration ***********************************************/
+
+//Telnet Protocoll Constants
+    protected static final int LM_SLC_CANTCHANGE = 1;
+    protected static final int LM_SLC_LEVELBITS = 3;
+    protected static final int LM_SLC_ACK = 128;
+    protected static final int LM_SLC_FLUSHIN = 64;
+    protected static final int LM_SLC_FLUSHOUT = 32;
+    protected static final int LM_SLC_SYNCH = 1;
+    protected static final int LM_SLC_BRK = 2;
+    protected static final int LM_SLC_IP = 3;
+    protected static final int LM_SLC_AO = 4;
+    protected static final int LM_SLC_AYT = 5;
+    protected static final int LM_SLC_EOR = 6;
+
+    /**
+     * The following implement the NVT (network virtual terminal) which offers the concept
+     * of a simple "printer". They are the basical meanings of control possibilities
+     * on a standard telnet implementation.
+     */
+    protected static final int LM_SLC_ABORT = 7;
+    protected static final int LM_SLC_EOF = 8;
+    protected static final int LM_SLC_SUSP = 9;
+    /**
+     * Telnet Option: Environment
+     */
+    protected static final int NEWENV = 39;
+    protected static final int NE_INFO = 2;
+
+    /**
+     * The following are constants for supported options,
+     * which can be negotiated based upon the telnet protocol
+     * specification.
+     */
+    protected static final int NE_VAR = 0;
+    protected static final int NE_VALUE = 1;
+
+    /**
+     * The following options are options for which we also support subnegotiation
+     * based upon the telnet protocol specification.
+     */
+    protected static final int NE_ESC = 2;
+    protected static final int NE_USERVAR = 3;
+    protected static final int NE_VAR_OK = 2;
+    protected static final int NE_VAR_DEFINED = 1;
+    protected static final int NE_VAR_DEFINED_EMPTY = 0;
+    protected static final int NE_VAR_UNDEFINED = -1;
+    protected static final int NE_IN_ERROR = -2;
+    protected static final int NE_IN_END = -3;
+    protected static final int NE_VAR_NAME_MAXLENGTH = 50;
+    protected static final int NE_VAR_VALUE_MAXLENGTH = 1000;
+    /**
+     * Unused
+     */
+    protected static final int EXT_ASCII = 17;        //Defines Extended ASCII
+    protected static final int SEND_LOC = 23;        //Defines Send Location
+    protected static final int AUTHENTICATION = 37;    //Defines Authentication
+    protected static final int ENCRYPT = 38;            //Defines Encryption
+    private static final Logger LOG = Logger.getLogger(TelnetIO.class.getName());
+    /**
+     * Window Size Constants
+     */
+    private static final int SMALLEST_BELIEVABLE_WIDTH = 20;
+    private static final int SMALLEST_BELIEVABLE_HEIGHT = 6;
+    private static final int DEFAULT_WIDTH = 80;
+    private static final int DEFAULT_HEIGHT = 25;
+    private Connection connection;                    //a reference to the connection this instance works for
+    private ConnectionData connectionData;            //holds all important information of the connection
+    private DataOutputStream out;                    //the byte oriented outputstream
+    private DataInputStream in;                        //the byte oriented input stream
+    //Aggregations
+    private IACHandler iacHandler;                    //holds a reference to the aggregated IACHandler
+    //Members
+    private InetAddress localAddress;                //address of the host the telnetd is running on
+    private boolean noIac = false;                    //describes if IAC was found and if its just processed
+    private boolean initializing;
+    private boolean crFlag;
+    /**
+     * Creates a TelnetIO object for the given connection.<br>
+     * Input- and OutputStreams are properly set and the primary telnet
+     * protocol initialization is carried out by the inner IACHandler class.<BR>
+     */
+    public TelnetIO() {
+    }//constructor
+
+    public void initIO() throws IOException {
+        //we make an instance of our inner class
+        iacHandler = new IACHandler();
+        //we setup underlying byte oriented streams
+        in = new DataInputStream(connectionData.getSocket().getInputStream());
+        out = new DataOutputStream(new BufferedOutputStream(connectionData.getSocket().getOutputStream()));
+
+        //we save the local address (necessary?)
+        localAddress = connectionData.getSocket().getLocalAddress();
+        crFlag = false;
+        //bootstrap telnet communication
+        initTelnetCommunication();
+    }//initIO
+
+    public void setConnection(Connection con) {
+        connection = con;
+        connectionData = connection.getConnectionData();
+    }//setConnection
+
+    /**
+     * Method to output a byte. Ensures that CR(\r) is never send
+     * alone,but CRLF(\r\n), which is a rule of the telnet protocol.
+     *
+     * @param b Byte to be written.
+     */
+    public void write(byte b) throws IOException {
+        //ensure CRLF(\r\n) is written for LF(\n) to adhere
+        //to the telnet protocol.
+        if (!crFlag && b == 10) {
+            out.write(13);
+        }
+
+        out.write(b);
+
+        if (b == 13) {
+            crFlag = true;
+        } else {
+            crFlag = false;
+        }
+    }//write(byte)
+
+    /**
+     * Method to output an int.
+     *
+     * @param i Integer to be written.
+     */
+    public void write(int i)
+            throws IOException {
+        write((byte) i);
+    }//write(int)
+
+    /**
+     * Method to write an array of bytes.
+     *
+     * @param sequence byte[] to be written.
+     */
+    public void write(byte[] sequence) throws IOException {
+        for (byte b : sequence) {
+            write(b);
+        }
+    }//write(byte[])
+
+    /**
+     * Method to output an array of int' s.
+     *
+     * @param sequence int [] to write
+     */
+    public void write(int[] sequence) throws IOException {
+        for (int i : sequence) {
+            write((byte) i);
+        }
+    }//write(int[])
+
+    /**
+     * Method to write a char.
+     *
+     * @param ch char to be written.
+     */
+    public void write(char ch) throws IOException {
+        write((byte) ch);
+    }//write(char)
+
+    /**
+     * Method to output a string.
+     *
+     * @param str String to be written.
+     */
+    public void write(String str) throws IOException {
+        write(str.getBytes());
+    }//write(String)
+
+    /**
+     * Method to flush all buffered output.
+     */
+    public void flush() throws IOException {
+        out.flush();
+    }//flush
+
+    /**
+     * Method to close the underlying output stream to free system resources.<br>
+     * Most likely only to be called by the ConnectionManager upon clean up of
+     * connections that ended or died.
+     */
+    public void closeOutput() {
+
+        try {
+            //sends telnetprotocol logout acknowledgement
+            write(IAC);
+            write(DO);
+            write(LOGOUT);
+            //and now close underlying outputstream
+
+            out.close();
+        } catch (IOException ex) {
+            LOG.log(Level.SEVERE, "closeOutput()", ex);
+            //handle?
+        }
+    }//close
+
+    private void rawWrite(int i) throws IOException {
+        out.write(i);
+    }//rawWrite
+
+    /**
+     * Method to read a byte from the InputStream.
+     * Invokes the IACHandler upon IAC (Byte=255).
+     *
+     * @return int read from stream.
+     */
+    public int read() throws IOException {
+        int c = rawread();
+        //if (c == 255) {
+        noIac = false;
+        while ((c == 255) && (!noIac)) {
+            /**
+             * Read next, and invoke
+             * the IACHandler he is taking care of the rest. Or at least he should :)
+             */
+            c = rawread();
+            if (c != 255) {
+                iacHandler.handleC(c);
+                c = rawread();
+            } else {
+                noIac = true;
+            }
+        }
+        return stripCRSeq(c);
+    }//read
+
+    /**
+     * Method to close the underlying inputstream to free system resources.<br>
+     * Most likely only to be called by the ConnectionManager upon clean up of
+     * connections that ended or died.
+     */
+    public void closeInput() {
+        try {
+            in.close();
+        } catch (IOException e) {
+            //handle?
+        }
+    }//closeInput
+
+    /**
+     * This method reads an unsigned 16bit Integer from the stream,
+     * its here for getting the NAWS Data Values for height and width.
+     */
+    private int read16int() throws IOException {
+        int c = in.readUnsignedShort();
+        return c;
+    }//read16int
+
+    /**
+     * The following options are options which might be of interest, but are not
+     * yet implemented or in use.
+     */
+
+    /**
+     * Method to read a raw byte from the InputStream.<br>
+     * Telnet protocol layer communication is filtered and processed here.
+     *
+     * @return int read from stream.
+     */
+    private int rawread() throws IOException {
+        int b = 0;
+
+        //try {
+        b = in.readUnsignedByte();
+        connectionData.activity();
+        return b;
+    }//rawread
+
+    /**
+     * Checks for the telnet protocol specified  CR followed by NULL or LF<BR>
+     * Subsequently reads for the next byte and forwards
+     * only a ENTER represented by LF internally.
+     */
+    private int stripCRSeq(int input) throws IOException {
+        if (input == 13) {
+            rawread();
+            return 10;
+        }
+        return input;
+    }//stripCRSeq
+
+    /**
+     * Method that initializes the telnet communication layer.
+     */
+    private void initTelnetCommunication() {
+
+        initializing = true;
+        try {
+            //start out, some clients just wait
+            if (connectionData.isLineMode()) {
+                iacHandler.doLineModeInit();
+                LOG.log(Level.FINE, "Line mode initialized.");
+            } else {
+                iacHandler.doCharacterModeInit();
+                LOG.log(Level.FINE, "Character mode initialized.");
+            }
+            //open for a defined timeout so we read incoming negotiation
+            connectionData.getSocket().setSoTimeout(1000);
+            read();
+
+        } catch (Exception e) {
+            //handle properly
+            //log.error("initTelnetCommunication()",e);
+        } finally {
+            //this is important, dont ask me why :)
+            try {
+                connectionData.getSocket().setSoTimeout(0);
+            } catch (Exception ex) {
+                LOG.log(Level.SEVERE, "initTelnetCommunication()", ex);
+            }
+        }
+        initializing = false;
+    }//initTelnetCommunication
+
+    /**
+     * Method that represents the answer to the
+     * AreYouThere question of the telnet protocol specification
+     * <p/>
+     * Output of the String [HostAdress:Yes]
+     */
+    private void IamHere() {
+        try {
+            write("[" + localAddress.toString() + ":Yes]");
+            flush();
+        } catch (Exception ex) {
+            LOG.log(Level.SEVERE, "IamHere()", ex);
+        }
+    }//IamHere
+
+    /**
+     * Network virtual terminal break.
+     */
+    private void nvtBreak() {
+        connection.processConnectionEvent(new ConnectionEvent(connection, ConnectionEvent.Type.CONNECTION_BREAK));
+    }//nvtBreak
+
+    /**
+     * Method that checks reported terminal sizes and sets the
+     * asserted values in the ConnectionData instance associated with
+     * the connection.
+     *
+     * @param width  Integer that represents the Window width in chars
+     * @param height Integer that represents the Window height in chars
+     */
+    private void setTerminalGeometry(int width, int height) {
+        if (width < SMALLEST_BELIEVABLE_WIDTH) {
+            width = DEFAULT_WIDTH;
+        }
+        if (height < SMALLEST_BELIEVABLE_HEIGHT) {
+            height = DEFAULT_HEIGHT;
+        }
+        //DEBUG: write("[New Window Size " + window_width + "x" + window_height + "]");
+        connectionData.setTerminalGeometry(width, height);
+        connection.processConnectionEvent(new ConnectionEvent(connection,
+                Type.CONNECTION_TERMINAL_GEOMETRY_CHANGED));
+    }//setTerminalGeometry
+
+    public void setEcho(boolean b) {
+    }//setEcho
+
+    /**
+     * An inner class for handling incoming option negotiations implementing the <B>telnet protocol</B>
+     * specification based upon following Standards and RFCs:
+     * <OL>
+     * <LI><A HREF="ftp://ds.internic.net/rfc/rfc854.txt">854 Telnet Protocol Specification</A>
+     * <LI><A HREF="ftp://ds.internic.net/rfc/rfc855.txt">855 Telnet Option Specifications</A>
+     * <LI><A HREF="ftp://ds.internic.net/rfc/rfc857.txt">857 Telnet Echo Option</A>
+     * <LI><A HREF="ftp://ds.internic.net/rfc/rfc858.txt">858 Telnet Supress Go Ahead Option</A>
+     * <LI><A HREF="ftp://ds.internic.net/rfc/rfc727.txt">727 Telnet Logout Option</A>
+     * <LI><A HREF="ftp://ds.internic.net/rfc/rfc1073.txt">1073 Telnet Window Size Option</A>
+     * <LI><A HREF="ftp://ds.internic.net/rfc/rfc1091.txt">1091 Telnet Terminal-Type Option</A>
+     * </OL>
+     * <p/>
+     * Furthermore there are some more, which helped to solve problems, or might be important
+     * for future enhancements:<BR>
+     * <A HREF="ftp://ds.internic.net/rfc/rfc1143.txt">1143 The Q Method of Implementing Option Negotiation</A><BR>
+     * <A HREF="ftp://ds.internic.net/rfc/rfc1416.txt">1416 Telnet Authentication Option</A><BR>
+     * <p/>
+     * After an intense study of the available material (mainly cryptical written RFCs,
+     * a telnet client implementation for the macintosh based upon NCSA telnet, and a server side
+     * implementation called key, a mud-like system completely written in Java) I realized
+     * the problems we are facing regarding to the telnet protocol:
+     * <OL>
+     * <LI> a minimal spread of invented options, which means there are a lot of invented options,
+     * but rarely they made it through to become a standard.
+     * <LI> Dependency on a special type of implementation is dangerous in our case.
+     * We are no kind of host that offers the user to run several processes at once,
+     * a BBS is intended to be a single process the user is interacting with.
+     * <LI> The <B>LAMER</B> has to be expected to log in with the standard Microsoft telnet
+     * implementation. This means forget every nice feature and most of the almost-standards.
+     * <p/>
+     * </OL>
+     * <BR>
+     *
+     * @author Dieter Wimberger
+     * @version 1.1 16/06/1998
+     * <p/>
+     * <p/>
+     * <B>To-Do</B>:<UL>
+     * <LI>UNIX conform new style TTYPE negotiation. Setting a list and selecting from it...
+     * </UL>
+     */
+    class IACHandler {
+
+        /**
+         * Telnet readin buffer
+         * Here its implemented guys. Open your eyes upon this solution.
+         * The others take a one byte solution :)
+         */
+        private int[] buffer = new int[2];
+
+        /**
+         * DO_ECHO or not
+         */
+        private boolean DO_ECHO = false;
+
+        /**
+         * DO_SUPGA or not
+         */
+        private boolean DO_SUPGA = false;
+
+        /**
+         * DO_NAWS or not
+         */
+        private boolean DO_NAWS = false;
+
+        /**
+         * DO_TTYPE or not
+         */
+        private boolean DO_TTYPE = false;
+
+        /**
+         * DO_LINEMODE or not
+         */
+        private boolean DO_LINEMODE = false;
+
+        /**
+         * DO_NEWENV or not
+         */
+        private boolean DO_NEWENV = false;
+
+        /**
+         * Are we waiting for a DO reply?
+         */
+        private boolean WAIT_DO_REPLY_SUPGA = false;
+        private boolean WAIT_DO_REPLY_ECHO = false;
+        private boolean WAIT_DO_REPLY_NAWS = false;
+        private boolean WAIT_DO_REPLY_TTYPE = false;
+        private boolean WAIT_DO_REPLY_LINEMODE = false;
+        private boolean WAIT_LM_MODE_ACK = false;
+        private boolean WAIT_LM_DO_REPLY_FORWARDMASK = false;
+        private boolean WAIT_DO_REPLY_NEWENV = false;
+        private boolean WAIT_NE_SEND_REPLY = false;
+
+        /**
+         * Are we waiting for a WILL reply?
+         */
+        private boolean WAIT_WILL_REPLY_SUPGA = false;
+        private boolean WAIT_WILL_REPLY_ECHO = false;
+        private boolean WAIT_WILL_REPLY_NAWS = false;
+        private boolean WAIT_WILL_REPLY_TTYPE = false;
+
+
+        public void doCharacterModeInit() throws IOException {
+            sendCommand(WILL, ECHO, true);
+            sendCommand(DONT, ECHO, true); //necessary for some clients
+            sendCommand(DO, NAWS, true);
+            sendCommand(WILL, SUPGA, true);
+            sendCommand(DO, SUPGA, true);
+            sendCommand(DO, TTYPE, true);
+            sendCommand(DO, NEWENV, true); //environment variables
+        }//doCharacterModeInit
+
+        public void doLineModeInit() throws IOException {
+            sendCommand(DO, NAWS, true);
+            sendCommand(WILL, SUPGA, true);
+            sendCommand(DO, SUPGA, true);
+            sendCommand(DO, TTYPE, true);
+            sendCommand(DO, LINEMODE, true);
+            sendCommand(DO, NEWENV, true);
+        }//doLineModeInit
+
+
+        /**
+         * Method to handle a IAC that came in over the line.
+         *
+         * @param i (int)ed byte that followed the IAC
+         */
+        public void handleC(int i) throws IOException {
+            buffer[0] = i;
+            if (!parseTWO(buffer)) {
+                buffer[1] = rawread();
+                parse(buffer);
+            }
+            buffer[0] = 0;
+            buffer[1] = 0;
+        }//handleC
+
+        /**
+         * Method that parses for options with two characters.
+         *
+         * @param buf int [] that represents the first byte that followed the IAC first.
+         * @return true when it was a two byte command (IAC OPTIONBYTE)
+         */
+        private boolean parseTWO(int[] buf) {
+            switch (buf[0]) {
+                case IAC:
+                    //doubled IAC to escape 255 is handled within the
+                    //read method.
+                    break;
+                case AYT:
+                    IamHere();
+                    break;
+                case AO:
+                case IP:
+                case EL:
+                case EC:
+                case NOP:
+                    break;
+                case BRK:
+                    nvtBreak();
+                    break;
+                default:
+                    return false;
+            }
+            return true;
+        }//parseTWO
+
+        /**
+         * Method that parses further on for options.
+         *
+         * @param buf that represents the first two bytes that followed the IAC.
+         */
+        private void parse(int[] buf) throws IOException {
+            switch (buf[0]) {
+        /* First switch on the Negotiation Option */
+                case WILL:
+                    if (supported(buf[1]) && isEnabled(buf[1])) {
+                        ;// do nothing
+                    } else {
+                        if (waitDOreply(buf[1]) && supported(buf[1])) {
+                            enable(buf[1]);
+                            setWait(DO, buf[1], false);
+                        } else {
+                            if (supported(buf[1])) {
+                                sendCommand(DO, buf[1], false);
+                                enable(buf[1]);
+                            } else {
+                                sendCommand(DONT, buf[1], false);
+                            }
+                        }
+                    }
+                    break;
+                case WONT:
+                    if (waitDOreply(buf[1]) && supported(buf[1])) {
+                        setWait(DO, buf[1], false);
+                    } else {
+                        if (supported(buf[1]) && isEnabled(buf[1])) {
+                            // eanable() Method disables an Option that is already enabled
+                            enable(buf[1]);
+                        }
+                    }
+                    break;
+                case DO:
+                    if (supported(buf[1]) && isEnabled(buf[1])) {
+                        ; // do nothing
+                    } else {
+                        if (waitWILLreply(buf[1]) && supported(buf[1])) {
+                            enable(buf[1]);
+                            setWait(WILL, buf[1], false);
+                        } else {
+                            if (supported(buf[1])) {
+                                sendCommand(WILL, buf[1], false);
+                                enable(buf[1]);
+                            } else {
+                                sendCommand(WONT, buf[1], false);
+                            }
+                        }
+                    }
+                    break;
+                case DONT:
+                    if (waitWILLreply(buf[1]) && supported(buf[1])) {
+                        setWait(WILL, buf[1], false);
+                    } else {
+                        if (supported(buf[1]) && isEnabled(buf[1])) {
+                            // enable() Method disables an Option that is already enabled
+                            enable(buf[1]);
+                        }
+                    }
+                    break;
+
+          /* Now about other two byte IACs */
+                case DM:    //How do I implement a SYNCH signal?
+                    break;
+                case SB: //handle subnegotiations
+                    if ((supported(buf[1])) && (isEnabled(buf[1]))) {
+                        switch (buf[1]) {
+                            case NAWS:
+                                handleNAWS();
+                                break;
+                            case TTYPE:
+                                handleTTYPE();
+                                break;
+                            case LINEMODE:
+                                handleLINEMODE();
+                                break;
+                            case NEWENV:
+                                handleNEWENV();
+                                break;
+                            default:
+                                ;
+                        }
+                    } else {
+                        //do nothing
+                    }
+                    break;
+                default:
+                    ;
+            }//switch
+        }//parse
+
+        /**
+         * Method that reads a NawsSubnegotiation that ends up with a IAC SE
+         * If the measurements are unbelieveable it switches to the defaults.
+         */
+        private void handleNAWS() throws IOException {
+            int width = read16int();
+            if (width == 255) {
+                width = read16int(); //handle doubled 255 value;
+            }
+            int height = read16int();
+            if (height == 255) {
+                height = read16int(); //handle doubled 255 value;
+            }
+            skipToSE();
+            setTerminalGeometry(width, height);
+        }//handleNAWS
+
+        /**
+         * Method that reads a TTYPE Subnegotiation String that ends up with a IAC SE
+         * If no Terminal is valid, we switch to the dumb "none" terminal.
+         */
+        private void handleTTYPE() throws IOException {
+            String tmpstr = "";
+            // The next read should be 0 which is IS by the protocol
+            // specs. hmmm?
+            rawread(); //that should be the is :)
+            tmpstr = readIACSETerminatedString(40);
+            LOG.log(Level.FINE, "Reported terminal name " + tmpstr);
+            connectionData.setNegotiatedTerminalType(tmpstr);
+        }//handleTTYPE
+
+        /**
+         * Method that handles LINEMODE subnegotiation.
+         */
+        public void handleLINEMODE() throws IOException {
+            int c = rawread();
+            switch (c) {
+                case LM_MODE:
+                    handleLMMode();
+                    break;
+                case LM_SLC:
+                    handleLMSLC();
+                    break;
+                case WONT:
+                case WILL:
+                    handleLMForwardMask(c);
+                    break;
+                default:
+                    //skip to (including) SE
+                    skipToSE();
+            }
+        }//handleLINEMODE
+
+        public void handleLMMode() throws IOException {
+            //we sent the default which no client might deny
+            //so we only wait the ACK
+            if (WAIT_LM_MODE_ACK) {
+                int mask = rawread();
+                if (mask != (LM_EDIT | LM_TRAPSIG | LM_MODEACK)) {
+                    LOG.log(Level.FINE, "Client violates linemodeack sent: " + mask);
+                }
+                WAIT_LM_MODE_ACK = false;
+            }
+            skipToSE();
+        }//handleLMMode
+
+        public void handleLMSLC() throws IOException {
+            int[] triple = new int[3];
+            if (!readTriple(triple)) return;
+
+            //SLC will be initiated by the client
+            //case 1. client requests set
+            //LINEMODE SLC 0 SLC_DEFAULT 0
+            if ((triple[0] == 0) && (triple[1] == LM_SLC_DEFAULT) && (triple[2] == 0)) {
+                skipToSE();
+                //reply with SLC xxx SLC_DEFAULT 0
+                rawWrite(IAC);
+                rawWrite(SB);
+                rawWrite(LINEMODE);
+                rawWrite(LM_SLC);
+                //triples defaults for all
+                for (int i = 1; i < 12; i++) {
+                    rawWrite(i);
+                    rawWrite(LM_SLC_DEFAULT);
+                    rawWrite(0);
+                }
+                rawWrite(IAC);
+                rawWrite(SE);
+                flush();
+            } else {
+
+                //case 2: just acknowledge anything we get from the client
+                rawWrite(IAC);
+                rawWrite(SB);
+                rawWrite(LINEMODE);
+                rawWrite(LM_SLC);
+                rawWrite(triple[0]);
+                rawWrite(triple[1] | LM_SLC_ACK);
+                rawWrite(triple[2]);
+                while (readTriple(triple)) {
+                    rawWrite(triple[0]);
+                    rawWrite(triple[1] | LM_SLC_ACK);
+                    rawWrite(triple[2]);
+                }
+                rawWrite(IAC);
+                rawWrite(SE);
+                flush();
+            }
+        }//handleLMSLC
+
+        public void handleLMForwardMask(int WHAT) throws IOException {
+            switch (WHAT) {
+                case WONT:
+                    if (WAIT_LM_DO_REPLY_FORWARDMASK) {
+                        WAIT_LM_DO_REPLY_FORWARDMASK = false;
+                    }
+                    break;
+            }
+            skipToSE();
+        }//handleLMForward
+
+        public void handleNEWENV() throws IOException {
+            LOG.log(Level.FINE, "handleNEWENV()");
+            int c = rawread();
+            switch (c) {
+                case IS:
+                    handleNEIs();
+                    break;
+                case NE_INFO:
+                    handleNEInfo();
+                    break;
+                default:
+                    //skip to (including) SE
+                    skipToSE();
+            }
+        }//handleNEWENV
+
+        /*
+          The characters following a "type" up to the next "type" or VALUE specify the
+          variable name.
+
+          If a "type" is not followed by a VALUE
+          (e.g., by another VAR, USERVAR, or IAC SE) then that variable is
+          undefined.
+         */
+        private int readNEVariableName(StringBuffer sbuf) throws IOException {
+            LOG.log(Level.FINE, "readNEVariableName()");
+            int i = -1;
+            do {
+                i = rawread();
+                if (i == -1) {
+                    return NE_IN_ERROR;
+                } else if (i == IAC) {
+                    i = rawread();
+                    if (i == IAC) {
+                        //duplicated IAC
+                        sbuf.append((char) i);
+                    } else if (i == SE) {
+                        return NE_IN_END;
+                    } else {
+                        //Error should have been duplicated
+                        return NE_IN_ERROR;
+                    }
+                } else if (i == NE_ESC) {
+                    i = rawread();
+                    if (i == NE_ESC || i == NE_VAR || i == NE_USERVAR || i == NE_VALUE) {
+                        sbuf.append((char) i);
+                    } else {
+                        return NE_IN_ERROR;
+                    }
+                } else if (i == NE_VAR || i == NE_USERVAR) {
+                    return NE_VAR_UNDEFINED;
+                } else if (i == NE_VALUE) {
+                    return NE_VAR_DEFINED;
+                } else {
+                    //check maximum length to prevent overflow
+                    if (sbuf.length() >= NE_VAR_NAME_MAXLENGTH) {
+                        //TODO: Log Overflow
+                        return NE_IN_ERROR;
+                    } else {
+                        sbuf.append((char) i);
+                    }
+                }
+            } while (true);
+        }//readNEVariableName
+
+
+        /*
+          The characters following a VALUE up to the next
+          "type" specify the value of the variable.
+          If a VALUE is immediately
+          followed by a "type" or IAC, then the variable is defined, but has
+          no value.
+          If an IAC is contained between the IS and the IAC SE,
+          it must be sent as IAC IAC.
+        */
+        private int readNEVariableValue(StringBuffer sbuf) throws IOException {
+            LOG.log(Level.FINE, "readNEVariableValue()");
+            //check conditions for first character after VALUE
+            int i = rawread();
+            if (i == -1) {
+                return NE_IN_ERROR;
+            } else if (i == IAC) {
+                i = rawread();
+                if (i == IAC) {
+                    //Double IAC
+                    return NE_VAR_DEFINED_EMPTY;
+                } else if (i == SE) {
+                    return NE_IN_END;
+                } else {
+                    //according to rule IAC has to be duplicated
+                    return NE_IN_ERROR;
+                }
+            } else if (i == NE_VAR || i == NE_USERVAR) {
+                return NE_VAR_DEFINED_EMPTY;
+            } else if (i == NE_ESC) {
+                //escaped value
+                i = rawread();
+                if (i == NE_ESC || i == NE_VAR || i == NE_USERVAR || i == NE_VALUE) {
+                    sbuf.append((char) i);
+                } else {
+                    return NE_IN_ERROR;
+                }
+            } else {
+                //character
+                sbuf.append((char) i);
+            }
+            //loop until end of value (IAC SE or TYPE)
+            do {
+                i = rawread();
+                if (i == -1) {
+                    return NE_IN_ERROR;
+                } else if (i == IAC) {
+                    i = rawread();
+                    if (i == IAC) {
+                        //duplicated IAC
+                        sbuf.append((char) i);
+                    } else if (i == SE) {
+                        return NE_IN_END;
+                    } else {
+                        //Error should have been duplicated
+                        return NE_IN_ERROR;
+                    }
+                } else if (i == NE_ESC) {
+                    i = rawread();
+                    if (i == NE_ESC || i == NE_VAR || i == NE_USERVAR || i == NE_VALUE) {
+                        sbuf.append((char) i);
+                    } else {
+                        return NE_IN_ERROR;
+                    }
+                } else if (i == NE_VAR || i == NE_USERVAR) {
+                    return NE_VAR_OK;
+                } else {
+                    //check maximum length to prevent overflow
+                    if (sbuf.length() > NE_VAR_VALUE_MAXLENGTH) {
+                        //TODO: LOG Overflow
+                        return NE_IN_ERROR;
+                    } else {
+                        sbuf.append((char) i);
+                    }
+                }
+            } while (true);
+        }//readNEVariableValue
+
+
+        public void readNEVariables() throws IOException {
+            LOG.log(Level.FINE, "readNEVariables()");
+            StringBuffer sbuf = new StringBuffer(50);
+            int i = rawread();
+            if (i == IAC) {
+                //invalid or empty response
+                skipToSE();
+                LOG.log(Level.FINE, "readNEVariables()::INVALID VARIABLE");
+                return;
+            }
+            boolean cont = true;
+            if (i == NE_VAR || i == NE_USERVAR) {
+                do {
+                    switch (readNEVariableName(sbuf)) {
+                        case NE_IN_ERROR:
+                            LOG.log(Level.FINE, "readNEVariables()::NE_IN_ERROR");
+                            return;
+                        case NE_IN_END:
+                            LOG.log(Level.FINE, "readNEVariables()::NE_IN_END");
+                            return;
+                        case NE_VAR_DEFINED:
+                            LOG.log(Level.FINE, "readNEVariables()::NE_VAR_DEFINED");
+                            String str = sbuf.toString();
+                            sbuf.delete(0, sbuf.length());
+                            switch (readNEVariableValue(sbuf)) {
+                                case NE_IN_ERROR:
+                                    LOG.log(Level.FINE, "readNEVariables()::NE_IN_ERROR");
+                                    return;
+                                case NE_IN_END:
+                                    LOG.log(Level.FINE, "readNEVariables()::NE_IN_END");
+                                    return;
+                                case NE_VAR_DEFINED_EMPTY:
+                                    LOG.log(Level.FINE, "readNEVariables()::NE_VAR_DEFINED_EMPTY");
+                                    break;
+                                case NE_VAR_OK:
+                                    //add variable
+                                    LOG.log(Level.FINE, "readNEVariables()::NE_VAR_OK:VAR=" + str + " VAL=" + sbuf.toString());
+                                    TelnetIO.this.connectionData.getEnvironment().put(str, sbuf.toString());
+                                    sbuf.delete(0, sbuf.length());
+                                    break;
+                            }
+                            break;
+                        case NE_VAR_UNDEFINED:
+                            LOG.log(Level.FINE, "readNEVariables()::NE_VAR_UNDEFINED");
+                            break;
+                    }
+                } while (cont);
+            }
+        }//readVariables
+
+        public void handleNEIs() throws IOException {
+            LOG.log(Level.FINE, "handleNEIs()");
+            if (isEnabled(NEWENV)) {
+                readNEVariables();
+            }
+        }//handleNEIs
+
+        public void handleNEInfo() throws IOException {
+            LOG.log(Level.FINE, "handleNEInfo()");
+            if (isEnabled(NEWENV)) {
+                readNEVariables();
+            }
+        }//handleNEInfo
+
+        /**
+         * Method that sends a TTYPE Subnegotiation Request.
+         * IAC SB TERMINAL-TYPE SEND
+         */
+        public void getTTYPE() throws IOException {
+            if (isEnabled(TTYPE)) {
+                rawWrite(IAC);
+                rawWrite(SB);
+                rawWrite(TTYPE);
+                rawWrite(SEND);
+                rawWrite(IAC);
+                rawWrite(SE);
+                flush();
+            }
+        }//getTTYPE
+
+        /**
+         * Method that sends a LINEMODE MODE Subnegotiation request.
+         * IAC LINEMODE MODE MASK SE
+         */
+        public void negotiateLineMode() throws IOException {
+            if (isEnabled(LINEMODE)) {
+                rawWrite(IAC);
+                rawWrite(SB);
+                rawWrite(LINEMODE);
+                rawWrite(LM_MODE);
+                rawWrite(LM_EDIT | LM_TRAPSIG);
+                rawWrite(IAC);
+                rawWrite(SE);
+                WAIT_LM_MODE_ACK = true;
+
+                //dont forwardmask
+                rawWrite(IAC);
+                rawWrite(SB);
+                rawWrite(LINEMODE);
+                rawWrite(DONT);
+                rawWrite(LM_FORWARDMASK);
+                rawWrite(IAC);
+                rawWrite(SE);
+                WAIT_LM_DO_REPLY_FORWARDMASK = true;
+                flush();
+            }
+        }//negotiateLineMode
+
+        /**
+         * Method that sends a NEW-ENVIRON SEND subnegotiation request
+         * for default variables and user variables.
+         * IAC SB NEW-ENVIRON SEND VAR USERVAR IAC SE
+         */
+        private void negotiateEnvironment() throws IOException {
+            //log.debug("negotiateEnvironment()");
+            if (isEnabled(NEWENV)) {
+                rawWrite(IAC);
+                rawWrite(SB);
+                rawWrite(NEWENV);
+                rawWrite(SEND);
+                rawWrite(NE_VAR);
+                rawWrite(NE_USERVAR);
+                rawWrite(IAC);
+                rawWrite(SE);
+                WAIT_NE_SEND_REPLY = true;
+                flush();
+            }
+        }//negotiateEnvironment
+
+        /**
+         * Method that skips a subnegotiation response.
+         */
+        private void skipToSE() throws IOException {
+            while (rawread() != SE) ;
+        }//skipSubnegotiation
+
+        private boolean readTriple(int[] triple) throws IOException {
+            triple[0] = rawread();
+            triple[1] = rawread();
+            if ((triple[0] == IAC) && (triple[1] == SE)) {
+                return false;
+            } else {
+                triple[2] = rawread();
+                return true;
+            }
+        }//readTriple
+
+        /**
+         * Method that reads a subnegotiation String,
+         * one of those that end with a IAC SE combination.
+         * A maximum length is observed to prevent overflow.
+         *
+         * @return IAC SE terminated String
+         */
+        private String readIACSETerminatedString(int maxlength) throws IOException {
+            int where = 0;
+            char[] cbuf = new char[maxlength];
+            char b = ' ';
+            boolean cont = true;
+
+            do {
+                int i;
+                i = rawread();
+                switch (i) {
+                    case IAC:
+                        i = rawread();
+                        if (i == SE) {
+                            cont = false;
+                        }
+                        break;
+                    case -1:
+                        return (new String("default"));
+                    default:
+                }
+                if (cont) {
+                    b = (char) i;
+                    //Fix for overflow wimpi (10/06/2004)
+                    if (b == '\n' || b == '\r' || where == maxlength) {
+                        cont = false;
+                    } else {
+                        cbuf[where++] = b;
+                    }
+                }
+            } while (cont);
+
+            return (new String(cbuf, 0, where));
+        }//readIACSETerminatedString
+
+        /**
+         * Method that informs internally about the supported Negotiation Options
+         *
+         * @param i int that represents requested the Option
+         * @return Boolean that represents support status
+         */
+        private boolean supported(int i) {
+            switch (i) {
+                case SUPGA:
+                case ECHO:
+                case NAWS:
+                case TTYPE:
+                case NEWENV:
+                    return true;
+                case LINEMODE:
+                    return connectionData.isLineMode();
+                default:
+                    return false;
+            }
+        }//supported
+
+        /**
+         * Method that sends a Telnet IAC String with TelnetIO.write(byte b) method.
+         *
+         * @param i int that represents requested Command Type (DO,DONT,WILL,WONT)
+         * @param j int that represents the Option itself (e.g. ECHO, NAWS)
+         */
+        private void sendCommand(int i, int j, boolean westarted) throws IOException {
+            rawWrite(IAC);
+            rawWrite(i);
+            rawWrite(j);
+            // we started with DO OPTION and now wait for reply
+            if ((i == DO) && westarted) setWait(DO, j, true);
+            // we started with WILL OPTION and now wait for reply
+            if ((i == WILL) && westarted) setWait(WILL, j, true);
+            flush();
+        }//sendCommand
+
+        /**
+         * Method enables or disables a supported Option
+         *
+         * @param i int that represents the Option
+         */
+        private void enable(int i) throws IOException {
+            switch (i) {
+                case SUPGA:
+                    if (DO_SUPGA) {
+                        DO_SUPGA = false;
+                    } else {
+                        DO_SUPGA = true;
+                    }
+                    break;
+                case ECHO:
+                    if (DO_ECHO) {
+                        DO_ECHO = false;
+                    } else {
+                        DO_ECHO = true;
+                    }
+                    break;
+                case NAWS:
+                    if (DO_NAWS) {
+                        DO_NAWS = false;
+                    } else {
+                        DO_NAWS = true;
+                    }
+                    break;
+                case TTYPE:
+                    if (DO_TTYPE) {
+                        DO_TTYPE = false;
+                    } else {
+                        DO_TTYPE = true;
+                        getTTYPE();
+                    }
+                    break;
+                case LINEMODE:
+                    if (DO_LINEMODE) {
+                        DO_LINEMODE = false;
+                        //set false in connection data, so the application knows.
+                        connectionData.setLineMode(false);
+                    } else {
+                        DO_LINEMODE = true;
+                        negotiateLineMode();
+                    }
+                    break;
+                case NEWENV:
+                    if (DO_NEWENV) {
+                        DO_NEWENV = false;
+                    } else {
+                        DO_NEWENV = true;
+                        negotiateEnvironment();
+                    }
+                    break;
+            }
+        }//enable
+
+        /**
+         * Method that informs internally about the status of the supported
+         * Negotiation Options.
+         *
+         * @param i int that represents requested the Option
+         * @return Boolean that represents the enabled status
+         */
+        private boolean isEnabled(int i) {
+            switch (i) {
+                case SUPGA:
+                    return DO_SUPGA;
+                case ECHO:
+                    return DO_ECHO;
+                case NAWS:
+                    return DO_NAWS;
+                case TTYPE:
+                    return DO_TTYPE;
+                case LINEMODE:
+                    return DO_LINEMODE;
+                case NEWENV:
+                    return DO_NEWENV;
+                default:
+                    return false;
+            }
+        }//isEnabled
+
+        /**
+         * Method that informs internally about the WILL wait status
+         * of an option.
+         *
+         * @param i that represents requested the Option
+         * @return Boolean that represents WILL wait status of the Option
+         */
+        private boolean waitWILLreply(int i) {
+            switch (i) {
+                case SUPGA:
+                    return WAIT_WILL_REPLY_SUPGA;
+                case ECHO:
+                    return WAIT_WILL_REPLY_ECHO;
+                case NAWS:
+                    return WAIT_WILL_REPLY_NAWS;
+                case TTYPE:
+                    return WAIT_WILL_REPLY_TTYPE;
+                default:
+                    return false;
+            }
+        }//waitWILLreply
+
+        /**
+         * Method that informs internally about the DO wait status
+         * of an option.
+         *
+         * @param i Integer that represents requested the Option
+         * @return Boolean that represents DO wait status of the Option
+         */
+        private boolean waitDOreply(int i) {
+            switch (i) {
+                case SUPGA:
+                    return WAIT_DO_REPLY_SUPGA;
+                case ECHO:
+                    return WAIT_DO_REPLY_ECHO;
+                case NAWS:
+                    return WAIT_DO_REPLY_NAWS;
+                case TTYPE:
+                    return WAIT_DO_REPLY_TTYPE;
+                case LINEMODE:
+                    return WAIT_DO_REPLY_LINEMODE;
+                case NEWENV:
+                    return WAIT_DO_REPLY_NEWENV;
+                default:
+                    return false;
+            }
+        }//waitDOreply
+
+        /**
+         * Method that mutates the wait status of an option in
+         * negotiation. We need the wait status to keep track of
+         * negotiation in process. So we cant miss if we started out
+         * or the other and so on.
+         *
+         * @param WHAT   Integer values of  DO or WILL
+         * @param OPTION Integer that represents the Option
+         * @param WAIT   Boolean that represents the status of wait that should be set
+         */
+        private void setWait(int WHAT, int OPTION, boolean WAIT) {
+            switch (WHAT) {
+                case DO:
+                    switch (OPTION) {
+                        case SUPGA:
+                            WAIT_DO_REPLY_SUPGA = WAIT;
+                            break;
+                        case ECHO:
+                            WAIT_DO_REPLY_ECHO = WAIT;
+                            break;
+                        case NAWS:
+                            WAIT_DO_REPLY_NAWS = WAIT;
+                            break;
+                        case TTYPE:
+                            WAIT_DO_REPLY_TTYPE = WAIT;
+                            break;
+                        case LINEMODE:
+                            WAIT_DO_REPLY_LINEMODE = WAIT;
+                            break;
+                        case NEWENV:
+                            WAIT_DO_REPLY_NEWENV = WAIT;
+                            break;
+                    }
+                    break;
+                case WILL:
+                    switch (OPTION) {
+                        case SUPGA:
+                            WAIT_WILL_REPLY_SUPGA = WAIT;
+                            break;
+                        case ECHO:
+                            WAIT_WILL_REPLY_ECHO = WAIT;
+                            break;
+                        case NAWS:
+                            WAIT_WILL_REPLY_NAWS = WAIT;
+                            break;
+                        case TTYPE:
+                            WAIT_WILL_REPLY_TTYPE = WAIT;
+                            break;
+                    }
+                    break;
+            }
+        }//setWait
+
+    }//inner class IACHandler
+
+    /** end Constants declaration **************************************************/
+
+}//class TelnetIO
diff --git a/gogo/jline/src/main/resources/gosh_profile b/gogo/jline/src/main/resources/gosh_profile
new file mode 100644
index 0000000..1a5822c
--- /dev/null
+++ b/gogo/jline/src/main/resources/gosh_profile
@@ -0,0 +1,247 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+# default gosh_profile
+# only read if etc/gosh_profile doesn't exist relative to the System property
+# gosh.home or failing that the current directory.
+
+# catch all exceptions from this script to avoid it aborting startup
+try {
+
+  # ensure gogo commands are found first
+  SCOPE = gogo:*
+
+  # add methods on BundleContext object as commands
+  addcommand context ${.context}
+
+  # add methods on System object as commands
+  #addcommand system (((${.context} getClass) getClassLoader) loadClass java.lang.System)
+
+  # alias to print full stack trace
+  e = { $exception printStackTrace }
+
+  ## disable console auto-formatting of each result
+  #  you will then need to explicitly use the 'format' command
+  #  to print the result of commands that don't write to stdout.
+  #.Gogo.format = false
+
+  ## disable printing the formatted result of a command into pipelines
+  #.Format.Pipe = false
+
+  # set prompt
+  prompt = 'g! '
+  \#rprompt = { (new java.text.SimpleDateFormat "HH:mm:ss") format (new Date) }
+
+
+  __option_not_present = {
+      res = true
+      opts = ([ $args ] get 0)
+      each $opts {
+          arg = $it
+          each ($.commandLine words) {
+              if { ($it toString) contentEquals ($arg toString) } {
+                  res = false
+              }
+          }
+      }
+      $res
+  }
+
+  __load_class = {
+      (($.reader class) classLoader) loadClass $1
+  }
+
+  __as_list = {
+      (__load_class java.util.Arrays) asList $1
+  }
+
+  __set_unset_arguments = {
+      is_setopt = (($.commandLine words) get 0) equals "setopt"
+      enums = ((__load_class 'org.jline.ConsoleReader$Option') enumConstants)
+      candidates = new ArrayList
+      each (__as_list $enums) {
+          name = ((($it name) toLowerCase) replace '_' '-')
+          is_set = ($.reader isSet $it)
+          neg = %(( if(is_setopt, is_set, not(is_set)) ))
+          if { $neg } {
+              name = "no-${name}"
+          }
+          if { not  { (($.commandLine words) subList 1 ($.commandLine wordIndex)) contains $name } } {
+              $candidates add (new org.jline.Candidate $name $name (if { $neg } { "unset" } { "set" }) null null null true)
+          }
+      }
+      $candidates
+  }
+
+  setopt pad-prompts
+  setopt group
+
+  complete -c gogo:complete -e
+  complete -c gogo:complete -d "Edit command specific completions"
+  complete -c gogo:complete -s c -l command --description "Command to add completion to" -n '__option_not_present -c --command' -a '$.commands'
+  complete -c gogo:complete -s s -l short-option --description "Posix-style option to complete" -n '__option_not_present -s --short-option'
+  complete -c gogo:complete -s l -l long-option --description "GNU-style option to complete" -n '__option_not_present -l --long-option'
+  complete -c gogo:complete -s a -l arguments --description "A list of possible arguments" -n '__option_not_present -a --argument'
+  complete -c gogo:complete -s d -l description --description "Description of this completions" -n '__option_not_present -d --description'
+  complete -c gogo:complete -s h -l help --description "Display help and exit" -n '__option_not_present -h --help'
+  complete -c gogo:complete -s n -l condition --description "The completion should only be used if the specified command has a zero exit status"  -n '__option_not_present -n --condition'
+  complete -c gogo:complete -s e -l erase --description "Remove completion"  -n '__option_not_present -e --erase'
+
+  complete -c gogo:history -e
+  complete -c gogo:history -d "Show and manipulate command history"
+  complete -c gogo:history -l clear --description "Clear history" -n '__option_not_present --clear'
+  complete -c gogo:history -l save --description "Save history" -n '__option_not_present --save'
+
+  complete -c gogo:setopt -e
+  complete -c gogo:setopt -d "Set or view set shell options"
+  complete -c gogo:setopt -a '__set_unset_arguments'
+
+  complete -c gogo:unsetopt -e
+  complete -c gogo:unsetopt -d "Unset or view unset shell options"
+  complete -c gogo:unsetopt -a '__set_unset_arguments'
+
+  complete -c gogo:cat -e
+  complete -c gogo:cat -d "Concatenate and print files"
+  complete -c gogo:cat -s n "Number the output lines, starting at 1"
+  complete -c gogo:cat -a '__files'
+
+  complete -c gogo:pwd -e
+  complete -c gogo:pwd -d "Get current directory"
+
+  complete -c gogo:ls -e
+  complete -c gogo:ls -d "List files"
+
+  complete -c gogo:cd -e
+  complete -c gogo:cd -d "Change current directory"
+  complete -c gogo:cd -a '__directories'
+
+  complete -c gogo:sleep -e
+  complete -c gogo:sleep -d "Pause execution for the specified amount of time"
+
+  complete -c gogo:echo -e
+  complete -c gogo:echo -d "Write arguments to the standard output"
+  complete -c gogo:echo -s n -d "No trailing new line"
+
+  complete -c gogo:grep -e
+  complete -c gogo:grep -d "File pattern searcher"
+  # TODO
+
+  complete -c gogo:sort -e
+  complete -c gogo:sort -d "Sort lines of text files"
+  # TODO
+
+  complete -c gogo:gosh -e
+  complete -c gogo:gosh -d "Execute script with arguments in a new session"
+  # TODO
+
+  complete -c gogo:sh -e
+  complete -c gogo:sh -d "Execute script with arguments in a new session"
+  # TODO
+
+  complete -c gogo:source -e
+  complete -c gogo:source -d "Execute script with arguments"
+  # TODO
+
+  # TODO: format getopt new set tac type addcommand removeCommand eval
+
+  complete -c gogo:each -e
+  complete -c gogo:each -d "Loop and execute script on the specified elements"
+
+  complete -c gogo:if -e
+  complete -c gogo:if -d "Conditionaly execute a script"
+
+  complete -c gogo:not -e
+  complete -c gogo:not -d "Negates the result of a script"
+
+  complete -c gogo:throw -e
+  complete -c gogo:throw -d "Throws an exception"
+
+  complete -c gogo:try -e
+  complete -c gogo:try -d "Try executing a script and catch any exception"
+
+  complete -c gogo:until -e
+  complete -c gogo:until -d "Loop and execute script until a condition is satisfied"
+
+  complete -c gogo:while -e
+  complete -c gogo:while -d "Loop and execute script while a condition is satisfied"
+
+  complete -c gogo:less -e
+  complete -c gogo:less -d "File pager"
+  complete -c gogo:less -s e -l quit-at-eof --description "Exit on second EOF"
+  complete -c gogo:less -s E -l QUIT-AT-EOF --description "Exit on EOF"
+  complete -c gogo:less -s q -l quiet -l silent --description "Silent mode"
+  complete -c gogo:less -s Q -l QUIET -l SILENT --description "Completely  silent"
+  complete -c gogo:less -s S -l chop-long-lines --description "Do not fold long lines"
+  complete -c gogo:less -s i -l ignore-case --description "Search ignores lowercase case"
+  complete -c gogo:less -s I -l IGNORE-CASE --description "Search ignores all case"
+  complete -c gogo:less -s x -l tabs --description "Set tab stops"
+  complete -c gogo:less -s N -l LINE-NUMBERS --description "Display line number for each line"
+  complete -c gogo:less -a '__files'
+
+  complete -c gogo:nano -e
+  complete -c gogo:nano -d "File editor"
+  complete -c gogo:nano -a '__files'
+
+  complete -c gogo:keymap -e
+  complete -c gogo:keymap -d "Manipulate keymaps"
+  complete -c gogo:keymap -s N --description "Create a new keymap" -n '__option_not_present -N -d -D -l -r -s -A'
+  complete -c gogo:keymap -s d --description "Delete existing keymaps and reset to default state" -n '__option_not_present -N -d -D -l -r -s -A'
+  complete -c gogo:keymap -s D --description "Delete named keymaps" -n '__option_not_present -N -d -D -l -r -s -A'
+  complete -c gogo:keymap -s l --description "List existing keymap names" -n '__option_not_present -N -d -D -l -r -s -A'
+  complete -c gogo:keymap -s r --description "Unbind specified in-strings" -n '__option_not_present -N -d -D -l -r -s -A'
+  complete -c gogo:keymap -s s --description "Bind each in-string to each out-string" -n '__option_not_present -N -d -D -l -r -s -A'
+  complete -c gogo:keymap -s A --description "Create alias to keymap" -n '__option_not_present -N -d -D -l -r -s -A'
+  complete -c gogo:keymap -s e --description "Select emacs keymap and bind it to main" -n '__option_not_present -e -a -v -M'
+  complete -c gogo:keymap -s v --description "Select viins keymap and bind it to main" -n '__option_not_present -e -a -v -M'
+  complete -c gogo:keymap -s a --description "Select vicmd keymap" -n '__option_not_present -e -a -v -M'
+  complete -c gogo:keymap -s M --description "Specify keymap to select" -n '__option_not_present -e -a -v -M' -a '(keymap -l | tac) split " "'
+  complete -c gogo:keymap -s R --description "Interpret in-strings as ranges"
+  complete -c gogo:keymap -s p --description "List bindings which have given key sequence as a a prefix"
+  complete -c gogo:keymap -s L --description "Output in form of keymap commands"
+
+  complete -c gogo:widget -e
+  complete -c gogo:widget -d "Manipulate widgets"
+  complete -c gogo:widget -s N --description "Create a new widget" -n '__option_not_present -N -A -D -U -l'
+  complete -c gogo:widget -s A --description "Create alias to widget" -n '__option_not_present -N -A -D -U -l'
+  complete -c gogo:widget -s D --description "Delete widgets" -n '__option_not_present -N -A -D -U -l'
+  complete -c gogo:widget -s U --description "Push characters to the stack" -n '__option_not_present -N -A -D -U -l'
+  complete -c gogo:widget -s l --description "List user-defined widgets" -n '__option_not_present -N -A -D -U -l'
+  complete -c gogo:widget -s a --description "With -l, list all widgets" -n '__option_not_present -l'
+
+  complete -c gogo:telnetd -e
+  complete -c gogo:telnetd -d "Telnet daemon"
+  complete -c gogo:telnetd -s i -l ip --description "Listening IP interface" -n '__option_not_present -i --ip'
+  complete -c gogo:telnetd -s p -l port --description "Listening IP port" -n '__option_not_present -p --port'
+  complete -c gogo:telnetd -a '[start stop status]'
+
+  complete -c gogo:sshd -e
+  complete -c gogo:sshd -d "SSH daemon"
+  complete -c gogo:sshd -s i -l ip --description "Listening IP interface" -n '__option_not_present -i --ip'
+  complete -c gogo:sshd -s p -l port --description "Listening IP port" -n '__option_not_present -p --port'
+  complete -c gogo:sshd -a '[start stop status]'
+
+  complete -c gogo:tmux -e
+  complete -c gogo:tmux -d "Terminal multiplexer"
+
+  # print welcome message
+  cat ($0 resolve motd)
+} {
+    echo "$0: ERROR: $exception"
+}
+
+# end
diff --git a/gogo/jline/src/main/resources/motd b/gogo/jline/src/main/resources/motd
new file mode 100644
index 0000000..01954f9
--- /dev/null
+++ b/gogo/jline/src/main/resources/motd
@@ -0,0 +1,3 @@
+____________________________
+Welcome to Apache Felix Gogo
+
diff --git a/gogo/pom.xml b/gogo/pom.xml
index d814dbe..39a32d0 100644
--- a/gogo/pom.xml
+++ b/gogo/pom.xml
@@ -40,6 +40,7 @@
     <modules>
     	<module>gogo-parent</module>
         <module>runtime</module>
+        <module>jline</module>
         <module>shell</module>
         <module>command</module>
     </modules>