FELIX-1266: maven build for gogo

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@789948 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/gogo/gogo-runtime/pom.xml b/gogo/gogo-runtime/pom.xml
new file mode 100644
index 0000000..22c6dfb
--- /dev/null
+++ b/gogo/gogo-runtime/pom.xml
@@ -0,0 +1,77 @@
+<!--
+ 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.gogo</groupId>
+        <artifactId>gogo</artifactId>
+        <version>1.0.0-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <packaging>bundle</packaging>
+    <name>Apache Felix Gogo Shell Runtime</name>
+    <artifactId>org.apache.felix.gogo.runtime</artifactId>
+    <version>1.0.0-SNAPSHOT</version>
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.osgi.core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <version>2.0.0</version>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Export-Package>
+                            org.osgi.service.command; version=1.0.0,
+                            org.osgi.service.threadio; version=1.0.0
+                        </Export-Package>
+                        <Import-Package>
+                            org.osgi.service.component*; resolution:=optional,
+                            org.osgi.service.log*; resolution:=optional,
+                            org.osgi.service.packageadmin*; resolution:=optional,
+                            org.osgi.service.startlevel*; resolution:=optional,
+                            *
+                        </Import-Package>
+                        <Private-Package>org.apache.felix.gogo.*</Private-Package>
+                        <Bundle-SymbolicName>${pom.artifactId}</Bundle-SymbolicName>
+                        <Bundle-Vendor>The Apache Software Foundation</Bundle-Vendor>
+                        <Bundle-Activator>org.apache.felix.gogo.runtime.Activator</Bundle-Activator>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/Activator.java b/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/Activator.java
new file mode 100644
index 0000000..4b9d215
--- /dev/null
+++ b/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/Activator.java
@@ -0,0 +1,127 @@
+/*
+ * 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.runtime;
+
+import org.apache.felix.gogo.runtime.lang.Support;
+import org.apache.felix.gogo.runtime.osgi.OSGiShell;
+import org.apache.felix.gogo.runtime.threadio.ThreadIOImpl;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.command.CommandProcessor;
+import org.osgi.service.command.Converter;
+import org.osgi.service.threadio.ThreadIO;
+import org.osgi.util.tracker.ServiceTracker;
+
+import java.util.Hashtable;
+
+public class Activator implements BundleActivator
+{
+
+    private OSGiShell shell;
+    private ThreadIOImpl threadio;
+    private ServiceRegistration shellRegistration;
+    private ServiceRegistration threadioRegistration;
+    private ServiceTracker converterTracker;
+    private ServiceTracker commandTracker;
+
+    public void start(final BundleContext context) throws Exception
+    {
+        Hashtable props = new Hashtable();
+        props.put("osgi.command.scope", "log");
+        props.put("osgi.command.function", "display");
+
+        threadio = new ThreadIOImpl();
+        threadio.start();
+        shell = new OSGiShell();
+        shell.setBundle(context.getBundle());
+        shell.setThreadio(threadio);
+        shell.setConverter(new Support());
+        shell.start();
+        converterTracker = new ServiceTracker(context, Converter.class.getName(), null) {
+            @Override
+            public Object addingService(ServiceReference reference)
+            {
+                Converter converter = (Converter) super.addingService(reference);
+                shell.setConverter(converter);
+                return converter;
+            }
+
+            @Override
+            public void removedService(ServiceReference reference, Object service)
+            {
+                shell.unsetConverter((Converter) service);
+                super.removedService(reference, service);
+            }
+        };
+        converterTracker.open();
+        commandTracker = new ServiceTracker(context, context.createFilter("(&(osgi.command.scope=*)(osgi.command.function=*))"), null) {
+            @Override
+            public Object addingService(ServiceReference reference)
+            {
+                Object scope = reference.getProperty("osgi.command.scope");
+                Object function = reference.getProperty("osgi.command.function");
+                if(scope != null && function != null)
+                {
+                    Object target = context.getService(reference);
+                    if (function.getClass().isArray())
+                    {
+                        for (Object f : ((Object[]) function))
+                        {
+                            shell.addCommand(scope.toString(), target, f.toString());
+                        }
+                    }
+                    else
+                    {
+                        shell.addCommand(scope.toString(), target, function.toString());
+                    }
+                    return target;
+                }
+                return null;
+            }
+
+            @Override
+            public void removedService(ServiceReference reference, Object service)
+            {
+                super.removedService(reference, service);
+            }
+        };
+        commandTracker.open();
+        threadioRegistration = context.registerService(ThreadIO.class.getName(), threadio, new Hashtable());
+        shellRegistration = context.registerService(CommandProcessor.class.getName(), shell, new Hashtable());
+    }
+
+    private String getProperty(BundleContext context, String name, String def) {
+        String v = context.getProperty(name);
+        if (v == null) {
+            v = def;
+        }
+        return v;
+    }
+
+    public void stop(BundleContext context) throws Exception
+    {
+        shellRegistration.unregister();
+        threadioRegistration.unregister();
+        threadio.stop();
+        converterTracker.close();
+        commandTracker.close();
+    }
+}
diff --git a/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/cpeg/Procedural.java b/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/cpeg/Procedural.java
new file mode 100644
index 0000000..a9ea63b
--- /dev/null
+++ b/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/cpeg/Procedural.java
@@ -0,0 +1,78 @@
+/*
+ * 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.runtime.cpeg;
+
+
+import org.osgi.framework.Bundle;
+import org.osgi.service.command.CommandSession;
+import org.osgi.service.command.Function;
+
+
+public class Procedural
+{
+
+    public Object _if(CommandSession session, Function condition, Function ifTrue, Function ifFalse) throws Exception
+    {
+        Object result = condition.execute(session, null);
+        if (isTrue(result))
+        {
+            return ifTrue.execute(session, null);
+        }
+        else
+        {
+            if (ifFalse != null)
+            {
+                return ifFalse.execute(session, null);
+            }
+        }
+        return null;
+    }
+
+    public Object _new(String name, Bundle bundle) throws Exception
+    {
+        if (bundle == null)
+        {
+            return Class.forName(name).newInstance();
+        }
+        else
+        {
+            return bundle.loadClass(name).newInstance();
+        }
+    }
+
+    private boolean isTrue(Object result)
+    {
+        if (result == null)
+        {
+            return false;
+        }
+
+        if (result instanceof String && ((String) result).equals(""))
+        {
+            return false;
+        }
+
+        if (result instanceof Boolean)
+        {
+            return ((Boolean) result).booleanValue();
+        }
+
+        return true;
+    }
+}
diff --git a/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/equinox/Equinox.java b/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/equinox/Equinox.java
new file mode 100644
index 0000000..8187496
--- /dev/null
+++ b/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/equinox/Equinox.java
@@ -0,0 +1,476 @@
+package org.apache.felix.gogo.runtime.equinox;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.command.CommandProcessor;
+import org.osgi.service.command.CommandSession;
+import org.osgi.service.command.Converter;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.log.LogEntry;
+import org.osgi.service.log.LogReaderService;
+import org.osgi.service.log.LogService;
+import org.osgi.service.packageadmin.ExportedPackage;
+import org.osgi.service.packageadmin.PackageAdmin;
+import org.osgi.service.startlevel.StartLevel;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.*;
+
+public class Equinox implements Converter
+{
+    BundleContext context;
+    PackageAdmin pka;
+    LogReaderService lrs;
+    StartLevel sls;
+    final static String[] functions = {"active", "bundles", "close", "diag", "exec", "exit", "fork", "gc", "getprop", "headers", "init", "install", "launch", "log", "packages", "packages", "refresh", "services", "setbsl", "setfwsl", "setibsl", "setprop", "shutdown", "sl", "ss", "start", "status", "stop", "uninstall", "update"};
+
+    protected void activate(ComponentContext context)
+    {
+        this.context = context.getBundleContext();
+        Dictionary<String, Object> dict = new Hashtable<String, Object>();
+        dict.put(CommandProcessor.COMMAND_SCOPE, "eqx");
+        dict.put(CommandProcessor.COMMAND_FUNCTION, functions);
+        this.context.registerService(Converter.class.getName(), this, dict);
+    }
+
+    BundleContext getContext()
+    {
+        return context;
+    }
+
+    public void setPka(PackageAdmin pka)
+    {
+        this.pka = pka;
+    }
+
+    public void setLrs(LogReaderService lrs)
+    {
+        this.lrs = lrs;
+    }
+
+    public void setSls(StartLevel sls)
+    {
+        this.sls = sls;
+    }
+
+    /**
+     * - Displays unsatisfied constraints for the specified bundle(s).
+     */
+    public void diag()
+    {
+    }
+
+    /*
+     * active - Displays a list of all bundles currently in the ACTIVE state.
+     */
+    public List<Bundle> active()
+    {
+        List<Bundle> result = new ArrayList<Bundle>();
+        Bundle[] bundles = getContext().getBundles();
+        for (Bundle b : bundles)
+        {
+            if (b.getState() == Bundle.ACTIVE)
+            {
+                result.add(b);
+            }
+        }
+        return result;
+    }
+
+    /*
+     * getprop { name } - Displays the system properties with the given name, or
+     * all of them.
+     */
+
+    public Object getprop(CharSequence name)
+    {
+        if (name == null)
+        {
+            return System.getProperties();
+        }
+        else
+        {
+            return System.getProperty(name.toString());
+        }
+    }
+
+    /**
+     * launch - start the OSGi Framework
+     */
+
+    public void launch()
+    {
+        throw new IllegalStateException("Already running");
+    }
+
+    /**
+     * shutdown - shutdown the OSGi Framework
+     */
+    public void shutdown() throws BundleException
+    {
+        getContext().getBundle().stop();
+    }
+
+    /**
+     * close - shutdown and exit
+     */
+    public void close(CommandSession session)
+    {
+        session.close();
+    }
+
+    /**
+     * exit - exit immediately (System.exit)
+     */
+
+    public void exit(int exitValue)
+    {
+        exit(exitValue);
+    }
+
+    /**
+     * gc - perform a garbage collection
+     */
+    public long gc()
+    {
+        Runtime.getRuntime().gc();
+        return Runtime.getRuntime().freeMemory();
+    }
+
+    /**
+     * init - uninstall all bundles
+     */
+
+    public void init() throws Exception
+    {
+        Bundle bundles[] = getContext().getBundles();
+        for (Bundle b : bundles)
+        {
+            if (b.getBundleId() != 0)
+            {
+                b.uninstall();
+            }
+        }
+    }
+
+    /**
+     * setprop <key>=<value> - set the OSGi property
+     */
+    public void setprop(CommandSession session, String key, String value)
+    {
+        session.put(key, value);
+    }
+
+    /**
+     * install - install and optionally start bundle from the given URL
+     *
+     * @throws BundleException
+     */
+
+    public Bundle install(URL url) throws BundleException
+    {
+        return getContext().installBundle(url.toExternalForm());
+    }
+
+    /**
+     * uninstall - uninstall the specified bundle(s)
+     *
+     * @throws BundleException
+     */
+    public void uninstall(Bundle[] bundles) throws BundleException
+    {
+        for (Bundle b : bundles)
+        {
+            b.uninstall();
+        }
+    }
+
+    /**
+     * start - start the specified bundle(s)
+     */
+    public void start(Bundle[] bundles) throws BundleException
+    {
+        for (Bundle b : bundles)
+        {
+            b.start();
+        }
+    }
+
+    /**
+     * stop - stop the specified bundle(s)
+     */
+    public void stop(Bundle[] bundles) throws BundleException
+    {
+        for (Bundle b : bundles)
+        {
+            b.stop();
+        }
+    }
+
+    /**
+     * refresh - refresh the packages of the specified bundles
+     */
+    public void refresh(Bundle[] bundles) throws Exception
+    {
+        if (pka != null)
+        {
+            pka.refreshPackages(bundles);
+        }
+        else
+        {
+            throw new RuntimeException("No PackageAdmin service registered");
+        }
+    }
+
+    /**
+     * update - update the specified bundle(s)
+     */
+    public void update(Bundle[] bundles) throws BundleException
+    {
+        for (Bundle b : bundles)
+        {
+            b.update();
+        }
+    }
+
+    /**
+     * status - display installed bundles and registered services
+     */
+    public List<Object> status() throws Exception
+    {
+        List<Object> status = new ArrayList<Object>();
+        status.addAll(Arrays.asList(getContext().getBundles()));
+        status.addAll(Arrays.asList(getContext().getServiceReferences(null, null)));
+        return status;
+    }
+
+    /**
+     * ss - display installed bundles (short status)
+     */
+    public Bundle[] ss()
+    {
+        return getContext().getBundles();
+    }
+
+    /**
+     * services {filter} - display registered service details
+     */
+    public ServiceReference[] services(String filter) throws Exception
+    {
+        return getContext().getServiceReferences(null, filter);
+    }
+
+    /**
+     * packages {<pkgname>|<id>|<location>} - display imported/exported
+     * package details
+     */
+    public ExportedPackage[] packages(Bundle bundle) throws Exception
+    {
+        if (pka != null)
+        {
+            return pka.getExportedPackages(bundle);
+        }
+        else
+        {
+            throw new RuntimeException("No PackageAdmin service registered");
+        }
+    }
+
+    public ExportedPackage[] packages(String packageName) throws Exception
+    {
+        if (pka != null)
+        {
+            return pka.getExportedPackages(packageName);
+        }
+        return null;
+    }
+
+    /**
+     * bundles - display details for all installed bundles
+     */
+    public Bundle[] bundles()
+    {
+        return ss();
+    }
+
+    /**
+     * bundle (<id>|<location>) - display details for the specified bundle(s)
+     */
+
+    /**
+     * headers (<id>|<location>) - print bundle headers
+     */
+
+    @SuppressWarnings("unchecked")
+    public Dictionary headers(Bundle b, String locale)
+    {
+        return b.getHeaders(locale);
+    }
+
+    /**
+     * log (<id>|<location>) - display log entries
+     */
+
+    @SuppressWarnings("unchecked")
+    public Collection<LogEntry> log(Bundle bundle) throws Exception
+    {
+        if (lrs != null)
+        {
+            return Collections.list((Enumeration<LogEntry>) lrs.getLog());
+        }
+        else
+        {
+            throw new RuntimeException("LogReader not available");
+        }
+    }
+
+    /**
+     * exec <command> - execute a command in a separate process and wait
+     *
+     * @throws IOException
+     */
+
+    public int exec(Object[] args, boolean fork) throws IOException
+    {
+        StringBuffer sb = new StringBuffer();
+        String del = "";
+        for (Object arg : args)
+        {
+            sb.append(del);
+            sb.append(arg);
+            del = " ";
+        }
+        Process p = Runtime.getRuntime().exec(sb.toString());
+        if (fork)
+        {
+            int c;
+            while ((c = p.getInputStream().read()) > 0)
+            {
+                System.out.print(c);
+            }
+        }
+        return p.exitValue();
+    }
+
+    /**
+     * fork <command> - execute a command in a separate process
+     */
+
+    public void fork(Object args[]) throws Exception
+    {
+        exec(args, true);
+    }
+
+    /**
+     * sl {(<id>|<location>)} - display the start level for the specified
+     * bundle, or for the framework if no bundle specified
+     */
+    public int sl(Bundle b) throws Exception
+    {
+        if (sls == null)
+        {
+            throw new RuntimeException("No StartLevel service registered");
+        }
+        if (b == null)
+        {
+            return sls.getStartLevel();
+        }
+        else
+        {
+            return sls.getBundleStartLevel(b);
+        }
+    }
+
+    /**
+     * setfwsl <start level> - set the framework start level
+     */
+    public int setfwsl(int n) throws Exception
+    {
+        if (sls == null)
+        {
+            throw new RuntimeException("No StartLevel service registered");
+        }
+        int old = sls.getStartLevel();
+        sls.setStartLevel(n);
+        return old;
+    }
+
+    /**
+     * setbsl <start level> (<id>|<location>) - set the start level for the
+     * bundle(s)
+     */
+    public int setbsl(Bundle b, int n) throws Exception
+    {
+        if (sls == null)
+        {
+            throw new RuntimeException("No StartLevel service registered");
+        }
+        int old = sls.getBundleStartLevel(b);
+        sls.setBundleStartLevel(b, n);
+        return old;
+    }
+
+    /**
+     * setibsl <start level> - set the initial bundle start level
+     */
+    public int setibsl(int n) throws Exception
+    {
+        if (sls == null)
+        {
+            throw new RuntimeException("No StartLevel service registered");
+        }
+        int old = sls.getInitialBundleStartLevel();
+        sls.setInitialBundleStartLevel(n);
+        return old;
+    }
+
+    public Object convert(Class<?> desiredType, Object in) throws Exception
+    {
+        return null;
+    }
+
+    String getLevel(int index)
+    {
+        switch (index)
+        {
+            case LogService.LOG_DEBUG:
+                return "DEBUG";
+            case LogService.LOG_INFO:
+                return "INFO ";
+            case LogService.LOG_WARNING:
+                return "WARNI";
+            case LogService.LOG_ERROR:
+                return "ERROR";
+            default:
+                return "<" + index + ">";
+        }
+    }
+
+    public CharSequence format(Object target, int level, Converter escape)
+    {
+        if (target instanceof LogEntry)
+        {
+            LogEntry entry = (LogEntry) target;
+            switch (level)
+            {
+                case LINE:
+                    Formatter f = new Formatter();
+                    f.format("%tT %04d %s %s", entry.getTime(), entry.getBundle().getBundleId(), getLevel(entry.getLevel()) + "", entry.getMessage() + "");
+                    return f.toString();
+
+                case PART:
+                    Formatter f2 = new Formatter();
+                    f2.format("%tT %s", entry.getTime(), entry.getMessage());
+                    return f2.toString();
+            }
+        }
+        return null;
+    }
+    /**
+     * profilelog - Display & flush the profile log messages
+     */
+
+}
diff --git a/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/lang/Support.java b/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/lang/Support.java
new file mode 100644
index 0000000..2ab6bbe
--- /dev/null
+++ b/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/lang/Support.java
@@ -0,0 +1,55 @@
+/*
+ * 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.runtime.lang;
+
+import org.osgi.service.command.Converter;
+import org.osgi.service.command.Function;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Arrays;
+
+
+public class Support implements Converter
+{
+
+    public Object convert(Class<?> desiredType, final Object in) throws Exception
+    {
+        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;
+    }
+
+    public CharSequence format(Object target, int level, Converter escape) throws Exception
+    {
+        return null;
+    }
+}
diff --git a/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/osgi/OSGiCommands.java b/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/osgi/OSGiCommands.java
new file mode 100644
index 0000000..21197d6
--- /dev/null
+++ b/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/osgi/OSGiCommands.java
@@ -0,0 +1,415 @@
+/*
+ * 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.
+ */
+// DWB1: osgi:each too verbose (formats reults to System.out)
+// DWB2: ClassNotFoundException should be caught in convert() method
+package org.apache.felix.gogo.runtime.osgi;
+
+import org.osgi.framework.*;
+import org.osgi.service.command.CommandSession;
+import org.osgi.service.command.Converter;
+import org.osgi.service.command.Function;
+
+import java.io.*;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Formatter;
+import java.util.List;
+import java.util.regex.Pattern;
+
+public class OSGiCommands implements Converter
+{
+    Bundle bundle;
+    String COLUMN = "%40s %s\n";
+
+    protected OSGiCommands(Bundle bundle)
+    {
+        this.bundle = bundle;
+    }
+
+//	Bundle[] getBundles() {
+//		return getContext().getBundles();
+//	}
+
+    public BundleContext getContext()
+    {
+        if (bundle.getState() != Bundle.ACTIVE)
+        {
+            throw new IllegalStateException("Framework is not started yet");
+        }
+        return bundle.getBundleContext();
+    }
+
+    CharSequence print(Bundle bundle)
+    {
+        String version = (String) bundle.getHeaders().get("Bundle-Version");
+        if (version == null)
+        {
+            version = "0.0.0";
+        }
+        return String.format("%06d %s %s-%s", bundle.getBundleId(), getState(bundle), bundle.getSymbolicName(), version);
+    }
+
+    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;
+    }
+
+    CharSequence getShortNames(String[] list)
+    {
+        StringBuilder sb = new StringBuilder();
+        String del = "";
+        for (String s : list)
+        {
+            sb.append(del + getShortName(s));
+            del = " | ";
+        }
+        return sb;
+    }
+
+    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 "ACT";
+
+            case Bundle.INSTALLED:
+                return "INS";
+
+            case Bundle.RESOLVED:
+                return "RES";
+
+            case Bundle.STARTING:
+                return "STA";
+
+            case Bundle.STOPPING:
+                return "STO";
+
+            case Bundle.UNINSTALLED:
+                return "UNI ";
+        }
+        return null;
+    }
+
+    public void grep(String match) throws IOException
+    {
+        Pattern p = Pattern.compile(match);
+        BufferedReader rdr = new BufferedReader(new InputStreamReader(System.in));
+        String s = rdr.readLine();
+        while (s != null)
+        {
+            if (p.matcher(s).find())
+            {
+                System.out.println(s);
+            }
+            s = rdr.readLine();
+        }
+    }
+
+    public String tac() throws IOException
+    {
+        StringWriter sw = new StringWriter();
+        BufferedReader rdr = new BufferedReader(new InputStreamReader(System.in));
+        String s = rdr.readLine();
+        while (s != null)
+        {
+            sw.write(s);
+            s = rdr.readLine();
+        }
+        return sw.toString();
+    }
+
+    public CharSequence echo(CommandSession session, Object args[])
+    {
+        StringBuilder sb = new StringBuilder();
+        String del = "";
+        for (Object arg : args)
+        {
+            sb.append(del);
+            if (arg != null)
+            {
+                sb.append(arg);
+                del = " ";
+            }
+        }
+        return sb;
+    }
+
+    public void each(CommandSession session, Collection<Object> list, Function closure) throws Exception
+    {
+        List<Object> args = new ArrayList<Object>();
+        args.add(null);
+        for (Object x : list)
+        {
+            args.set(0, x);
+            //Object result = closure.execute(session, args);
+            // System.out.println(session.format(result,Converter.INSPECT));
+            // derek: this is way too noisy
+            closure.execute(session, args);
+        }
+    }
+
+    public Bundle bundle(Bundle i)
+    {
+        return i;
+    }
+
+    public String[] ls(CommandSession session, File f) throws Exception
+    {
+        File cwd = (File) session.get("_cwd");
+        if (cwd == null)
+        {
+            cwd = new File("").getAbsoluteFile();
+        }
+
+        if (f == null)
+        {
+            f = cwd;
+        }
+        else
+        {
+            if (!f.isAbsolute())
+            {
+                f = new File(cwd, f.getPath());
+            }
+        }
+
+        if (f.isDirectory())
+        {
+            return f.list();
+        }
+
+        if (f.isFile())
+        {
+            cat(session, f);
+        }
+
+        return null;
+    }
+
+    public Object cat(CommandSession session, File f) throws Exception
+    {
+        File cwd = (File) session.get("_cwd");
+        if (cwd == null)
+        {
+            cwd = new File("").getAbsoluteFile();
+        }
+
+        if (!f.isAbsolute())
+        {
+            f = new File(cwd, f.getPath());
+        }
+
+        ByteArrayOutputStream bout = new ByteArrayOutputStream();
+        FileInputStream in = new FileInputStream(f);
+        byte[] buffer = new byte[(int) (f.length() % 100000)];
+        int size = in.read(buffer);
+        while (size > 0)
+        {
+            bout.write(buffer, 0, size);
+            size = in.read(buffer);
+        }
+        return new String(bout.toByteArray());
+    }
+
+    public Object convert(Class<?> desiredType, Object in) throws Exception
+    {
+        if (desiredType == Bundle.class)
+        {
+            return convertBundle(in);
+        }
+        else
+        {
+            if (desiredType == ServiceReference.class)
+            {
+                return convertServiceReference(in);
+            }
+            else
+            {
+                if (desiredType == Class.class)
+                {
+                    // derek.baum@paremus.com - added try/catch
+                    try
+                    {
+                        return Class.forName(in.toString());
+                    }
+                    catch (ClassNotFoundException e)
+                    {
+                        return null;
+                    }
+                }
+                else
+                {
+                    if (desiredType.isAssignableFrom(String.class) && in instanceof InputStream)
+                    {
+                        return read(((InputStream) in));
+                    }
+                }
+            }
+        }
+
+        return null;
+    }
+
+    private Object convertServiceReference(Object in) throws InvalidSyntaxException
+    {
+        String s = in.toString();
+        if (s.startsWith("(") && s.endsWith(")"))
+        {
+            ServiceReference refs[] = getContext().getServiceReferences(null, String.format("(|(service.id=%s)(service.pid=%s))", in, in));
+            if (refs != null && refs.length > 0)
+            {
+                return refs[0];
+            }
+        }
+
+        ServiceReference refs[] = getContext().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 getContext().getBundle(id);
+        }
+        catch (NumberFormatException nfe)
+        {
+            // Ignore
+        }
+
+        Bundle bundles[] = getContext().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;
+    }
+
+    public 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;
+    }
+
+    public void start(Bundle b) throws BundleException
+    {
+        b.start();
+    }
+
+    public void stop(Bundle b) throws BundleException
+    {
+        b.stop();
+    }
+
+    public Object service(String clazz, String filter) throws InvalidSyntaxException
+    {
+        ServiceReference ref[] = getContext().getServiceReferences(clazz, filter);
+        if (ref == null)
+        {
+            return null;
+        }
+
+        return getContext().getService(ref[0]);
+    }
+
+}
diff --git a/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/osgi/OSGiShell.java b/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/osgi/OSGiShell.java
new file mode 100644
index 0000000..75bdcf6
--- /dev/null
+++ b/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/osgi/OSGiShell.java
@@ -0,0 +1,157 @@
+/*
+ * 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.
+ */
+// DWB3: dynamically load optional framework components to reduce dependencies
+// DWB4: get() with trailing colon causes org.osgi.framework.InvalidSyntaxException
+package org.apache.felix.gogo.runtime.osgi;
+
+import org.apache.felix.gogo.runtime.shell.CommandShellImpl;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.command.Converter;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.packageadmin.PackageAdmin;
+import org.osgi.service.threadio.ThreadIO;
+
+public class OSGiShell extends CommandShellImpl
+{
+    Bundle bundle;
+    OSGiCommands commands;
+
+    protected void activate(ComponentContext context) throws Exception
+    {
+        this.bundle = context.getBundleContext().getBundle();
+        if (threadIO == null)
+        {
+            threadIO = (ThreadIO) context.locateService("x");
+        }
+        start();
+    }
+
+    public void start() throws Exception
+    {
+        commands = new OSGiCommands(bundle);
+        addCommand("osgi", this.bundle);
+        addCommand("osgi", commands);
+        setConverter(commands);
+        if (bundle.getState() == Bundle.ACTIVE)
+        {
+            addCommand("osgi", commands.service(PackageAdmin.class.getName(), null), PackageAdmin.class);
+            addCommand("osgi", commands.getContext(), BundleContext.class);
+
+            try
+            {
+                // derek - dynamically load StartLevel to avoid import dependency
+                String sl = "org.osgi.service.startlevel.StartLevel";
+                Class<?> slClass = bundle.loadClass(sl);
+                addCommand("osgi", commands.service(sl, null), slClass);
+            }
+            catch (ClassNotFoundException e)
+            {
+            }
+
+            try
+            {
+                // derek - dynamically load PermissionAdmin to avoid import dependency
+                String pa = "org.osgi.service.permissionadmin.PermissionAdmin";
+                Class<?> paClass = bundle.loadClass(pa);
+                addCommand("osgi", commands.service(pa, null), paClass);
+            }
+            catch (ClassNotFoundException e)
+            {
+            }
+        }
+        else
+        {
+            System.err.println("eek! bundle not active: " + bundle);
+        }
+    }
+
+    protected void deactivate(ComponentContext context)
+    {
+        System.out.println("Deactivating");
+    }
+
+    public Object get(String name)
+    {
+        if (bundle.getBundleContext() != null)
+        {
+            BundleContext context = bundle.getBundleContext();
+            try
+            {
+                Object cmd = super.get(name);
+                if (cmd != null)
+                {
+                    return cmd;
+                }
+
+                int n = name.indexOf(':');
+                if (n < 0)
+                {
+                    return null;
+                }
+
+                String service = name.substring(0, n);
+                String function = name.substring(n + 1);
+
+                // derek - fix org.osgi.framework.InvalidSyntaxException
+                if (service.length() == 0 || function.length() == 0)
+                {
+                    return null;
+                }
+
+                String filter = String.format("(&(osgi.command.scope=%s)(osgi.command.function=%s))", service, function);
+                ServiceReference refs[] = context.getServiceReferences(null, filter);
+                if (refs == null || refs.length == 0)
+                {
+                    return null;
+                }
+
+                if (refs.length > 1)
+                {
+                    throw new IllegalArgumentException("Command name is not unambiguous: " + name + ", found multiple impls");
+                }
+
+                return new ServiceCommand(this, refs[0], function);
+            }
+            catch (InvalidSyntaxException ise)
+            {
+                ise.printStackTrace();
+            }
+        }
+        return super.get(name);
+    }
+
+    public void setThreadio(Object t)
+    {
+        super.setThreadio((ThreadIO) t);
+    }
+
+    public void setBundle(Bundle bundle)
+    {
+        this.bundle = bundle;
+    }
+
+    public void setConverter(Converter c)
+    {
+        super.setConverter(c);
+    }
+
+}
diff --git a/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/osgi/ServiceCommand.java b/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/osgi/ServiceCommand.java
new file mode 100644
index 0000000..c285d6b
--- /dev/null
+++ b/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/osgi/ServiceCommand.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.runtime.osgi;
+
+import org.apache.felix.gogo.runtime.shell.CommandShellImpl;
+import org.apache.felix.gogo.runtime.shell.Reflective;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.command.CommandSession;
+import org.osgi.service.command.Function;
+
+import java.util.List;
+
+public class ServiceCommand extends Reflective implements Function
+{
+    ServiceReference ref;
+    OSGiShell shell;
+    String name;
+
+    public ServiceCommand(OSGiShell shell, ServiceReference ref, String name)
+    {
+        this.shell = shell;
+        this.ref = ref;
+        this.name = name;
+    }
+
+    public Object execute(CommandSession session, List<Object> arguments) throws Exception
+    {
+        try
+        {
+            Object target = shell.bundle.getBundleContext().getService(ref);
+            Object result = method(session, target, name, arguments);
+            if (result != CommandShellImpl.NO_SUCH_COMMAND)
+            {
+                return result;
+            }
+
+            throw new IllegalArgumentException("Service does not implement promised command " + ref + " " + name);
+        }
+        finally
+        {
+            shell.bundle.getBundleContext().ungetService(ref);
+        }
+    }
+}
diff --git a/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/shell/Closure.java b/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/shell/Closure.java
new file mode 100644
index 0000000..e479bac
--- /dev/null
+++ b/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/shell/Closure.java
@@ -0,0 +1,325 @@
+/*
+ * 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.
+ */
+// DWB5: session.err is not redirected when creating pipeline
+// DWB6: add 'set -x' trace feature if echo is set
+// DWB7: removing variable via 'execute("name=") throws OutOfBoundsException
+package org.apache.felix.gogo.runtime.shell;
+
+import org.osgi.service.command.CommandSession;
+import org.osgi.service.command.Function;
+
+import java.util.*;
+
+public class Closure extends Reflective implements Function
+{
+    private static final long serialVersionUID = 1L;
+    final CharSequence source;
+    final Closure parent;
+    CommandSessionImpl session;
+    List<Object> parms;
+
+    Closure(CommandSessionImpl session, Closure parent, CharSequence source)
+    {
+        this.session = session;
+        this.parent = parent;
+        this.source = source;
+    }
+
+    public Object execute(CommandSession x, List<Object> values) throws Exception
+    {
+        parms = values;
+        Parser parser = new Parser(source);
+        ArrayList<Pipe> pipes = new ArrayList<Pipe>();
+        List<List<List<CharSequence>>> program = parser.program();
+
+        for (List<List<CharSequence>> statements : program)
+        {
+            Pipe current = new Pipe(this, statements);
+
+            if (pipes.isEmpty())
+            {
+                current.setIn(session.in);
+                current.setOut(session.out);
+                current.setErr(session.err);    // XXX: derek.baum@paremus.com
+            }
+            else
+            {
+                Pipe previous = pipes.get(pipes.size() - 1);
+                previous.connect(current);
+            }
+            pipes.add(current);
+        }
+        if (pipes.size() == 0)
+        {
+            return null;
+        }
+
+        if (pipes.size() == 1)
+        {
+            pipes.get(0).run();
+        }
+        else
+        {
+            for (Pipe pipe : pipes)
+            {
+                pipe.start();
+            }
+            for (Pipe pipe : pipes)
+            {
+                pipe.join();
+            }
+        }
+
+        Pipe last = pipes.get(pipes.size() - 1);
+        if (last.exception != null)
+        {
+            throw last.exception;
+        }
+
+        if (last.result instanceof Object[])
+        {
+            return Arrays.asList((Object[]) last.result);
+        }
+        return last.result;
+    }
+
+    Object executeStatement(List<CharSequence> statement) throws Exception
+    {
+        Object result;
+        List<Object> values = new ArrayList<Object>();
+        CharSequence statement0 = statement.remove(0);
+
+        // derek: FEATURE: add set -x facility if echo is set
+        StringBuilder buf = new StringBuilder("+ ");
+        buf.append(statement0);
+
+        Object cmd = eval(statement0);
+        for (CharSequence token : statement)
+        {
+            buf.append(' ');
+            buf.append(token);
+            values.add(eval(token));
+        }
+
+        if (Boolean.TRUE.equals(session.get("echo")))
+        {
+            System.err.println(buf);
+        }
+
+        result = execute(cmd, values);
+        return result;
+    }
+
+    private Object execute(Object cmd, List<Object> values) throws Exception
+    {
+        if (cmd == null)
+        {
+            if (values.isEmpty())
+            {
+                return null;
+            }
+            else
+            {
+                throw new IllegalArgumentException("Command name evaluates to null");
+            }
+        }
+
+        // Now there are the following cases
+        // <string> '=' statement // complex assignment
+        // <string> statement // cmd call
+        // <object> // value of <object>
+        // <object> statement // method call
+
+        if (cmd instanceof CharSequence)
+        {
+            String scmd = cmd.toString();
+
+            if (values.size() > 0 && "=".equals(values.get(0)))
+            {
+                //if (values.size() == 0)
+                if (values.size() == 1)            // derek: BUGFIX
+                {
+                    return session.variables.remove(scmd);
+                }
+                else
+                {
+                    Object value = execute(values.get(1), values.subList(2, values.size()));
+                    return assignment(scmd, value);
+                }
+            }
+            else
+            {
+                String scopedFunction = scmd;
+                Object x = get(scmd);
+                if (!(x instanceof Function))
+                {
+                    if (scmd.indexOf(':') < 0)
+                    {
+                        scopedFunction = "*:" + scmd;
+                    }
+                    x = get(scopedFunction);
+                    if (x == null || !(x instanceof Function))
+                    {
+                        if (values.isEmpty())
+                        {
+                            return scmd;
+                        }
+                        throw new IllegalArgumentException("Command not found:  " + scopedFunction);
+                    }
+                }
+                return ((Function) x).execute(session, values);
+            }
+        }
+        else
+        {
+            if (values.isEmpty())
+            {
+                return cmd;
+            }
+            else
+            {
+                return method(session, cmd, values.remove(0).toString(), values);
+            }
+        }
+    }
+
+    private Object assignment(Object name, Object value)
+    {
+        session.variables.put(name, value);
+        return value;
+    }
+
+    private Object eval(CharSequence seq) throws Exception
+    {
+        int end = seq.length();
+        switch (seq.charAt(0))
+        {
+            case '$':
+                return var(seq);
+            case '<':
+                Closure c = new Closure(session, this, seq.subSequence(1, end - 1));
+                return c.execute(session, parms);
+            case '[':
+                return array(seq.subSequence(1, end - 1));
+
+            case '{':
+                return new Closure(session, this, seq.subSequence(1, end - 1));
+
+            default:
+                String result = new Parser(seq).unescape();
+                if ("null".equals(result))
+                {
+                    return null;
+                }
+                if ("true".equalsIgnoreCase(result))
+                {
+                    return true;
+                }
+                if ("false".equalsIgnoreCase(result))
+                {
+                    return false;
+                }
+                return seq;
+        }
+    }
+
+    private Object array(CharSequence array) throws Exception
+    {
+        List<Object> list = new ArrayList<Object>();
+        Map<Object, Object> map = new LinkedHashMap<Object, Object>();
+        Parser p = new Parser(array);
+
+        while (!p.eof())
+        {
+            CharSequence token = p.value();
+
+            p.ws();
+            if (p.peek() == '=')
+            {
+                p.next();
+                p.ws();
+                if (!p.eof())
+                {
+                    CharSequence value = p.messy();
+                    map.put(eval(token), eval(value));
+                }
+            }
+            else
+            {
+                list.add(eval(token));
+            }
+
+            if (p.peek() == ',')
+            {
+                p.next();
+            }
+            p.ws();
+        }
+        p.ws();
+        if (!p.eof())
+        {
+            throw new IllegalArgumentException("Invalid array syntax: " + array);
+        }
+
+        if (map.size() != 0 && list.size() != 0)
+        {
+            throw new IllegalArgumentException("You can not mix maps and arrays: " + array);
+        }
+
+        if (map.size() > 0)
+        {
+            return map;
+        }
+        else
+        {
+            return list;
+        }
+    }
+
+    private Object var(CharSequence var) throws Exception
+    {
+        String name = eval(var.subSequence(1, var.length())).toString();
+        return get(name);
+    }
+
+    /**
+     * @param name
+     * @return
+     */
+    private Object get(String name)
+    {
+        if (parms != null)
+        {
+            if ("it".equals(name))
+            {
+                return parms.get(0);
+            }
+            if ("args".equals(name))
+            {
+                return parms;
+            }
+
+            if (name.length() == 1 && Character.isDigit(name.charAt(0)))
+            {
+                return parms.get(name.charAt(0) - '0');
+            }
+        }
+        return session.get(name);
+    }
+}
diff --git a/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/shell/Command.java b/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/shell/Command.java
new file mode 100644
index 0000000..2d5f7d0
--- /dev/null
+++ b/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/shell/Command.java
@@ -0,0 +1,42 @@
+/*
+ * 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.runtime.shell;
+
+import org.osgi.service.command.CommandSession;
+import org.osgi.service.command.Function;
+
+import java.util.List;
+
+public class Command extends Reflective implements Function
+{
+    Object target;
+    String function;
+
+    public Command(Object target, String function)
+    {
+        this.function = function;
+        this.target = target;
+    }
+
+    public Object execute(CommandSession session, List<Object> arguments) throws Exception
+    {
+        return method(session, target, function, arguments);
+    }
+
+}
diff --git a/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/shell/CommandSessionImpl.java b/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/shell/CommandSessionImpl.java
new file mode 100644
index 0000000..c56142e
--- /dev/null
+++ b/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/shell/CommandSessionImpl.java
@@ -0,0 +1,343 @@
+/*
+ * 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.
+ */
+// DWB8: throw IllegatlStateException if session used after closed (as per rfc132)
+// DWB9: there is no API to list all variables: https://www.osgi.org/bugzilla/show_bug.cgi?id=49
+// DWB10: add SCOPE support: https://www.osgi.org/bugzilla/show_bug.cgi?id=51
+package org.apache.felix.gogo.runtime.shell;
+
+import org.osgi.service.command.CommandSession;
+import org.osgi.service.command.Converter;
+
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.*;
+
+public class CommandSessionImpl implements CommandSession, Converter
+{
+    String COLUMN = "%-20s %s\n";
+    InputStream in;
+    PrintStream out;
+    PrintStream err;
+    CommandShellImpl service;
+    Map<Object, Object> variables = new HashMap<Object, Object>();
+    private boolean closed;    // derek
+
+    CommandSessionImpl(CommandShellImpl service, InputStream in, PrintStream out, PrintStream err)
+    {
+        this.service = service;
+        this.in = in;
+        this.out = out;
+        this.err = err;
+    }
+
+    public void close()
+    {
+        this.closed = true;    // derek
+    }
+
+    public Object execute(CharSequence commandline) throws Exception
+    {
+        assert service != null;
+        assert service.threadIO != null;
+
+        if (closed)
+        {
+            throw new IllegalStateException("session is closed");    // derek
+        }
+
+        Closure impl = new Closure(this, null, commandline);
+        Object result = impl.execute(this, null);
+        return result;
+    }
+
+    public InputStream getKeyboard()
+    {
+        return in;
+    }
+
+    public Object get(String name)
+    {
+        // XXX: derek.baum@paremus.com
+        // there is no API to list all variables, so overload name == null
+        if (name == null)
+        {
+            return variables.keySet();
+        }
+
+        if (variables != null && variables.containsKey(name))
+        {
+            return variables.get(name);
+        }
+
+        // XXX: derek: add SCOPE support
+        if (name.startsWith("*:"))
+        {
+            String path = variables.containsKey("SCOPE") ? variables.get("SCOPE").toString() : "osgi:*";
+            String func = name.substring(2);
+            for (String scope : path.split(":"))
+            {
+                Object result = service.get(scope + ":" + func);
+                if (result != null)
+                {
+                    return result;
+                }
+            }
+            return null;
+        }
+        return service.get(name);
+    }
+
+    public void put(String name, Object value)
+    {
+        variables.put(name, value);
+    }
+
+    public PrintStream getConsole()
+    {
+        return out;
+    }
+
+    @SuppressWarnings("unchecked")
+    public CharSequence format(Object target, int level, Converter escape) throws Exception
+    {
+        if (target == null)
+        {
+            return "null";
+        }
+
+        if (target instanceof CharSequence)
+        {
+            return (CharSequence) target;
+        }
+
+        for (Converter c : service.converters)
+        {
+            CharSequence s = c.format(target, level, this);
+            if (s != null)
+            {
+                return s;
+            }
+        }
+
+        if (target.getClass().isArray())
+        {
+            if (target.getClass().getComponentType().isPrimitive())
+            {
+                if (target.getClass().getComponentType() == boolean.class)
+                {
+                    return Arrays.toString((boolean[]) target);
+                }
+                else
+                {
+                    if (target.getClass().getComponentType() == byte.class)
+                    {
+                        return Arrays.toString((byte[]) target);
+                    }
+                    else
+                    {
+                        if (target.getClass().getComponentType() == short.class)
+                        {
+                            return Arrays.toString((short[]) target);
+                        }
+                        else
+                        {
+                            if (target.getClass().getComponentType() == int.class)
+                            {
+                                return Arrays.toString((int[]) target);
+                            }
+                            else
+                            {
+                                if (target.getClass().getComponentType() == long.class)
+                                {
+                                    return Arrays.toString((long[]) target);
+                                }
+                                else
+                                {
+                                    if (target.getClass().getComponentType() == float.class)
+                                    {
+                                        return Arrays.toString((float[]) target);
+                                    }
+                                    else
+                                    {
+                                        if (target.getClass().getComponentType() == double.class)
+                                        {
+                                            return Arrays.toString((double[]) target);
+                                        }
+                                        else
+                                        {
+                                            if (target.getClass().getComponentType() == char.class)
+                                            {
+                                                return Arrays.toString((char[]) target);
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            target = Arrays.asList((Object[]) target);
+        }
+        if (target instanceof Collection)
+        {
+            if (level == Converter.INSPECT)
+            {
+                StringBuilder sb = new StringBuilder();
+                Collection<?> c = (Collection<?>) target;
+                for (Object o : c)
+                {
+                    sb.append(format(o, level + 1, this));
+                    sb.append("\n");
+                }
+                return sb;
+            }
+            else
+            {
+                if (level == Converter.LINE)
+                {
+                    StringBuilder sb = new StringBuilder();
+                    String del = "[";
+                    Collection<?> c = (Collection<?>) target;
+                    for (Object o : c)
+                    {
+                        sb.append(del);
+                        sb.append(format(o, level + 1, this));
+                        del = ", ";
+                    }
+                    sb.append("]");
+                    return sb;
+                }
+            }
+        }
+        if (target instanceof Dictionary)
+        {
+            Map<Object, Object> result = new HashMap<Object, Object>();
+            for (Enumeration e = ((Dictionary) target).keys(); e.hasMoreElements();)
+            {
+                Object key = e.nextElement();
+                result.put(key, ((Dictionary) target).get(key));
+            }
+            target = result;
+        }
+        if (target instanceof Map)
+        {
+            if (level == Converter.INSPECT)
+            {
+                StringBuilder sb = new StringBuilder();
+                Map<?, ?> c = (Map<?, ?>) target;
+                for (Map.Entry<?, ?> entry : c.entrySet())
+                {
+                    CharSequence key = format(entry.getKey(), level + 1, this);
+                    sb.append(key);
+                    for (int i = key.length(); i < 20; i++)
+                    {
+                        sb.append(' ');
+                    }
+                    sb.append(format(entry.getValue(), level + 1, this));
+                    sb.append("\n");
+                }
+                return sb;
+            }
+            else
+            {
+                if (level == Converter.LINE)
+                {
+                    StringBuilder sb = new StringBuilder();
+                    String del = "[";
+                    Map<?, ?> c = (Map<?, ?>) target;
+                    for (Map.Entry<?, ?> entry : c.entrySet())
+                    {
+                        sb.append(del);
+                        sb.append(format(entry, level + 1, this));
+                        del = ", ";
+                    }
+                    sb.append("]");
+                    return sb;
+                }
+            }
+        }
+        if (level == Converter.INSPECT)
+        {
+            return inspect(target);
+        }
+        else
+        {
+            return target.toString();
+        }
+    }
+
+    CharSequence inspect(Object b)
+    {
+        boolean found = false;
+        Formatter f = new Formatter();
+        Method methods[] = b.getClass().getMethods();
+        for (Method m : methods)
+        {
+            try
+            {
+                String name = m.getName();
+                if (m.getName().startsWith("get") && !m.getName().equals("getClass") && m.getParameterTypes().length == 0 && Modifier.isPublic(m.getModifiers()))
+                {
+                    found = true;
+                    name = name.substring(3);
+                    m.setAccessible(true);
+                    Object value = m.invoke(b, (Object[]) null);
+                    f.format(COLUMN, name, format(value, Converter.LINE, this));
+                }
+            }
+            catch (IllegalAccessException e)
+            {
+                // Ignore
+            }
+            catch (Exception e)
+            {
+                e.printStackTrace();
+            }
+        }
+        if (found)
+        {
+            return (StringBuilder) f.out();
+        }
+        else
+        {
+            return b.toString();
+        }
+    }
+
+    public Object convert(Class<?> desiredType, Object in)
+    {
+        return service.convert(desiredType, in);
+    }
+
+    public CharSequence format(Object result, int inspect)
+    {
+        try
+        {
+            return format(result, inspect, this);
+        }
+        catch (Exception e)
+        {
+            return "<can not format " + result + ":" + e;
+        }
+    }
+
+}
diff --git a/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/shell/CommandShellImpl.java b/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/shell/CommandShellImpl.java
new file mode 100644
index 0000000..5b8d6b1
--- /dev/null
+++ b/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/shell/CommandShellImpl.java
@@ -0,0 +1,220 @@
+/*
+ * 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.
+ */
+// DWB11: add removeCommand: https://www.osgi.org/bugzilla/show_bug.cgi?id=49
+// DWB12: there is no API to list commands: https://www.osgi.org/bugzilla/show_bug.cgi?id=49
+// DWB13: addCommand() fails to add static methods (if target is Class)
+package org.apache.felix.gogo.runtime.shell;
+
+import org.osgi.service.command.CommandProcessor;
+import org.osgi.service.command.CommandSession;
+import org.osgi.service.command.Converter;
+import org.osgi.service.command.Function;
+import org.osgi.service.threadio.ThreadIO;
+
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.lang.reflect.Method;
+import java.util.*;
+
+public class CommandShellImpl implements CommandProcessor
+{
+    Set<Converter> converters = new HashSet<Converter>();
+    protected ThreadIO threadIO;
+    public final static Object NO_SUCH_COMMAND = new Object();
+    Map<String, Object> commands = new LinkedHashMap<String, Object>();
+
+    public CommandShellImpl()
+    {
+        addCommand("shell", this, "addCommand");
+        addCommand("shell", this, "removeCommand");    // derek
+    }
+
+    public CommandSession createSession(InputStream in, PrintStream out, PrintStream err)
+    {
+
+        return new CommandSessionImpl(this, in, out, err);
+    }
+
+    public void setThreadio(ThreadIO threadIO)
+    {
+        this.threadIO = threadIO;
+    }
+
+    public void setConverter(Converter c)
+    {
+        converters.add(c);
+    }
+
+    public void unsetConverter(Converter c)
+    {
+        converters.remove(c);
+    }
+
+    public Object get(String name)
+    {
+        name = name.toLowerCase();
+        int n = name.indexOf(':');
+        if (n < 0)
+        {
+            return null;
+        }
+
+        String function = name.substring(n);
+
+        Object cmd = null;
+
+        if (commands.containsKey(name))
+        {
+            cmd = commands.get(name);
+        }
+        else
+        {
+            String scope = name.substring(0, n);
+            if (scope.equals("*"))
+            {
+                for (Map.Entry<String, Object> entry : commands.entrySet())
+                {
+                    if (entry.getKey().endsWith(function))
+                    {
+                        cmd = entry.getValue();
+                        break;
+                    }
+                }
+            }
+
+            // XXX: derek.baum@paremus.com
+            // there is no API to list commands
+            // so override otherwise illegal name ":"
+            if (cmd == null && name.equals(":"))
+            {
+                return Collections.unmodifiableSet(commands.keySet());
+            }
+        }
+
+        if (cmd == null)
+        {
+            return null;
+        }
+
+        if (cmd instanceof Function)
+        {
+            return cmd;
+        }
+        else
+        {
+            return new Command(cmd, function.substring(1));
+        }
+    }
+
+    public void addCommand(String scope, Object target)
+    {
+        // derek - fix target class
+        Class<?> tc = (target instanceof Class) ? (Class<?>) target : target.getClass();
+        addCommand(scope, target, tc);
+    }
+
+    public void addCommand(String scope, Object target, Class<?> functions)
+    {
+        if (target == null)
+        {
+            return;
+        }
+
+        String[] names = getFunctions(functions);
+        for (String function : names)
+        {
+            addCommand(scope, target, function);
+        }
+    }
+
+    public void addCommand(String scope, Object target, String function)
+    {
+        commands.put((scope + ":" + function).toLowerCase(), target);
+    }
+
+    // derek.baum@paremus.com: need removeCommand, so stopped bundles can clean up.
+    public void removeCommand(String scope, String function)
+    {
+        String func = (scope + ":" + function).toLowerCase();
+        commands.remove(func);
+    }
+
+    public void removeCommand(Object target)
+    {
+        for (Iterator<Object> i = commands.values().iterator(); i.hasNext();)
+        {
+            if (i.next() == target)
+            {
+                i.remove();
+            }
+        }
+    }
+
+    private String[] getFunctions(Class<?> target)
+    {
+        String[] functions;
+        Set<String> list = new TreeSet<String>();
+        Method methods[] = target.getMethods();
+        for (Method m : methods)
+        {
+            if (m.getDeclaringClass().equals(Object.class))    // derek
+            {
+                continue;
+            }
+            list.add(m.getName());
+            if (m.getName().startsWith("get"))
+            {
+                String s = m.getName().substring(3);
+                if (s.length() > 0)
+                {
+                    list.add(s.substring(0, 1).toLowerCase() + s.substring(1));
+                }
+            }
+        }
+
+        functions = list.toArray(new String[list.size()]);
+        return functions;
+    }
+
+    protected void put(String name, Object target)
+    {
+        commands.put(name, target);
+    }
+
+    public Object convert(Class<?> desiredType, Object in)
+    {
+        for (Converter c : converters)
+        {
+            try
+            {
+                Object converted = c.convert(desiredType, in);
+                if (converted != null)
+                {
+                    return converted;
+                }
+            }
+            catch (Exception e)
+            {
+                e.printStackTrace();
+            }
+        }
+        return null;
+    }
+
+}
diff --git a/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/shell/Context.java b/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/shell/Context.java
new file mode 100644
index 0000000..c14a790
--- /dev/null
+++ b/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/shell/Context.java
@@ -0,0 +1,52 @@
+/*
+ * 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.runtime.shell;
+
+import org.apache.felix.gogo.runtime.threadio.ThreadIOImpl;
+
+public class Context extends CommandShellImpl
+{
+    public static final String EMPTY = "";
+    CommandSessionImpl session = (CommandSessionImpl) createSession(System.in, System.out, System.err);
+    static ThreadIOImpl threadio;
+
+    static
+    {
+        threadio = new ThreadIOImpl();
+        threadio.start();
+
+    }
+
+    public Context()
+    {
+        setThreadio(threadio);
+    }
+
+    public Object execute(CharSequence source) throws Exception
+    {
+        return session.execute(source);
+    }
+
+    public void addCommand(String name, Object target)
+    {
+        put("test:" + name, target);
+    }
+
+
+}
diff --git a/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/shell/Parser.java b/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/shell/Parser.java
new file mode 100644
index 0000000..6d6e7cd
--- /dev/null
+++ b/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/shell/Parser.java
@@ -0,0 +1,384 @@
+/*
+ * 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.
+ */
+// DWB14: parser loops if // comment at start of program
+// DWB15: allow program to have trailing ';'
+package org.apache.felix.gogo.runtime.shell;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Parser
+{
+    int current = 0;
+    CharSequence text;
+    boolean escaped;
+    static final String SPECIAL = "<;|{[\"'$'`(=";
+
+    public Parser(CharSequence program)
+    {
+        text = program;
+    }
+
+    void ws()
+    {
+        // derek: BUGFIX: loop if comment  at beginning of input
+        //while (!eof() && Character.isWhitespace(peek())) {
+        while (!eof() && (Character.isWhitespace(peek()) || current == 0))
+        {
+            if (current != 0 || Character.isWhitespace(peek()))
+            {
+                current++;
+            }
+            if (peek() == '/' && current < text.length() - 2 && text.charAt(current + 1) == '/')
+            {
+                comment();
+            }
+            if (current == 0)
+            {
+                break;
+            }
+        }
+    }
+
+    private void comment()
+    {
+        while (!eof() && peek() != '\n' && peek() != '\r')
+        {
+            next();
+        }
+    }
+
+    boolean eof()
+    {
+        return current >= text.length();
+    }
+
+    char peek()
+    {
+        escaped = false;
+        if (eof())
+        {
+            return 0;
+        }
+
+        char c = text.charAt(current);
+
+        if (c == '\\')
+        {
+            escaped = true;
+            ++current;
+            if (eof())
+            {
+                throw new RuntimeException("Eof found after \\"); // derek
+            }
+
+            c = text.charAt(current);
+
+            switch (c)
+            {
+                case 't':
+                    c = '\t';
+                    break;
+                case '\r':
+                case '\n':
+                    c = ' ';
+                    break;
+                case 'b':
+                    c = '\b';
+                    break;
+                case 'f':
+                    c = '\f';
+                    break;
+                case 'n':
+                    c = '\n';
+                    break;
+                case 'r':
+                    c = '\r';
+                    break;
+                case 'u':
+                    c = unicode();
+                    break;
+                default:
+                    // We just take the next character literally
+                    // but have the escaped flag set, important for {},[] etc
+            }
+        }
+        return c;
+    }
+
+    public List<List<List<CharSequence>>> program()
+    {
+        List<List<List<CharSequence>>> program = new ArrayList<List<List<CharSequence>>>();
+        ws();
+        if (!eof())
+        {
+            program.add(statements());
+            while (peek() == '|')
+            {
+                current++;
+                program.add(statements());
+            }
+        }
+        if (!eof())
+        {
+            throw new RuntimeException("Program has trailing text: " + context(current));
+        }
+
+        return program;
+    }
+
+    CharSequence context(int around)
+    {
+        return text.subSequence(Math.max(0, current - 20), Math.min(text.length(), current + 4));
+    }
+
+    public List<List<CharSequence>> statements()
+    {
+        List<List<CharSequence>> statements = new ArrayList<List<CharSequence>>();
+        statements.add(statement());
+        while (peek() == ';')
+        {
+            current++;
+            // derek: BUGFIX: allow trailing ;
+            ws();
+            if (!eof())
+            {
+                statements.add(statement());
+            }
+        }
+        return statements;
+    }
+
+    public List<CharSequence> statement()
+    {
+        List<CharSequence> statement = new ArrayList<CharSequence>();
+        statement.add(value());
+        while (!eof())
+        {
+            ws();
+            if (peek() == '|' || peek() == ';')
+            {
+                break;
+            }
+
+            if (!eof())
+            {
+                statement.add(messy());
+            }
+        }
+        return statement;
+    }
+
+    public CharSequence messy()
+    {
+        char c = peek();
+        if (c > 0 && SPECIAL.indexOf(c) < 0)
+        {
+            int start = current++;
+            while (!eof())
+            {
+                c = peek();
+                if (c == ';' || c == '|' || Character.isWhitespace(c))
+                {
+                    break;
+                }
+                next();
+            }
+
+            return text.subSequence(start, current);
+        }
+        else
+        {
+            return value();
+        }
+    }
+
+    CharSequence value()
+    {
+        ws();
+
+        int start = current;
+        char c = next();
+        switch (c)
+        {
+            case '{':
+                return text.subSequence(start, find('}', '{'));
+            case '(':
+                return text.subSequence(start, find(')', '('));
+            case '[':
+                return text.subSequence(start, find(']', '['));
+            case '"':
+                return text.subSequence(start + 1, quote('"'));
+            case '\'':
+                return text.subSequence(start + 1, quote('\''));
+            case '<':
+                return text.subSequence(start, find('>', '<'));
+            case '$':
+                value();
+                return text.subSequence(start, current);
+        }
+
+        if (Character.isJavaIdentifierPart(c))
+        {
+            // Some identifier or number
+            while (!eof())
+            {
+                c = peek();
+                if (c != ':' && !Character.isJavaIdentifierPart(c) && c != '.')
+                {
+                    break;
+                }
+                next();
+            }
+        }
+        else
+        {
+            // Operator, repeat while in operator class
+            while (!eof())
+            {
+                c = peek();
+                if (Character.isWhitespace(c) || Character.isJavaIdentifierPart(c))
+                {
+                    break;
+                }
+            }
+        }
+
+        return text.subSequence(start, current);
+    }
+
+    char next()
+    {
+        char c = peek();
+        current++;
+        return c;
+    }
+
+    char unicode()
+    {
+        if (current + 4 > text.length())
+        {
+            throw new IllegalArgumentException("Unicode \\u escape at eof at pos ..." + context(current) + "...");
+        }
+
+        String s = text.subSequence(current, current + 4).toString();
+        int n = Integer.parseInt(s, 16);
+        return (char) n;
+    }
+
+    private int find(char target, char deeper)
+    {
+        int start = current;
+        int level = 1;
+
+        while (level != 0)
+        {
+            if (eof())
+            {
+                throw new RuntimeException("Eof found in the middle of a compound for '" + target + deeper + "', begins at " + context(start));
+            }
+
+            char c = next();
+            if (!escaped)
+            {
+                if (c == target)
+                {
+                    level--;
+                }
+                else
+                {
+                    if (c == deeper)
+                    {
+                        level++;
+                    }
+                    else
+                    {
+                        if (c == '"')
+                        {
+                            quote('"');
+                        }
+                        else
+                        {
+                            if (c == '\'')
+                            {
+                                quote('\'');
+                            }
+                            else
+                            {
+                                if (c == '`')
+                                {
+                                    quote('`');
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        return current;
+    }
+
+    int quote(char which)
+    {
+        while (!eof() && (peek() != which || escaped))
+        {
+            next();
+        }
+
+        return current++;
+    }
+
+    CharSequence findVar()
+    {
+        int start = current - 1;
+        char c = peek();
+
+        if (c == '{')
+        {
+            next();
+            int end = find('}', '{');
+            return text.subSequence(start, end);
+        }
+
+        if (Character.isJavaIdentifierStart(c))
+        {
+            while (!eof() && Character.isJavaIdentifierPart(c) || c == '.')
+            {
+                next();
+            }
+            return text.subSequence(start, current);
+        }
+        throw new IllegalArgumentException("Reference to variable does not match syntax of a variable: " + context(start));
+    }
+
+    public String toString()
+    {
+        return "..." + context(current) + "...";
+    }
+
+    public String unescape()
+    {
+        StringBuilder sb = new StringBuilder();
+        while (!eof())
+        {
+            sb.append(next());
+        }
+        return sb.toString();
+    }
+}
diff --git a/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/shell/Pipe.java b/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/shell/Pipe.java
new file mode 100644
index 0000000..3b1b253
--- /dev/null
+++ b/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/shell/Pipe.java
@@ -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.
+ */
+// DWB16: redirect System.err when creating pipe
+package org.apache.felix.gogo.runtime.shell;
+
+import org.osgi.service.command.Converter;
+
+import java.io.*;
+import java.util.List;
+
+public class Pipe extends Thread
+{
+    InputStream in;
+    PrintStream out;
+    PrintStream err;    // derek
+    PipedOutputStream pout;
+    Closure closure;
+    Exception exception;
+    Object result;
+    List<List<CharSequence>> statements;
+
+    public Pipe(Closure closure, List<List<CharSequence>> statements)
+    {
+        super("pipe-" + statements);
+        this.closure = closure;
+        this.statements = statements;
+    }
+
+    public void setIn(InputStream in)
+    {
+        this.in = in;
+    }
+
+    public void setOut(PrintStream out)
+    {
+        this.out = out;
+    }
+
+    public void setErr(PrintStream err)
+    {
+        this.err = err;
+    }
+
+    public Pipe connect(Pipe next) throws IOException
+    {
+        next.setOut(out);
+        next.setErr(err);
+        pout = new PipedOutputStream();
+        next.setIn(new PipedInputStream(pout));
+        out = new PrintStream(pout);
+        return next;
+
+    }
+
+    public void run()
+    {
+        //closure.session.service.threadIO.setStreams(in, out, System.err);
+        closure.session.service.threadIO.setStreams(in, out, err);    // derek
+        try
+        {
+            for (List<CharSequence> statement : statements)
+            {
+                result = closure.executeStatement(statement);
+                if (result != null && pout != null)
+                {
+                    out.println(closure.session.format(result, Converter.INSPECT));
+                }
+            }
+        }
+        catch (Exception e)
+        {
+            exception = e;
+        }
+        finally
+        {
+            out.flush();
+            closure.session.service.threadIO.close();
+            try
+            {
+                if (in instanceof PipedInputStream)
+                {
+                    in.close();
+                }
+                if (pout != null)
+                {
+                    pout.close();
+                }
+            }
+            catch (IOException e)
+            {
+                e.printStackTrace();
+            }
+        }
+    }
+}
diff --git a/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/shell/Reflective.java b/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/shell/Reflective.java
new file mode 100644
index 0000000..eeab41a
--- /dev/null
+++ b/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/shell/Reflective.java
@@ -0,0 +1,367 @@
+/*
+* 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.
+ */
+// DWB16: coerce() doesn't support static methods
+// DWB17: coerce() doesn't support static void main(String[]) in rfc132
+// DWB18: coerce() doesn't extract cause from InvocationTargetException
+// DWB19: coerce() won't add empty array to satisfy Object[] argument
+package org.apache.felix.gogo.runtime.shell;
+
+import org.osgi.service.command.CommandSession;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.*;
+
+public class Reflective
+{
+    public final static Object NO_MATCH = new Object();
+    public 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"}));
+
+    public Object method(CommandSession session, Object target, String name, List<Object> args) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, Exception
+    {
+        // derek - support static methods
+        //Method[] methods = target.getClass().getMethods();
+        Class<?> tc = (target instanceof Class) ? (Class<?>) target : target.getClass();
+        Method[] methods = tc.getMethods();
+        name = name.toLowerCase();
+
+        String get = "get" + name;
+        String is = "is" + name;
+        String set = "set" + name;
+
+        if (KEYWORDS.contains(name))
+        {
+            name = "_" + name;
+        }
+
+        Method bestMethod = null;
+        Object[] bestArgs = null;
+        int match = -1;
+        ArrayList<Class<?>[]> possibleTypes = new ArrayList<Class<?>[]>();    // derek
+
+        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"))
+            {    // derek - added _main
+                Class<?>[] types = m.getParameterTypes();
+                ArrayList<Object> xargs = new ArrayList<Object>(args); // derek - BUGFIX don't modify args
+
+                // Check if the command takes a session
+                if (types.length > 0 && CommandSession.class.isAssignableFrom(types[0]))
+                {
+                    xargs.add(0, session);
+                }
+
+                Object[] parms = new Object[types.length];
+                // if (types.length >= args.size() ) {
+                int local = coerce(session, target, types, parms, xargs);
+                if ((local >= xargs.size()) && (local >= types.length))
+                {       // derek - stop no-args
+                    boolean exact = (local == xargs.size() && local == types.length);
+                    if (exact || local > match)
+                    {
+                        bestMethod = m;
+                        bestArgs = parms;
+                        match = local;
+                    }
+                    if (exact)
+                    {
+                        break;
+                    }
+                }
+                else
+                {
+                    possibleTypes.add(types);    // derek
+                }
+                // }
+                // if (match == -1 && types.length == 1
+                // && types[0] == Object[].class) {
+                // bestMethod = m;
+                // Object value = args.toArray();
+                // bestArgs = new Object[] { value };
+                // }
+            }
+        }
+
+        if (bestMethod != null)
+        {
+            bestMethod.setAccessible(true);
+            // derek: BUGFIX catch InvocationTargetException
+            // return bestMethod.invoke(target, bestArgs);
+            try
+            {
+                return bestMethod.invoke(target, bestArgs);
+            }
+            catch (InvocationTargetException e)
+            {
+                Throwable cause = e.getCause();
+                if (cause instanceof Exception)
+                {
+                    throw (Exception) cause;
+                }
+                throw e;
+            }
+        }
+        else
+        {
+            //throw new IllegalArgumentException("Cannot find command:" + name + " with args:" + args);
+            // { derek
+            ArrayList<String> list = new ArrayList<String>();
+            for (Class<?>[] types : possibleTypes)
+            {
+                StringBuilder buf = new StringBuilder();
+                buf.append('(');
+                for (Class<?> type : types)
+                {
+                    if (buf.length() > 1)
+                    {
+                        buf.append(", ");
+                    }
+                    buf.append(type.getSimpleName());
+                }
+                buf.append(')');
+                list.add(buf.toString());
+            }
+
+            throw new IllegalArgumentException(String.format("Cannot coerce %s%s to any of %s", name, args, list));
+            // } derek
+        }
+    }
+
+    /**
+     * Complex routein to convert the arguments given from the command line to
+     * the arguments of the method call. First, an attempt is made to convert
+     * each argument. If this fails, a check is made to see if varargs can be
+     * applied. This happens when the last method argument is an array.
+     *
+     * @param session
+     * @param target
+     * @param types
+     * @param out
+     * @param in
+     * @return
+     * @throws Exception
+     */
+    @SuppressWarnings("unchecked")
+    private int coerce(CommandSession session, Object target, Class<?> types[], Object out[], List<Object> in) throws Exception
+    {
+        int i = 0;
+        while (i < out.length)
+        {
+            out[i] = null;
+            try
+            {
+                // Try to convert one argument
+                // derek: add empty array as extra argument
+                //out[i] = coerce(session, target, types[i], in.get(i));
+                if (i == in.size())
+                {
+                    out[i] = NO_MATCH;
+                }
+                else
+                {
+                    out[i] = coerce(session, target, types[i], in.get(i));
+                }
+
+                if (out[i] == NO_MATCH)
+                {
+                    // Failed
+                    // No match, check for varargs
+                    if (types[i].isArray() && i == types.length - 1)
+                    {
+
+                        // derek - expand final array arg
+                        if (i < in.size())
+                        {
+                            Object arg = in.get(i);
+                            if (arg instanceof List)
+                            {
+                                List<Object> args = (List<Object>) arg;
+                                in = new ArrayList<Object>(in);
+                                in.remove(i);
+                                in.addAll(args);
+                            }
+                        }
+
+                        // Try to parse the remaining arguments in an array
+                        Class<?> component = types[i].getComponentType();
+                        Object components = Array.newInstance(component, in.size() - i);
+                        int n = i;
+                        while (i < in.size())
+                        {
+                            Object t = coerce(session, target, component, in.get(i));
+                            if (t == NO_MATCH)
+                            {
+                                return -1;
+                            }
+                            Array.set(components, i - n, t);
+                            i++;
+                        }
+                        out[n] = components;
+                        // Is last element, so we will quite hereafter
+                        // return n;
+                        if (i == in.size())
+                        {
+                            ++i;
+                        }
+                        return i;    // derek - return number of args converted
+                    }
+                    return -1;
+                }
+                i++;
+            }
+            catch (Exception e)
+            {
+                System.err.println("Reflective:" + e);
+                e.printStackTrace();
+
+                // should get rid of those exceptions, but requires
+                // reg ex matching to see if it throws an exception.
+                // dont know what is better
+                return -1;
+            }
+        }
+        return i;
+    }
+
+    Object coerce(CommandSession session, Object target, Class<?> type, Object arg) throws Exception
+    {
+        if (arg == null)
+        {
+            return null;
+        }
+
+        if (type.isAssignableFrom(arg.getClass()))
+        {
+            return arg;
+        }
+
+        Object converted = session.convert(type, arg);
+        if (converted != null)
+        {
+            return converted;
+        }
+
+        String string = arg.toString();
+        if (type.isAssignableFrom(String.class))
+        {
+            return string;
+        }
+
+        if (type.isArray())
+        {
+            // Must handle array types
+            return NO_MATCH;
+        }
+        else
+        {
+            if (!type.isPrimitive())
+            {
+                try
+                {
+                    return type.getConstructor(String.class).newInstance(string);
+                }
+                catch (Exception e)
+                {
+                    return NO_MATCH;
+                }
+            }
+        }
+
+        try
+        {
+            if (type == boolean.class)
+            {
+                return new Boolean(string);
+            }
+            else
+            {
+                if (type == byte.class)
+                {
+                    return new Byte(string);
+                }
+                else
+                {
+                    if (type == char.class)
+                    {
+                        if (string.length() == 1)
+                        {
+                            return string.charAt(0);
+                        }
+                    }
+                    else
+                    {
+                        if (type == short.class)
+                        {
+                            return new Short(string);
+                        }
+                        else
+                        {
+                            if (type == int.class)
+                            {
+                                return new Integer(string);
+                            }
+                            else
+                            {
+                                if (type == float.class)
+                                {
+                                    return new Float(string);
+                                }
+                                else
+                                {
+                                    if (type == double.class)
+                                    {
+                                        return new Double(string);
+                                    }
+                                    else
+                                    {
+                                        if (type == long.class)
+                                        {
+                                            return new Long(string);
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        catch (NumberFormatException e)
+        {
+        }
+
+        return NO_MATCH;
+    }
+
+    public static boolean hasCommand(Object target, String function)
+    {
+        Method[] methods = target.getClass().getMethods();
+        for (Method m : methods)
+        {
+            if (m.getName().equals(function))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/threadio/Marker.java b/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/threadio/Marker.java
new file mode 100644
index 0000000..95c50e5
--- /dev/null
+++ b/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/threadio/Marker.java
@@ -0,0 +1,48 @@
+/*
+ * 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.runtime.threadio;
+
+import java.io.InputStream;
+import java.io.PrintStream;
+
+public class Marker
+{
+    Marker previous;
+    InputStream in;
+    PrintStream out;
+    PrintStream err;
+    ThreadIOImpl parent;
+
+    public Marker(ThreadIOImpl parent, InputStream in, PrintStream out, PrintStream err, Marker previous)
+    {
+        this.previous = previous;
+        this.parent = parent;
+        this.in = in;
+        this.out = out;
+        this.err = err;
+    }
+
+    Marker activate()
+    {
+        parent.in.setStream(in);
+        parent.out.setStream(out);
+        parent.err.setStream(err);
+        return previous;
+    }
+}
diff --git a/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/threadio/ThreadIOImpl.java b/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/threadio/ThreadIOImpl.java
new file mode 100644
index 0000000..b9cab7a
--- /dev/null
+++ b/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/threadio/ThreadIOImpl.java
@@ -0,0 +1,119 @@
+/*
+ * 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.
+ */
+// DWB20: ThreadIO should check and reset IO if something (e.g. jetty) overrides
+package org.apache.felix.gogo.runtime.threadio;
+
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.threadio.ThreadIO;
+
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.util.logging.Logger;
+
+public class ThreadIOImpl implements ThreadIO
+{
+    static private final Logger log = Logger.getLogger(ThreadIOImpl.class.getName());
+    ThreadPrintStream err = new ThreadPrintStream(System.err);
+    ThreadPrintStream out = new ThreadPrintStream(System.out);
+    ThreadInputStream in = new ThreadInputStream(System.in);
+    ThreadLocal<Marker> current = new ThreadLocal<Marker>();
+
+    protected void activate(ComponentContext context)
+    {
+        start();
+    }
+
+    protected void deactivate()
+    {
+        stop();
+    }
+
+    public void stop()
+    {
+        System.setErr(err.dflt);
+        System.setOut(out.dflt);
+        System.setIn(in.dflt);
+    }
+
+    public void start()
+    {
+        if (System.out instanceof ThreadPrintStream)
+        {
+            throw new IllegalStateException("Thread Print Stream already set");
+        }
+        System.setOut(out);
+        System.setIn(in);
+        System.setErr(err);
+    }
+
+    private void checkIO()
+    {    // derek
+        if (System.in != in)
+        {
+            log.fine("ThreadIO: eek! who's set System.in=" + System.in);
+            System.setIn(in);
+        }
+
+        if (System.out != out)
+        {
+            log.fine("ThreadIO: eek! who's set System.out=" + System.out);
+            System.setOut(out);
+        }
+
+        if (System.err != err)
+        {
+            log.fine("ThreadIO: eek! who's set System.err=" + System.err);
+            System.setErr(err);
+        }
+    }
+
+    public void close()
+    {
+        checkIO(); // derek
+        Marker top = this.current.get();
+        if (top == null)
+        {
+            throw new IllegalStateException("No thread io active");
+        }
+
+        Marker previous = top.previous;
+        if (previous == null)
+        {
+            in.end();
+            out.end();
+            err.end();
+        }
+        else
+        {
+            this.current.set(previous);
+            previous.activate();
+        }
+    }
+
+    public void setStreams(InputStream in, PrintStream out, PrintStream err)
+    {
+        assert in != null;
+        assert out != null;
+        assert err != null;
+        checkIO(); // derek
+        Marker marker = new Marker(this, in, out, err, current.get());
+        this.current.set(marker);
+        marker.activate();
+    }
+}
diff --git a/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/threadio/ThreadInputStream.java b/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/threadio/ThreadInputStream.java
new file mode 100644
index 0000000..8d869ac
--- /dev/null
+++ b/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/threadio/ThreadInputStream.java
@@ -0,0 +1,86 @@
+/*

+ * 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.runtime.threadio;

+

+import java.io.IOException;

+import java.io.InputStream;

+

+public class ThreadInputStream extends InputStream

+{

+    ThreadLocal<InputStream> map = new ThreadLocal<InputStream>();

+    InputStream dflt;

+

+    public ThreadInputStream(InputStream in)

+    {

+        dflt = in;

+    }

+

+    public int read(byte[] buffer, int offset, int length) throws IOException

+    {

+        return getCurrent().read(buffer, offset, length);

+    }

+

+    public int read(byte[] buffer) throws IOException

+    {

+        return getCurrent().read(buffer);

+    }

+

+    private InputStream getCurrent()

+    {

+        InputStream in = map.get();

+        if (in != null)

+        {

+            return in;

+        }

+        return dflt;

+    }

+

+    public int read() throws IOException

+    {

+        return getCurrent().read();

+    }

+

+    public void setStream(InputStream in)

+    {

+        if (in != dflt && in != this)

+        {

+            map.set(in);

+        }

+        else

+        {

+            map.remove();

+        }

+    }

+

+    public void end()

+    {

+        map.remove();

+    }

+

+    /**

+     * Access to the root stream through reflection

+     *

+     * @return

+     */

+    public InputStream getRoot()

+    {

+        return dflt;

+    }

+

+}

diff --git a/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/threadio/ThreadPrintStream.java b/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/threadio/ThreadPrintStream.java
new file mode 100644
index 0000000..0a8c529
--- /dev/null
+++ b/gogo/gogo-runtime/src/main/java/org/apache/felix/gogo/runtime/threadio/ThreadPrintStream.java
@@ -0,0 +1,88 @@
+/*

+ * 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.runtime.threadio;

+

+import java.io.IOException;

+import java.io.PrintStream;

+import java.io.InputStream;

+

+public class ThreadPrintStream extends PrintStream

+{

+    PrintStream dflt;

+    ThreadLocal<PrintStream> map = new ThreadLocal<PrintStream>();

+

+    public ThreadPrintStream(PrintStream out)

+    {

+        super(out);

+        dflt = out;

+    }

+

+    public void write(byte[] buffer, int offset, int length)

+    {

+        getCurrent().write(buffer, offset, length);

+    }

+

+    public void write(byte[] buffer) throws IOException

+    {

+        getCurrent().write(buffer);

+    }

+

+    public PrintStream getCurrent()

+    {

+        PrintStream out = map.get();

+        if (out != null)

+        {

+            return out;

+        }

+        return dflt;

+    }

+

+    public void write(int b)

+    {

+        getCurrent().write(b);

+    }

+

+    public void setStream(PrintStream out)

+    {

+        if (out != dflt && out != this)

+        {

+            map.set(out);

+        }

+        else

+        {

+            map.remove();

+        }

+    }

+

+    public void end()

+    {

+        map.remove();

+    }

+

+    /**

+     * Access to the root stream through reflection

+     * 

+     * @return

+     */

+    public PrintStream getRoot()

+    {

+        return dflt;

+    }

+

+}

diff --git a/gogo/gogo-runtime/src/main/java/org/osgi/service/command/CommandProcessor.java b/gogo/gogo-runtime/src/main/java/org/osgi/service/command/CommandProcessor.java
new file mode 100644
index 0000000..b997845
--- /dev/null
+++ b/gogo/gogo-runtime/src/main/java/org/osgi/service/command/CommandProcessor.java
@@ -0,0 +1,64 @@
+/*
+ * 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.osgi.service.command;
+
+import java.io.InputStream;
+import java.io.PrintStream;
+
+/**
+ * A command shell can create and maintain a number of command sessions.
+ *
+ * @author aqute
+ */
+public interface CommandProcessor
+{
+    /**
+     * The scope of commands provided by this service. This name can be used to distinguish
+     * between different command providers with the same function names.
+     */
+    final static String COMMAND_SCOPE = "osgi.command.scope";
+
+    /**
+     * A list of method names that may be called for this command provider. A
+     * name may end with a *, this will then be calculated from all declared public
+     * methods in this service.
+     * <p/>
+     * Help information for the command may be supplied with a space as
+     * separation.
+     */
+    final static String COMMAND_FUNCTION = "osgi.command.function";
+
+    /**
+     * Create a new command session associated with IO streams.
+     * <p/>
+     * The session is bound to the life cycle of the bundle getting this
+     * service. The session will be automatically closed when this bundle is
+     * stopped or the service is returned.
+     * <p/>
+     * The shell will provide any available commands to this session and
+     * can set additional variables.
+     *
+     * @param in  The value used for System.in
+     * @param out The stream used for System.out
+     * @param err The stream used for System.err
+     * @return A new session.
+     */
+    CommandSession createSession(InputStream in, PrintStream out,
+			PrintStream err);
+}
diff --git a/gogo/gogo-runtime/src/main/java/org/osgi/service/command/CommandSession.java b/gogo/gogo-runtime/src/main/java/org/osgi/service/command/CommandSession.java
new file mode 100644
index 0000000..293c612
--- /dev/null
+++ b/gogo/gogo-runtime/src/main/java/org/osgi/service/command/CommandSession.java
@@ -0,0 +1,96 @@
+/*
+ * 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.osgi.service.command;
+
+import java.io.InputStream;
+import java.io.PrintStream;
+
+public interface CommandSession
+{
+    /**
+     * Execute a program in this session.
+     *
+     * @param commandline
+     * @return the result of the execution
+     */
+    Object execute(CharSequence commandline) throws Exception;
+
+    /**
+     * Close this command session. After the session is closed, it will throw
+     * IllegalStateException when it is used.
+     *
+     * @param
+     */
+    void close();
+
+    /**
+     * Return the input stream that is the first of the pipeline. This stream is
+     * sometimes necessary to communicate directly to the end user. For example,
+     * a "less" or "more" command needs direct input from the keyboard to
+     * control the paging.
+     *
+     * @return InpuStream used closest to the user or null if input is from a
+     *         file.
+     */
+    InputStream getKeyboard();
+
+    /**
+     * Return the PrintStream for the console. This must always be the stream
+     * "closest" to the user. This stream can be used to post messages that
+     * bypass the piping. If the output is piped to a file, then the object
+     * returned must be null.
+     *
+     * @return
+     */
+    PrintStream getConsole();
+
+    /**
+     * Get the value of a variable.
+     *
+     * @param name
+     * @return
+     */
+    Object get(String name);
+
+    /**
+     * Set the value of a variable.
+     *
+     * @param name  Name of the variable.
+     * @param value Value of the variable
+     */
+    void put(String name, Object value);
+
+    /**
+     * Convert an object to string form (CharSequence). The level is defined in
+     * the Converter interface, it can be one of INSPECT, LINE, PART. This
+     * function always returns a non null value. As a last resort, toString is
+     * called on the Object.
+     *
+     * @param target
+     * @param level
+     * @return
+     */
+    CharSequence format(Object target, int level);
+
+	/**
+	 * Convert an object to another type.
+	 */
+	
+	Object convert(Class<?> type, Object instance);
+}
diff --git a/gogo/gogo-runtime/src/main/java/org/osgi/service/command/Converter.java b/gogo/gogo-runtime/src/main/java/org/osgi/service/command/Converter.java
new file mode 100644
index 0000000..7ca29d4
--- /dev/null
+++ b/gogo/gogo-runtime/src/main/java/org/osgi/service/command/Converter.java
@@ -0,0 +1,86 @@
+/*
+ * 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.osgi.service.command;
+
+
+/**
+ * A converter is a service that can help create specific object types from a
+ * string, and vice versa.
+ * <p/>
+ * The shell is capable of coercing arguments to the their proper type. However,
+ * sometimes commands require extra help to do this conversion. This service can
+ * implement a converter for a number of types.
+ * <p/>
+ * The command shell will rank these services in order of service.ranking and
+ * will then call them until one of the converters succeeds.
+ */
+public interface Converter
+{
+    /**
+     * This property is a string, or array of strings, and defines the classes
+     * or interfaces that this converter recognizes. Recognized classes can be
+     * converted from a string to a class and they can be printed in 3 different
+     * modes.
+     */
+    String CONVERTER_CLASSES = "osgi.converter.classes";
+
+    /**
+     * Print the object in detail. This can contain multiple lines.
+     */
+    int INSPECT = 0;
+
+    /**
+     * Print the object as a row in a table. The columns should align for
+     * multiple objects printed beneath each other. The print may run over
+     * multiple lines but must not end in a CR.
+     */
+    int LINE = 1;
+
+    /**
+     * Print the value in a small format so that it is identifiable. This
+     * printed format must be recognizable by the conversion method.
+     */
+    int PART = 2;
+
+    /**
+     * Convert an object to the desired type.
+     * <p/>
+     * Return null if the conversion can not be done. Otherwise return and
+     * object that extends the desired type or implements it.
+     *
+     * @param desiredType The type that the returned object can be assigned to
+     * @param in          The object that must be converted
+     * @return An object that can be assigned to the desired type or null.
+     * @throws Exception
+     */
+    Object convert(Class<?> desiredType, Object in) throws Exception;
+
+    /**
+     * Convert an objet to a CharSequence object in the requested format. The
+     * format can be INSPECT, LINE, or PART. Other values must throw
+     * IllegalArgumentException.
+     *
+     * @param target The object to be converted to a String
+     * @param level  One of INSPECT, LINE, or PART.
+     * @param escape Use this object to format sub ordinate objects.
+     * @return A printed object of potentially multiple lines
+     * @throws Exception
+     */
+    CharSequence format(Object target, int level, Converter escape) throws Exception;
+}
diff --git a/gogo/gogo-runtime/src/main/java/org/osgi/service/command/Function.java b/gogo/gogo-runtime/src/main/java/org/osgi/service/command/Function.java
new file mode 100644
index 0000000..a282d80
--- /dev/null
+++ b/gogo/gogo-runtime/src/main/java/org/osgi/service/command/Function.java
@@ -0,0 +1,36 @@
+/*
+ * 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.osgi.service.command;
+
+import java.util.List;
+
+/**
+ * A Function is a a block of code that can be executed with a set of arguments,
+ * it returns the result object of executing the script.
+ */
+public interface Function
+{
+    /**
+     * Execute this function and return the result.
+     *
+     * @return the result from the execution.
+     * @throws Exception if anything goes terribly wrong
+     */
+    Object execute(CommandSession session, List<Object> arguments) throws Exception;
+}
diff --git a/gogo/gogo-runtime/src/main/java/org/osgi/service/threadio/ThreadIO.java b/gogo/gogo-runtime/src/main/java/org/osgi/service/threadio/ThreadIO.java
new file mode 100644
index 0000000..682e833
--- /dev/null
+++ b/gogo/gogo-runtime/src/main/java/org/osgi/service/threadio/ThreadIO.java
@@ -0,0 +1,57 @@
+/*
+ * 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.osgi.service.threadio;
+
+import java.io.InputStream;
+import java.io.PrintStream;
+
+/**
+ * Enable multiplexing of the standard IO streams for input, output, and error.
+ * <p/>
+ * This service guards the central resource of IO streams. The standard streams
+ * are singletons. This service replaces the singletons with special versions that
+ * can find a unique stream for each thread. If no stream is associated with a
+ * thread, it will use the standard input/output that was originally set.
+ *
+ * @author aqute
+ */
+public interface ThreadIO
+{
+    /**
+     * Associate this streams with the current thread.
+     * <p/>
+     * Ensure that when output is performed on System.in, System.out, System.err it
+     * will happen on the given streams.
+     * <p/>
+     * The streams will automatically be canceled when the bundle that has gotten
+     * this service is stopped or returns this service.
+     *
+     * @param in  InputStream to use for the current thread when System.in is used
+     * @param out PrintStream to use for the current thread when System.out is used
+     * @param err PrintStream to use for the current thread when System.err is used
+     */
+    void setStreams(InputStream in, PrintStream out, PrintStream err);
+
+    /**
+     * Cancel the streams associated with the current thread.
+     * <p/>
+     * This method will not do anything when no streams are associated.
+     */
+	void close();
+}
diff --git a/gogo/gogo-runtime/src/test/java/org/apache/felix/gogo/runtime/shell/TestParser.java b/gogo/gogo-runtime/src/test/java/org/apache/felix/gogo/runtime/shell/TestParser.java
new file mode 100644
index 0000000..aea28f1
--- /dev/null
+++ b/gogo/gogo-runtime/src/test/java/org/apache/felix/gogo/runtime/shell/TestParser.java
@@ -0,0 +1,285 @@
+/*
+ * 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.runtime.shell;
+
+import junit.framework.TestCase;
+import org.osgi.service.command.CommandSession;
+import org.osgi.service.command.Function;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.regex.Pattern;
+
+public class TestParser extends TestCase
+{
+    int beentheredonethat = 0;
+
+    public void testPipe() throws Exception
+    {
+        Context c = new Context();
+        c.addCommand("echo", this);
+        c.addCommand("capture", this);
+        c.addCommand("grep", this);
+        assertEquals("def", c.execute("echo def|grep (d.*)|capture"));
+        assertEquals("def", c.execute("echo abc; echo def; echo ghi|grep (d.*)|capture"));
+        assertEquals("hello world", c.execute("echo hello world|capture"));
+        assertEquals("defghi", c.execute("echo abc; echo def; echo ghi|grep (def|ghi)|capture"));
+    }
+
+    public void testAssignment() throws Exception
+    {
+        Context c = new Context();
+        c.addCommand("echo", this);
+        c.addCommand("capture", this);
+        c.addCommand("grep", this);
+        assertEquals("hello", c.execute("echo hello|capture").toString());
+        assertEquals("hello", c.execute("a = <echo hello|capture>").toString());
+        assertEquals("a", c.execute("a = a; echo $<echo a>").toString());
+        assertEquals("3", c.execute("a=3; echo $a").toString());
+        assertEquals("3", c.execute("a = 3; echo $a").toString());
+        assertEquals("a", c.execute("a = a; echo $$a").toString());
+    }
+
+    public void testComment() throws Exception
+    {
+        Context c = new Context();
+        c.addCommand("echo", this);
+        assertEquals("1", c.execute("echo 1 // hello").toString());
+
+    }
+
+    public void testClosure() throws Exception
+    {
+        Context c = new Context();
+        c.addCommand("echo", this);
+        assertEquals("http://www.aqute.biz?com=2&biz=1", c.execute("['http://www.aqute.biz?com=2&biz=1'] get 0").toString());
+        assertEquals("{a=2, b=3}", c.execute("[a=2 b=3]").toString());
+        assertEquals("3", c.execute("[a=2 <b>=<3>] get b").toString());
+        assertEquals("[3, 4]", c.execute("[1 2 [3 4] 5 6] get 2").toString());
+        assertEquals(5, c.execute("[1 2 [3 4] 5 6] size"));
+
+    }
+
+    public void testArray() throws Exception
+    {
+        Context c = new Context();
+        assertEquals("http://www.aqute.biz?com=2&biz=1", c.execute("['http://www.aqute.biz?com=2&biz=1'] get 0").toString());
+        assertEquals("{a=2, b=3}", c.execute("[a=2 b=3]").toString());
+        assertEquals("3", c.execute("[a=2 <b>=<3>] get b").toString());
+        assertEquals("[3, 4]", c.execute("[1 2 [3 4] 5 6] get 2").toString());
+        assertEquals(5, c.execute("[1 2 [3 4] 5 6] size"));
+
+    }
+
+    public void testEscape()
+    {
+        Parser parser = new Parser("'a|b;c'");
+        CharSequence cs = parser.messy();
+        assertEquals("a|b;c", cs.toString());
+        assertEquals("a|b;c", new Parser(cs).unescape());
+    }
+
+
+    public void testParentheses()
+    {
+        Parser parser = new Parser("(a|b)|(d|f)");
+        List<List<List<CharSequence>>> p = parser.program();
+        assertEquals("(a|b)", p.get(0).get(0).get(0));
+
+        parser = new Parser("grep (d.*)|grep (d|f)");
+        p = parser.program();
+        assertEquals("(d.*)", p.get(0).get(0).get(1));
+    }
+
+    public void testEcho() throws Exception
+    {
+        Context c = new Context();
+        c.addCommand("echo", this);
+        c.execute("echo peter");
+    }
+
+    public void grep(String match) throws IOException
+    {
+        Pattern p = Pattern.compile(match);
+        BufferedReader rdr = new BufferedReader(new InputStreamReader(System.in));
+        String s = rdr.readLine();
+        while (s != null)
+        {
+            if (p.matcher(s).find())
+            {
+                System.out.println(s);
+            }
+            s = rdr.readLine();
+        }
+    }
+
+    public String capture() throws IOException
+    {
+        StringWriter sw = new StringWriter();
+        BufferedReader rdr = new BufferedReader(new InputStreamReader(System.in));
+        String s = rdr.readLine();
+        while (s != null)
+        {
+            sw.write(s);
+            s = rdr.readLine();
+        }
+        return sw.toString();
+    }
+
+    public void testVars() throws Exception
+    {
+        Context c = new Context();
+        c.addCommand("echo", this);
+
+        assertEquals("", c.execute("echo ${very.likely.that.this.does.not.exist}").toString());
+        assertNotNull(c.execute("echo ${java.shell.name}"));
+    }
+
+    public void testFunny() throws Exception
+    {
+        Context c = new Context();
+        c.addCommand("echo", this);
+        assertEquals("a", c.execute("echo a") + "");
+        assertEquals("a", c.execute("<echo echo> a") + "");
+        assertEquals("a", c.execute("<<echo echo> echo> <echo a>") + "");
+    }
+
+    public CharSequence echo(Object args[])
+    {
+        if (args == null)
+        {
+            return "";
+        }
+
+        StringBuilder sb = new StringBuilder();
+        String del = "";
+        for (Object arg : args)
+        {
+            sb.append(del);
+            if (arg != null)
+            {
+                sb.append(arg);
+                del = " ";
+            }
+        }
+        return sb;
+    }
+
+    public void testContext() throws Exception
+    {
+        Context c = new Context();
+        c.addCommand("ls", this);
+        beentheredonethat = 0;
+        c.execute("ls");
+        assertEquals(1, beentheredonethat);
+
+        beentheredonethat = 0;
+        c.execute("ls 10");
+        assertEquals(10, beentheredonethat);
+
+        beentheredonethat = 0;
+        c.execute("ls a b c d e f g h i j");
+        assertEquals(10, beentheredonethat);
+
+        beentheredonethat = 0;
+        Integer result = (Integer) c.execute("ls <ls 5>");
+        assertEquals(10, beentheredonethat);
+        assertEquals((Integer) 5, result);
+
+    }
+
+    public void ls()
+    {
+        beentheredonethat++;
+        System.out.println("ls(): Yes!");
+    }
+
+    public int ls(int onoff)
+    {
+        beentheredonethat += onoff;
+        System.out.println("ls(int) " + onoff);
+        return onoff;
+    }
+
+    public void ls(Object args[])
+    {
+        beentheredonethat = args.length;
+        System.out.print("ls(Object[]) [");
+        for (Object i : args)
+        {
+            System.out.print(i + " ");
+        }
+        System.out.println("]");
+    }
+
+    public void testProgram()
+    {
+        List<List<List<CharSequence>>> x = new Parser("abc def|ghi jkl;mno pqr|stu vwx").program();
+        assertEquals("abc", x.get(0).get(0).get(0));
+        assertEquals("def", x.get(0).get(0).get(1));
+        assertEquals("ghi", x.get(1).get(0).get(0));
+        assertEquals("jkl", x.get(1).get(0).get(1));
+        assertEquals("mno", x.get(1).get(1).get(0));
+        assertEquals("pqr", x.get(1).get(1).get(1));
+        assertEquals("stu", x.get(2).get(0).get(0));
+        assertEquals("vwx", x.get(2).get(0).get(1));
+    }
+
+    public void testStatements()
+    {
+        List<List<CharSequence>> x = new Parser("abc def;ghi jkl;mno pqr").statements();
+        assertEquals("abc", x.get(0).get(0));
+        assertEquals("def", x.get(0).get(1));
+        assertEquals("ghi", x.get(1).get(0));
+        assertEquals("jkl", x.get(1).get(1));
+        assertEquals("mno", x.get(2).get(0));
+        assertEquals("pqr", x.get(2).get(1));
+    }
+
+    public void testSimpleValue()
+    {
+        List<CharSequence> x = new Parser("abc def.ghi http://www.osgi.org?abc=&x=1 [1,2,3] {{{{{{{xyz}}}}}}} <immediate> {'{{{{{'} {\\}} 'abc{}'").statement();
+        assertEquals("abc", x.get(0));
+        assertEquals("def.ghi", x.get(1));
+        assertEquals("http://www.osgi.org?abc=&x=1", x.get(2));
+        assertEquals("[1,2,3]", x.get(3));
+        assertEquals("{{{{{{{xyz}}}}}}}", x.get(4));
+        assertEquals("<immediate>", x.get(5));
+        assertEquals("{'{{{{{'}", x.get(6));
+        assertEquals("{\\}}", x.get(7));
+        assertEquals("abc{}", x.get(8));
+    }
+
+    void each(CommandSession session, Collection<Object> list, Function closure) throws Exception
+    {
+        List<Object> args = new ArrayList<Object>();
+        args.add(null);
+        for (Object x : list)
+        {
+            args.set(0, x);
+            closure.execute(session, args);
+        }
+    }
+
+}
diff --git a/gogo/gogo-runtime/src/test/java/org/apache/felix/gogo/runtime/threadio/TestThreadIO.java b/gogo/gogo-runtime/src/test/java/org/apache/felix/gogo/runtime/threadio/TestThreadIO.java
new file mode 100644
index 0000000..32ea917
--- /dev/null
+++ b/gogo/gogo-runtime/src/test/java/org/apache/felix/gogo/runtime/threadio/TestThreadIO.java
@@ -0,0 +1,89 @@
+/*
+ * 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.runtime.threadio;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.List;
+
+public class TestThreadIO extends TestCase
+{
+
+    /**
+     * Test if the threadio works in a nested fashion. We first push
+     * ten markers on the stack and print a message for each, capturing
+     * the output in a ByteArrayOutputStream. Then we pop them, also printing
+     * a message identifying the level. Then we verify the output for each level.
+     */
+    public void testNested()
+    {
+        ThreadIOImpl tio = new ThreadIOImpl();
+        tio.start();
+        List<ByteArrayOutputStream> list = new ArrayList<ByteArrayOutputStream>();
+        for (int i = 0; i < 10; i++)
+        {
+            ByteArrayOutputStream out = new ByteArrayOutputStream();
+            list.add(out);
+            tio.setStreams(System.in, new PrintStream(out), System.err);
+            System.out.print("b" + i);
+        }
+        for (int i = 9; i >= 0; i--)
+        {
+            System.out.println("e" + i);
+            tio.close();
+        }
+        tio.stop();
+        for (int i = 0; i < 10; i++)
+        {
+            String message = list.get(i).toString().trim();
+            assertEquals("b" + i + "e" + i, message);
+        }
+    }
+
+    /**
+     * Simple test too see if the basics work.
+     */
+    public void testSimple()
+    {
+        ThreadIOImpl tio = new ThreadIOImpl();
+        tio.start();
+        System.out.println("Hello World");
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        ByteArrayOutputStream err = new ByteArrayOutputStream();
+        tio.setStreams(System.in, new PrintStream(out), new PrintStream(err));
+        try
+        {
+            System.out.println("Simple Normal Message");
+            System.err.println("Simple Error Message");
+        }
+        finally
+        {
+            tio.close();
+        }
+        tio.stop();
+        String normal = out.toString().trim();
+        //String error = err.toString().trim();
+        assertEquals("Simple Normal Message", normal);
+        //assertEquals("Simple Error Message", error );
+        System.out.println("Goodbye World");
+    }
+}