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/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;
+    }
+}