Initial commit of OSGi Shell contribution. (FELIX-946)


git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@783826 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/gogo/aQute.osgi.launcher.bnd b/gogo/aQute.osgi.launcher.bnd
new file mode 100644
index 0000000..c211783
--- /dev/null
+++ b/gogo/aQute.osgi.launcher.bnd
@@ -0,0 +1,4 @@
+Private-Package: aQute.osgi.launcher, aQute.shell.runtime, aQute.shell.osgi, aQute.threadio, \
+ org.osgi.service.shell, org.osgi.service.threadio, aQute.shell.console, org.osgi.framework 
+Main-Class: aQute.osgi.launcher.Launcher
+
diff --git a/gogo/aQute.shell.equinox.bnd b/gogo/aQute.shell.equinox.bnd
new file mode 100644
index 0000000..3053ee4
--- /dev/null
+++ b/gogo/aQute.shell.equinox.bnd
@@ -0,0 +1,6 @@
+-include: ~default.bnd
+Service-Component: aQute.shell.equinox.Equinox; \
+   pka=org.osgi.service.packageadmin.PackageAdmin?; \
+   lrs=org.osgi.service.log.LogReaderService?; \
+   sls=org.osgi.service.startlevel.StartLevel?
+Private-Package: aQute.shell.equinox
diff --git a/gogo/aQute.shell.runtime.bnd b/gogo/aQute.shell.runtime.bnd
new file mode 100644
index 0000000..24613f2
--- /dev/null
+++ b/gogo/aQute.shell.runtime.bnd
@@ -0,0 +1,9 @@
+-include: ~default.bnd
+Bundle-Name: Command Shell Service
+Bundle-Description: Provides a shell service
+Private-Package: aQute.shell.osgi, aQute.shell.runtime
+Export-Package: org.osgi.service.command
+Service-Component: aQute.shell.osgi.OSGiShell; \
+  provide:=org.osgi.service.command.CommandProcessor; \
+  threadio=org.osgi.service.threadio.ThreadIO; \
+  converter=org.osgi.service.command.Converter*
diff --git a/gogo/aQute.shell.stdio.bnd b/gogo/aQute.shell.stdio.bnd
new file mode 100644
index 0000000..0f9faa6
--- /dev/null
+++ b/gogo/aQute.shell.stdio.bnd
@@ -0,0 +1,4 @@
+-include: ~default.bnd
+Service-Component: aQute.shell.stdio.StdioConsole; \
+   processor=org.osgi.service.command.CommandProcessor
+Private-Package: aQute.shell.stdio,aQute.shell.console
diff --git a/gogo/aQute.shell.support.bnd b/gogo/aQute.shell.support.bnd
new file mode 100644
index 0000000..34c11c2
--- /dev/null
+++ b/gogo/aQute.shell.support.bnd
@@ -0,0 +1,6 @@
+-include: ~default.bnd
+Bundle-Name: aQute Shell Support
+Bundle-Description: Commands for the shell that turn it into a programming language
+Private-Package: aQute.shell.lang
+Service-Component: aQute.shell.lang.Support; \
+  provide:=org.osgi.service.command.Converter
\ No newline at end of file
diff --git a/gogo/aQute.shell.telnet.bnd b/gogo/aQute.shell.telnet.bnd
new file mode 100644
index 0000000..9632317
--- /dev/null
+++ b/gogo/aQute.shell.telnet.bnd
@@ -0,0 +1,7 @@
+-include: ~default.bnd
+
+Bundle-Name: Telnet Shell Service
+Bundle-Description: Listens to port 2019 (unless overridden with configuration "port" for pid=aQute.shell.telnet.TelnetShell)
+Private-Package: aQute.shell.telnet, aQute.shell.console
+Service-Component: aQute.shell.telnet.TelnetShell; \
+  processor=org.osgi.service.command.CommandProcessor
diff --git a/gogo/aQute.threadio.bnd b/gogo/aQute.threadio.bnd
new file mode 100644
index 0000000..f2808fc
--- /dev/null
+++ b/gogo/aQute.threadio.bnd
@@ -0,0 +1,8 @@
+-include: ~default.bnd
+
+Bundle-Name: Thread IO
+Bundle-Description: Multiplexes input/output/error streams based on the thread.
+Private-Package: aQute.threadio 
+Export-Package: org.osgi.service.threadio
+Service-Component: aQute.threadio.ThreadIOImpl;\
+  provide:=org.osgi.service.threadio.ThreadIO
diff --git a/gogo/default.bnd b/gogo/default.bnd
new file mode 100644
index 0000000..e9347ae
--- /dev/null
+++ b/gogo/default.bnd
@@ -0,0 +1,3 @@
+-output: work
+-sources: true
+Bundle-Version: 0.0.12
\ No newline at end of file
diff --git a/gogo/proc.bnd b/gogo/proc.bnd
new file mode 100644
index 0000000..84dc020
--- /dev/null
+++ b/gogo/proc.bnd
@@ -0,0 +1,5 @@
+-output: work
+Private-Package: aQute.cpeg
+Service-Component: aQute.cpeg.Procedural; \
+  provide:=aQute.cpeg.Procedural; \
+  properties:="osgi.command.scope=proc,osgi.command.function=new"
diff --git a/gogo/src/aQute/cpeg/Procedural.java b/gogo/src/aQute/cpeg/Procedural.java
new file mode 100644
index 0000000..1fa1d32
--- /dev/null
+++ b/gogo/src/aQute/cpeg/Procedural.java
@@ -0,0 +1,59 @@
+/*
+ * 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 aQute.cpeg;
+
+
+import org.osgi.framework.*;
+import org.osgi.service.command.*;
+
+
+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/src/aQute/osgi/launcher/Launcher.java b/gogo/src/aQute/osgi/launcher/Launcher.java
new file mode 100644
index 0000000..33306ff
--- /dev/null
+++ b/gogo/src/aQute/osgi/launcher/Launcher.java
@@ -0,0 +1,121 @@
+/*
+ * 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 aQute.osgi.launcher;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.net.*;
+import java.util.*;
+
+import org.osgi.framework.*;
+import org.osgi.service.command.*;
+
+import aQute.shell.console.*;
+import aQute.shell.osgi.*;
+import aQute.threadio.*;
+
+public class Launcher {
+	static List<URL> classpath = new ArrayList<URL>();
+	static File cwd = new File("").getAbsoluteFile();
+	
+	public static void main(String args[]) throws Exception {
+		StringBuffer sb = new StringBuffer();
+		String framework = null;
+		PrintStream		out = System.out;
+		InputStream		in = System.in;
+		boolean console = false;
+		
+		for (int i = 0; i < args.length; i++) {
+			String arg = args[i];
+			if (arg.equals("-f")) {
+				framework = args[++i];
+			} else if (arg.equals("-cp") || arg.equals("-classpath")) {
+				classpath(args[++i]);
+			} else if (arg.equals("-console")) {
+				console = true;
+			} else if (arg.equals("-i")) {
+				in = new FileInputStream(args[++i]);
+			} else if (arg.equals("-o")) {
+				out = new PrintStream(new FileOutputStream(args[++i]));
+			} else {
+				sb.append(' ');
+				sb.append(arg);
+			}
+		}
+
+		if (framework == null) {
+			System.err.println("No framework set");
+			System.exit(1);
+		}
+
+		ThreadIOImpl threadio = new ThreadIOImpl();
+		threadio.start();
+		URL[] urls = classpath.toArray(new URL[classpath.size()]);
+		URLClassLoader urlcl = new URLClassLoader(urls, Launcher.class.getClassLoader());
+		Class<?> fw = urlcl.loadClass(framework);
+		
+		Constructor<?> c = fw.getConstructor(Map.class, List.class );
+		Properties p = new Properties( System.getProperties());
+		p.setProperty("felix.cache.profile", "default");
+		p.setProperty("felix.embedded.execution", "true");
+		Bundle bundle = (Bundle) c.newInstance(p,null);
+		
+		OSGiShell shell = new OSGiShell();
+		shell.setThreadio(threadio);
+		shell.setBundle(bundle);
+		shell.start();
+		
+		
+		CommandSession session = shell.createSession(in, out,
+				System.err);
+		session.put("shell", shell);
+		session.put("threadio", threadio);
+		
+		session.execute(sb);
+		out.flush();
+		
+		if ( bundle.getState() == Bundle.ACTIVE ) {
+			bundle.getBundleContext().registerService(CommandProcessor.class.getName(), shell, null );
+		}
+		if ( console ) {
+			Console cons = new Console();
+			cons.setSession(session);
+			cons.run();
+		}
+	}
+
+
+	private static void classpath(String string) throws MalformedURLException {
+		StringTokenizer st = new StringTokenizer(string, File.pathSeparator);
+		while (st.hasMoreTokens()) {
+			String part = st.nextToken();
+			if ( part.equals("."))
+				classpath.add( cwd.toURL() );
+			
+			File f = new File(part);
+			if ( ! f.isAbsolute() ) {
+				f = new File( cwd, part );
+			}
+			if ( f.exists() )
+				classpath.add( f.toURL());
+			else
+				System.err.println("Can not find " + part );
+		}
+	}	
+}
diff --git a/gogo/src/aQute/shell/console/Console.java b/gogo/src/aQute/shell/console/Console.java
new file mode 100644
index 0000000..a74ff8e
--- /dev/null
+++ b/gogo/src/aQute/shell/console/Console.java
@@ -0,0 +1,143 @@
+/*
+ * 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 aQute.shell.console;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+import org.osgi.service.command.*;
+
+public class Console implements Runnable {
+	StringBuilder		sb;
+	CommandSession		session;
+	List<CharSequence>	history	= new ArrayList<CharSequence>();
+	int					current	= 0;
+	boolean				quit;
+
+	public void setSession(CommandSession session) {
+	    this.session= session;
+	}
+	
+	public void run() {
+		try {
+			while (!quit) {
+				try {
+					CharSequence line = getLine(session.getKeybord());
+					if (line != null) {
+						history.add(line);
+						if (history.size() > 40)
+							history.remove(0);
+						Object result = session.execute(line);
+						if (result != null)
+							session.getConsole().println(
+									session.format(result, Converter.INSPECT));
+					} else
+						quit = true;
+
+				} catch (InvocationTargetException ite) {
+					session.getConsole().println(
+							"E: " + ite.getTargetException());
+					session.put("exception", ite.getTargetException());
+				} catch (Throwable e) {
+					if (!quit) {
+						session.getConsole().println("E: " + e.getMessage());
+						session.put("exception", e);
+					}
+				}
+			}
+		} catch (Exception e) {
+			if (!quit)
+				e.printStackTrace();
+		}
+	}
+
+	CharSequence getLine(InputStream in) throws IOException {
+		sb = new StringBuilder();
+		session.getConsole().print("$ ");
+		int outer = 0;
+		while (!quit) {
+			session.getConsole().flush();
+			int c = in.read();
+			if (c < 0)
+				quit = true;
+			else {
+				switch (c) {
+				case '\r':
+				    break;
+				case '\n':
+					if (outer == 0 && sb.length() > 0) {
+						return sb;
+					} else {
+					        session.getConsole().print("$ ");
+					}
+					break;
+
+				case '\u001b':
+					c = in.read();
+					if (c == '[') {
+						c = in.read();
+						session.getConsole().print("\b\b\b");
+						switch (c) {
+						case 'A':
+							history(current - 1);
+							break;
+						case 'B':
+							history(current + 1);
+							break;
+						case 'C': // right(); break;
+						case 'D': // left(); break;
+						}
+					}
+					break;
+
+				case '\b':
+					if (sb.length() > 0) {
+						session.getConsole().print("\b \b");
+						sb.deleteCharAt(sb.length() - 1);
+					}
+					break;
+
+				default:
+					sb.append((char) c);
+					break;
+				}
+			}
+		}
+		return null;
+	}
+
+	void history(int n) {
+		if (n < 0 || n > history.size())
+			return;
+		current = n;
+		for (int i = 0; i < sb.length(); i++)
+			session.getConsole().print("\b \b");
+
+		sb = new StringBuilder(history.get(current));
+		session.getConsole().print(sb);
+	}
+
+    public void close() {
+        quit = true;
+    }
+    
+    public void open() {}
+}
diff --git a/gogo/src/aQute/shell/equinox/Equinox.java b/gogo/src/aQute/shell/equinox/Equinox.java
new file mode 100644
index 0000000..d2c34f1
--- /dev/null
+++ b/gogo/src/aQute/shell/equinox/Equinox.java
@@ -0,0 +1,389 @@
+package aQute.shell.equinox;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+
+import org.osgi.framework.*;
+import org.osgi.service.command.*;
+import org.osgi.service.component.*;
+import org.osgi.service.log.*;
+import org.osgi.service.packageadmin.*;
+import org.osgi.service.startlevel.*;
+
+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/src/aQute/shell/lang/Support.java b/gogo/src/aQute/shell/lang/Support.java
new file mode 100644
index 0000000..bdf27de
--- /dev/null
+++ b/gogo/src/aQute/shell/lang/Support.java
@@ -0,0 +1,49 @@
+/*
+ * 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 aQute.shell.lang;
+
+import java.lang.reflect.*;
+import java.util.*;
+
+import org.osgi.service.command.*;
+
+
+
+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/src/aQute/shell/osgi/OSGiCommands.java b/gogo/src/aQute/shell/osgi/OSGiCommands.java
new file mode 100644
index 0000000..f715a89
--- /dev/null
+++ b/gogo/src/aQute/shell/osgi/OSGiCommands.java
@@ -0,0 +1,302 @@
+/*
+ * 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 aQute.shell.osgi;
+
+import java.io.*;
+import java.util.*;
+import java.util.regex.*;
+
+import org.osgi.framework.*;
+import org.osgi.service.command.*;
+
+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));
+		}
+	}
+
+	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)
+            return Class.forName(in.toString());
+        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/src/aQute/shell/osgi/OSGiShell.java b/gogo/src/aQute/shell/osgi/OSGiShell.java
new file mode 100644
index 0000000..3b35496
--- /dev/null
+++ b/gogo/src/aQute/shell/osgi/OSGiShell.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.
+ */
+package aQute.shell.osgi;
+
+import org.osgi.framework.*;
+import org.osgi.service.command.*;
+import org.osgi.service.component.*;
+import org.osgi.service.packageadmin.*;
+import org.osgi.service.permissionadmin.*;
+import org.osgi.service.startlevel.*;
+import org.osgi.service.threadio.*;
+
+import aQute.shell.runtime.*;
+
+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(StartLevel.class.getName(),
+                    null), StartLevel.class);
+            addCommand("osgi", commands.service(PackageAdmin.class.getName(),
+                    null), PackageAdmin.class);
+            addCommand("osgi", commands.service(
+                    PermissionAdmin.class.getName(), null),
+                    PermissionAdmin.class);
+            addCommand("osgi", commands.getContext(), BundleContext.class);
+        }
+    }
+
+    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);
+
+                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/src/aQute/shell/osgi/ServiceCommand.java b/gogo/src/aQute/shell/osgi/ServiceCommand.java
new file mode 100644
index 0000000..61d4087
--- /dev/null
+++ b/gogo/src/aQute/shell/osgi/ServiceCommand.java
@@ -0,0 +1,51 @@
+/*
+ * 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 aQute.shell.osgi;
+
+import java.util.*;
+
+import org.osgi.framework.*;
+import org.osgi.service.command.*;
+
+import aQute.shell.runtime.*;
+
+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/src/aQute/shell/runtime/Closure.java b/gogo/src/aQute/shell/runtime/Closure.java
new file mode 100644
index 0000000..00a297b
--- /dev/null
+++ b/gogo/src/aQute/shell/runtime/Closure.java
@@ -0,0 +1,233 @@
+/*
+ * 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 aQute.shell.runtime;
+
+import java.util.*;
+
+import org.osgi.service.command.*;
+
+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);
+            } 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>();
+        Object cmd = eval(statement.remove(0));
+        for (CharSequence token : statement)
+            values.add(eval(token));
+
+        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)
+                    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/src/aQute/shell/runtime/Command.java b/gogo/src/aQute/shell/runtime/Command.java
new file mode 100644
index 0000000..cd9f500
--- /dev/null
+++ b/gogo/src/aQute/shell/runtime/Command.java
@@ -0,0 +1,38 @@
+/*
+ * 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 aQute.shell.runtime;
+
+import java.util.*;
+
+import org.osgi.service.command.*;
+
+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/src/aQute/shell/runtime/CommandSessionImpl.java b/gogo/src/aQute/shell/runtime/CommandSessionImpl.java
new file mode 100644
index 0000000..2607554
--- /dev/null
+++ b/gogo/src/aQute/shell/runtime/CommandSessionImpl.java
@@ -0,0 +1,215 @@
+/*
+ * 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 aQute.shell.runtime;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+import org.osgi.service.command.*;
+
+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>();
+
+	CommandSessionImpl(CommandShellImpl service, InputStream in,
+			PrintStream out, PrintStream err) {
+		this.service = service;
+		this.in = in;
+		this.out = out;
+		this.err = err;
+	}
+
+	public void close() {
+	}
+
+	public Object execute(CharSequence commandline) throws Exception {
+		assert service != null;
+		assert service.threadIO != null;
+		
+		Closure impl = new Closure(this, null, commandline);
+		Object result = impl.execute(this, null);
+		return result;
+	}
+
+	public InputStream getKeybord() {
+		return in;
+	}
+
+	public Object get(String name) {
+		if (variables != null && variables.containsKey(name))
+			return variables.get(name);
+
+		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/src/aQute/shell/runtime/CommandShellImpl.java b/gogo/src/aQute/shell/runtime/CommandShellImpl.java
new file mode 100644
index 0000000..5326343
--- /dev/null
+++ b/gogo/src/aQute/shell/runtime/CommandShellImpl.java
@@ -0,0 +1,140 @@
+/*
+ * 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 aQute.shell.runtime;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+import org.osgi.service.command.*;
+import org.osgi.service.threadio.*;
+
+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" );
+    }
+
+    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;
+                    }
+                }
+            }
+        }
+        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) {
+        addCommand(scope,target,target.getClass());
+    }
+
+    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);
+    }
+
+    public String[] getFunctions(Class<?> target) {
+        String[] functions;
+        Set<String> list = new TreeSet<String>();
+        Method methods[] = target.getMethods();
+        for (Method m : methods) {
+            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/src/aQute/shell/runtime/Parser.java b/gogo/src/aQute/shell/runtime/Parser.java
new file mode 100644
index 0000000..8585847
--- /dev/null
+++ b/gogo/src/aQute/shell/runtime/Parser.java
@@ -0,0 +1,280 @@
+/*
+ * 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 aQute.shell.runtime;
+
+import java.util.*;
+
+public class Parser {
+	int					current	= 0;
+	CharSequence		text;
+	boolean				escaped;
+	static final String	SPECIAL	= "<;|{[\"'$'`(=";
+
+	public Parser(CharSequence program) {
+		text = program;
+	}
+
+	void ws() {
+		while (!eof() && Character.isWhitespace(peek())) {
+			current++;
+			if (peek() == '/' && current < text.length()-2 && text.charAt(current + 1) == '/') {
+				comment();
+			}
+		}
+	}
+
+	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;
+			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++;
+			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/src/aQute/shell/runtime/Pipe.java b/gogo/src/aQute/shell/runtime/Pipe.java
new file mode 100644
index 0000000..c6f04e8
--- /dev/null
+++ b/gogo/src/aQute/shell/runtime/Pipe.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package aQute.shell.runtime;
+
+import java.io.*;
+import java.util.*;
+
+import org.osgi.service.command.*;
+
+public class Pipe extends Thread {
+	InputStream in;
+	PrintStream out;
+	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 Pipe connect(Pipe next) throws IOException {
+		next.setOut(out);
+		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);
+		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/src/aQute/shell/runtime/Reflective.java b/gogo/src/aQute/shell/runtime/Reflective.java
new file mode 100644
index 0000000..284191b
--- /dev/null
+++ b/gogo/src/aQute/shell/runtime/Reflective.java
@@ -0,0 +1,207 @@
+/*
+ * 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 aQute.shell.runtime;
+
+import java.lang.reflect.*;
+import java.util.*;
+
+import org.osgi.service.command.*;
+
+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 {
+        Method[] methods = target.getClass().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;
+        for (Method m : methods) {
+            String mname = m.getName().toLowerCase();
+            if (mname.equals(name) || mname.equals(get) || mname.equals(set)
+                    || mname.equals(is)) {
+                Class<?>[] types = m.getParameterTypes();
+
+                // Check if the command takes a session
+                if (types.length > 0
+                        && CommandSession.class.isAssignableFrom(types[0])) {
+                    args.add(0, session);
+                }
+
+                Object[] parms = new Object[types.length];
+                // if (types.length >= args.size() ) {
+                int local = coerce(session, target, types, parms, args);
+                if (local == types.length || local > match) {
+                    bestMethod = m;
+                    bestArgs = parms;
+                    match = local;
+                }
+                // }
+                // 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);
+            return bestMethod.invoke(target, bestArgs);
+        } else
+            throw new IllegalArgumentException("Cannot find command:" + name
+                    + " with args:" + args);
+    }
+
+    /**
+     * 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
+     */
+    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
+                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) {
+                        // 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;
+                    }
+                    return -1;
+                }
+                i++;
+            } catch (Exception e) {
+                // e.printStackTrace();
+                System.err.println(e);
+                // 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;
+            }
+        }
+        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);
+
+        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/src/aQute/shell/stdio/StdioConsole.java b/gogo/src/aQute/shell/stdio/StdioConsole.java
new file mode 100644
index 0000000..d923996
--- /dev/null
+++ b/gogo/src/aQute/shell/stdio/StdioConsole.java
@@ -0,0 +1,49 @@
+/*
+ * 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 aQute.shell.stdio;
+
+import org.osgi.service.command.*;
+import org.osgi.service.component.*;
+
+import aQute.shell.console.*;
+
+public class StdioConsole extends Thread {
+    final Console console = new Console();
+
+    public StdioConsole() {
+        super("StdioConsole");
+    }
+    protected void activate(ComponentContext context) {
+        start();
+    }
+
+    protected void deactivate(ComponentContext context) {
+        console.close();
+        interrupt();
+    }
+
+    public void setProcessor(CommandProcessor processor ) {
+        console.setSession(processor.createSession(System.in,System.out,System.err));
+    }
+    
+    public void run() {
+        console.run();
+    }
+}
+
diff --git a/gogo/src/aQute/shell/telnet/Handler.java b/gogo/src/aQute/shell/telnet/Handler.java
new file mode 100644
index 0000000..e3791ea
--- /dev/null
+++ b/gogo/src/aQute/shell/telnet/Handler.java
@@ -0,0 +1,61 @@
+/*
+ * 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 aQute.shell.telnet;
+
+import java.io.*;
+import java.net.*;
+
+import org.osgi.service.command.*;
+
+import aQute.shell.console.*;
+
+public class Handler extends Thread {
+	TelnetShell		master;
+	Socket			socket;
+	CommandSession	session;
+	Console			console;
+	
+	public Handler(TelnetShell master, CommandSession session, Socket socket)
+			throws IOException {
+		this.master = master;
+		this.socket = socket;
+		this.session = session;
+	}
+
+	public void run() {
+		try {
+			console = new Console();
+			console.setSession(session);
+			console.run();
+		} finally {
+			close();
+			master.handlers.remove(this);
+		}
+	}
+
+	public void close() {
+		session.close();
+		try {
+			socket.close();
+		} catch (IOException e) {
+			// Ignore, this is close
+		}
+	}
+
+}
diff --git a/gogo/src/aQute/shell/telnet/TelnetShell.java b/gogo/src/aQute/shell/telnet/TelnetShell.java
new file mode 100644
index 0000000..6e66de6
--- /dev/null
+++ b/gogo/src/aQute/shell/telnet/TelnetShell.java
@@ -0,0 +1,104 @@
+/*
+ * 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 aQute.shell.telnet;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+
+import org.osgi.service.command.*;
+import org.osgi.service.component.*;
+
+public class TelnetShell extends Thread {
+    boolean          quit;
+    CommandProcessor processor;
+    ServerSocket     server;
+    int              port     = 2019;
+    List<Handler>    handlers = new ArrayList<Handler>();
+
+    protected void activate(ComponentContext context) {
+        String s = (String) context.getProperties().get("port");
+        if (s != null)
+            port = Integer.parseInt(s);
+        System.out.println("Telnet Listener at port " + port);
+        start();
+    }
+
+    protected void deactivate(ComponentContext ctx) throws Exception {
+        try {
+            quit = true;
+            server.close();
+            interrupt();
+        } catch (Exception e) {
+            // Ignore
+        }
+    }
+
+    public void run() {
+        int delay = 0;
+        try {
+            while (!quit)
+                try {
+                    server = new ServerSocket(port);
+                    delay = 5;
+                    while (!quit) {
+                        Socket socket = server.accept();
+                        CommandSession session = processor.createSession(socket
+                                .getInputStream(), new PrintStream(socket
+                                .getOutputStream()), System.err);
+                        Handler handler = new Handler(this, session, socket);
+                        handlers.add(handler);
+                        handler.start();
+                    }
+                } catch (BindException be) {
+                    delay += 5;
+                    System.err.println("Can not bind to port " + port);
+                    try {
+                        Thread.sleep(delay * 1000);
+                    } catch (InterruptedException e) {
+                        // who cares?
+                    }
+                } catch (Exception e) {
+                    if (!quit)
+                        e.printStackTrace();
+                } finally {
+                    try {
+                        server.close();
+                        Thread.sleep(2000);
+                    } catch (Exception ie) {
+                        //
+                    }
+                }
+
+        } finally {
+            try {
+                if (server != null)
+                    server.close();
+            } catch (IOException e) {
+                //
+            }
+            for (Handler handler : handlers)
+                handler.close();
+        }
+    }
+
+    public void setProcessor(CommandProcessor processor) {
+        this.processor = processor;
+    }
+}
diff --git a/gogo/src/aQute/threadio/Marker.java b/gogo/src/aQute/threadio/Marker.java
new file mode 100644
index 0000000..5c6573b
--- /dev/null
+++ b/gogo/src/aQute/threadio/Marker.java
@@ -0,0 +1,45 @@
+/*
+ * 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 aQute.threadio;
+
+import java.io.*;
+
+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/src/aQute/threadio/ThreadIOImpl.java b/gogo/src/aQute/threadio/ThreadIOImpl.java
new file mode 100644
index 0000000..314671a
--- /dev/null
+++ b/gogo/src/aQute/threadio/ThreadIOImpl.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 aQute.threadio;
+
+import java.io.*;
+
+import org.osgi.service.component.*;
+import org.osgi.service.threadio.*;
+
+public class ThreadIOImpl implements ThreadIO {
+	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);
+	}
+	
+	public void close() {
+	    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;
+        Marker marker = new Marker(this,in,out,err, current.get());
+	    this.current.set(marker);
+	    marker.activate();
+	}
+}
diff --git a/gogo/src/aQute/threadio/ThreadInputStream.java b/gogo/src/aQute/threadio/ThreadInputStream.java
new file mode 100644
index 0000000..e7fc918
--- /dev/null
+++ b/gogo/src/aQute/threadio/ThreadInputStream.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 aQute.threadio;

+

+import java.io.*;

+

+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();

+	}

+

+	InputStream getRoot() {

+		return dflt;

+	}

+}

diff --git a/gogo/src/aQute/threadio/ThreadPrintStream.java b/gogo/src/aQute/threadio/ThreadPrintStream.java
new file mode 100644
index 0000000..b8a7a82
--- /dev/null
+++ b/gogo/src/aQute/threadio/ThreadPrintStream.java
@@ -0,0 +1,65 @@
+/*

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

+

+import java.io.*;

+

+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();

+	}

+	

+

+}

diff --git a/gogo/src/org/osgi/framework/boot/SystemBundle.java b/gogo/src/org/osgi/framework/boot/SystemBundle.java
new file mode 100644
index 0000000..ed58ad9
--- /dev/null
+++ b/gogo/src/org/osgi/framework/boot/SystemBundle.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.framework.boot;
+
+import java.util.*;
+
+/**
+ * This interface should be implemented by framework implementations when their
+ * main object is created. It allows a configurator to set the properties and
+ * launch the framework.
+ * 
+ * @author aqute
+ * 
+ */
+public interface SystemBundle {
+    /**
+     * The name of a Security Manager class with public empty constructor. A
+     * valid value is also true, this means that the framework should
+     * instantiate its own security manager. If not set, security could be
+     * defined by a parent framework or there is no security. This can be
+     * detected by looking if there is a security manager set
+     */
+    String SECURITY          = "org.osgi.framework.security";
+
+    /**
+     * A valid file path in the file system to a directory that exists. The
+     * framework is free to use this directory as it sees fit. This area can not
+     * be shared with anything else. If this property is not set, the framework
+     * should use a file area from the parent bundle. If it is not embedded, it
+     * must use a reasonable platform default.
+     */
+    String STORAGE           = "org.osgi.framework.storage";
+
+    /*
+     * A list of paths (separated by path separator) that point to additional
+     * directories to search for platform specific libraries
+     */
+    String LIBRARIES         = "org.osgi.framework.libraries";
+    /*
+     * The command to give a file executable permission. This is necessary in
+     * some environments for running shared libraries.
+     */
+    String EXECPERMISSION    = "org.osgi.framework.command.execpermission";
+
+    /*
+     * Points to a directory with certificates. ###??? Keystore? Certificate
+     * format?
+     */
+    String ROOT_CERTIFICATES = "org.osgi.framework.root.certificates";
+
+    /*
+     * Set by the configurator but the framework should provide a reasonable
+     * default.
+     */
+    String WINDOWSYSTEM      = "org.osgi.framework.windowsystem";
+
+    /**
+     * Configure this framework with the given properties. These properties can
+     * contain framework specific properties or of the general kind defined in
+     * the specification or in this interface.
+     * 
+     * @param properties
+     *            The properties. This properties can be backed by another
+     *            properties, it can there not be assumed that it contains all
+     *            keys. Use it only through the getProperty methods. This parameter may be null.
+     * 
+     */
+    void init(Properties configuration);
+
+    /**
+     * Wait until the framework is completely finished.
+     * 
+     * This method will return if the framework is stopped and has cleaned up
+     * all the framework resources. 
+     * 
+     * @param timeout Maximum number of milliseconds to wait until the framework is finished. Specifying a zero will wait indefinitely.
+     */
+    
+    void join(long timeout) throws InterruptedException;
+}
diff --git a/gogo/src/org/osgi/service/command/CommandProcessor.java b/gogo/src/org/osgi/service/command/CommandProcessor.java
new file mode 100644
index 0000000..dba883d
--- /dev/null
+++ b/gogo/src/org/osgi/service/command/CommandProcessor.java
@@ -0,0 +1,66 @@
+/*
+ * 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.*;
+
+/**
+ * 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.
+	 * 
+	 * 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.
+	 * 
+	 * 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.
+	 * 
+	 * 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/src/org/osgi/service/command/CommandSession.java b/gogo/src/org/osgi/service/command/CommandSession.java
new file mode 100644
index 0000000..6770689
--- /dev/null
+++ b/gogo/src/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.*;
+
+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 getKeybord();
+
+	/**
+	 * 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/src/org/osgi/service/command/Converter.java b/gogo/src/org/osgi/service/command/Converter.java
new file mode 100644
index 0000000..9f57481
--- /dev/null
+++ b/gogo/src/org/osgi/service/command/Converter.java
@@ -0,0 +1,91 @@
+/*
+ * 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.
+ * 
+ * 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.
+ * 
+ * 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.
+	 * 
+	 * 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/src/org/osgi/service/command/Function.java b/gogo/src/org/osgi/service/command/Function.java
new file mode 100644
index 0000000..81f175e
--- /dev/null
+++ b/gogo/src/org/osgi/service/command/Function.java
@@ -0,0 +1,38 @@
+/*
+ * 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.*;
+
+/**
+ * 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/src/org/osgi/service/threadio/ThreadIO.java b/gogo/src/org/osgi/service/threadio/ThreadIO.java
new file mode 100644
index 0000000..765f594
--- /dev/null
+++ b/gogo/src/org/osgi/service/threadio/ThreadIO.java
@@ -0,0 +1,56 @@
+/*
+ * 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.*;
+
+/**
+ * Enable multiplexing of the standard IO streams for input, output, and error.
+ * 
+ * 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.
+	 * 
+	 * Ensure that when output is performed on System.in, System.out, System.err it
+	 * will happen on the given streams. 
+	 * 
+	 * 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.
+	 * 
+	 * This method will not do anything when no streams are associated.
+	 */
+	void close();
+}
diff --git a/gogo/test/test/aQute/shell/runtime/Context.java b/gogo/test/test/aQute/shell/runtime/Context.java
new file mode 100644
index 0000000..5070835
--- /dev/null
+++ b/gogo/test/test/aQute/shell/runtime/Context.java
@@ -0,0 +1,47 @@
+/*
+ * 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 test.aQute.shell.runtime;
+
+import aQute.shell.runtime.*;
+import aQute.threadio.*;
+
+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/test/test/aQute/shell/runtime/TestParser.java b/gogo/test/test/aQute/shell/runtime/TestParser.java
new file mode 100644
index 0000000..d957554
--- /dev/null
+++ b/gogo/test/test/aQute/shell/runtime/TestParser.java
@@ -0,0 +1,262 @@
+/*
+ * 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 test.aQute.shell.runtime;
+
+import java.io.*;
+import java.util.*;
+import java.util.regex.*;
+
+import junit.framework.*;
+
+import org.osgi.service.command.*;
+
+import aQute.shell.runtime.*;
+
+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.runtime.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("Yes!");
+    }
+
+    public int ls(int onoff) {
+        beentheredonethat += onoff;
+        System.out.println("ls " + onoff);
+        return onoff;
+    }
+
+    public void ls(Object args[]) {
+        beentheredonethat = args.length;
+        System.out.print("ls [");
+        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/test/test/aQute/threadio/TestThreadIO.java b/gogo/test/test/aQute/threadio/TestThreadIO.java
new file mode 100644
index 0000000..d986814
--- /dev/null
+++ b/gogo/test/test/aQute/threadio/TestThreadIO.java
@@ -0,0 +1,79 @@
+/*
+ * 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 test.aQute.threadio;
+
+import java.io.*;
+import java.util.*;
+
+import junit.framework.*;
+import aQute.threadio.*;
+
+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");           
+    }
+}