FELIX-946: Apply Derek Baum patch on gogo

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@787270 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/gogo/src/aQute/shell/console/Console.java b/gogo/src/aQute/shell/console/Console.java
index a74ff8e..1c85493 100644
--- a/gogo/src/aQute/shell/console/Console.java
+++ b/gogo/src/aQute/shell/console/Console.java
@@ -40,7 +40,7 @@
 		try {
 			while (!quit) {
 				try {
-					CharSequence line = getLine(session.getKeybord());
+					CharSequence line = getLine(session.getKeyboard());
 					if (line != null) {
 						history.add(line);
 						if (history.size() > 40)
diff --git a/gogo/src/aQute/shell/osgi/OSGiCommands.java b/gogo/src/aQute/shell/osgi/OSGiCommands.java
index f715a89..7f3e9a3 100644
--- a/gogo/src/aQute/shell/osgi/OSGiCommands.java
+++ b/gogo/src/aQute/shell/osgi/OSGiCommands.java
@@ -16,6 +16,8 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+// DWB1: osgi:each too verbose (formats reults to System.out)
+// DWB2: ClassNotFoundException should be caught in convert() method
 package aQute.shell.osgi;
 
 import java.io.*;
@@ -152,8 +154,10 @@
 		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));
+			//Object result = closure.execute(session, args);
+			// System.out.println(session.format(result,Converter.INSPECT));
+			// derek: this is way too noisy
+			closure.execute(session, args);
 		}
 	}
 
@@ -204,8 +208,14 @@
 			return convertBundle(in);
         else if (desiredType == ServiceReference.class)
             return convertServiceReference(in);
-        else if (desiredType == Class.class)
-            return Class.forName(in.toString());
+        else if (desiredType == Class.class) {
+            // derek.baum@paremus.com - added try/catch
+            try {
+                return Class.forName(in.toString());
+            } catch (ClassNotFoundException e) {
+                return null;
+            }
+        }
         else if (desiredType.isAssignableFrom(String.class) && in instanceof InputStream)
             return read(((InputStream) in));
 
diff --git a/gogo/src/aQute/shell/osgi/OSGiShell.java b/gogo/src/aQute/shell/osgi/OSGiShell.java
index 3b35496..20e5ef1 100644
--- a/gogo/src/aQute/shell/osgi/OSGiShell.java
+++ b/gogo/src/aQute/shell/osgi/OSGiShell.java
@@ -16,14 +16,14 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+// DWB3: dynamically load optional framework components to reduce dependencies
+// DWB4: get() with trailing colon causes org.osgi.framework.InvalidSyntaxException
 package 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.*;
@@ -45,14 +45,28 @@
         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);
+            
+            try {
+                // derek - dynamically load StartLevel to avoid import dependency
+                String sl = "org.osgi.service.startlevel.StartLevel";
+                Class<?> slClass = bundle.loadClass(sl);
+                addCommand("osgi", commands.service(sl, null), slClass);
+            } catch (ClassNotFoundException e) {
+            }
+            
+            try {
+                // derek - dynamically load PermissionAdmin to avoid import dependency
+                String pa = "org.osgi.service.permissionadmin.PermissionAdmin";
+                Class<?> paClass = bundle.loadClass(pa);
+                addCommand("osgi", commands.service(pa, null), paClass);
+            } catch (ClassNotFoundException e) {
+            }
+        }
+        else {
+            System.err.println("eek! bundle not active: " + bundle);
         }
     }
 
@@ -74,6 +88,10 @@
 
                 String service = name.substring(0, n);
                 String function = name.substring(n + 1);
+                
+                // derek - fix org.osgi.framework.InvalidSyntaxException
+                if (service.length() == 0 || function.length() == 0)
+                    return null;
 
                 String filter = String.format(
                         "(&(osgi.command.scope=%s)(osgi.command.function=%s))",
diff --git a/gogo/src/aQute/shell/runtime/Closure.java b/gogo/src/aQute/shell/runtime/Closure.java
index 00a297b..d4973cd 100644
--- a/gogo/src/aQute/shell/runtime/Closure.java
+++ b/gogo/src/aQute/shell/runtime/Closure.java
@@ -16,6 +16,9 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+// DWB5: session.err is not redirected when creating pipeline
+// DWB6: add 'set -x' trace feature if echo is set
+// DWB7: removing variable via 'execute("name=") throws OutOfBoundsException
 package aQute.shell.runtime;
 
 import java.util.*;
@@ -48,6 +51,7 @@
             if (pipes.isEmpty()) {
                 current.setIn(session.in);
                 current.setOut(session.out);
+                current.setErr(session.err);    // XXX: derek.baum@paremus.com
             } else {
                 Pipe previous = pipes.get(pipes.size() - 1);
                 previous.connect(current);
@@ -81,9 +85,21 @@
     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)
+        CharSequence statement0 = statement.remove(0);
+        
+        // derek: FEATURE: add set -x facility if echo is set
+        StringBuilder buf = new StringBuilder("+ ");
+        buf.append(statement0);
+        
+        Object cmd = eval(statement0);
+        for (CharSequence token : statement) {
+            buf.append(' ');
+            buf.append(token);
             values.add(eval(token));
+        }
+        
+        if (Boolean.TRUE.equals(session.get("echo")))
+            System.err.println(buf);
 
         result = execute(cmd, values);
         return result;
@@ -108,7 +124,8 @@
             String scmd = cmd.toString();
 
             if (values.size() > 0 && "=".equals(values.get(0))) {
-                if (values.size() == 0)
+                //if (values.size() == 0)
+                if (values.size() == 1)            // derek: BUGFIX
                     return session.variables.remove(scmd);
                 else {
                     Object value = execute(values.get(1), values.subList(2,
diff --git a/gogo/src/aQute/shell/runtime/CommandSessionImpl.java b/gogo/src/aQute/shell/runtime/CommandSessionImpl.java
index 2607554..1a73a00 100644
--- a/gogo/src/aQute/shell/runtime/CommandSessionImpl.java
+++ b/gogo/src/aQute/shell/runtime/CommandSessionImpl.java
@@ -16,6 +16,9 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+// DWB8: throw IllegatlStateException if session used after closed (as per rfc132)
+// DWB9: there is no API to list all variables: https://www.osgi.org/bugzilla/show_bug.cgi?id=49
+// DWB10: add SCOPE support: https://www.osgi.org/bugzilla/show_bug.cgi?id=51
 package aQute.shell.runtime;
 
 import java.io.*;
@@ -25,191 +28,209 @@
 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>();
+    String COLUMN = "%-20s %s\n";
+    InputStream in;
+    PrintStream out;
+    PrintStream err;
+    CommandShellImpl service;
+    Map<Object, Object> variables = new HashMap<Object, Object>();
+    private boolean closed;    // derek
 
-	CommandSessionImpl(CommandShellImpl service, InputStream in,
-			PrintStream out, PrintStream err) {
-		this.service = service;
-		this.in = in;
-		this.out = out;
-		this.err = err;
-	}
+    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 void close() {
+        this.closed = true;    // derek
+    }
 
-	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 Object execute(CharSequence commandline) throws Exception {
+        assert service != null;
+        assert service.threadIO != null;
+        
+        if (closed)
+            throw new IllegalStateException("session is closed");    // derek
 
-	public InputStream getKeybord() {
-		return in;
-	}
+        Closure impl = new Closure(this, null, commandline);
+        Object result = impl.execute(this, null);
+        return result;
+    }
 
-	public Object get(String name) {
-		if (variables != null && variables.containsKey(name))
-			return variables.get(name);
+    public InputStream getKeyboard() {
+        return in;
+    }
 
-		return service.get(name);
-	}
+    public Object get(String name) {
+        // XXX: derek.baum@paremus.com
+        // there is no API to list all variables, so overload name == null
+        if (name == null)
+            return variables.keySet();
 
-	public void put(String name, Object value) {
-		variables.put(name, value);
-	}
+        if (variables != null && variables.containsKey(name))
+            return variables.get(name);
 
-	public PrintStream getConsole() {
-		return out;
-	}
-	
-	
-	@SuppressWarnings("unchecked")
-	public
-	CharSequence format(Object target, int level, Converter escape ) throws Exception {
-		if (target == null)
-			return "null";
+        // XXX: derek: add SCOPE support
+        if (name.startsWith("*:")) {
+            String path = variables.containsKey("SCOPE") ? variables.get("SCOPE").toString()
+                    : "osgi:*";
+            String func = name.substring(2);
+            for (String scope : path.split(":")) {
+                Object result = service.get(scope + ":" + func);
+                if (result != null)
+                    return result;
+            }
+            return null;
+        }
+        return service.get(name);
+    }
 
-		if (target instanceof CharSequence )
-			return (CharSequence) target;
-		
-		for (Converter c : service.converters) {
-			CharSequence s = c.format(target, level, this);
-			if (s != null)
-				return s;
-		}
+    public void put(String name, Object value) {
+        variables.put(name, value);
+    }
 
-		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();
-	}
+    public PrintStream getConsole() {
+        return out;
+    }
 
-	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();
-	}
+    @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;
 
-	public Object convert(Class<?> desiredType, Object in)  {
-		return service.convert(desiredType, in);
-	}
+        for (Converter c : service.converters) {
+            CharSequence s = c.format(target, level, this);
+            if (s != null)
+                return s;
+        }
 
-	public CharSequence format(Object result, int inspect) {
-	    try {
-		return format(result,inspect,this);
-	    } catch(Exception e ) {
-	        return "<can not format " + result + ":" + e;
-	    }
-	}
+        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
index 5326343..401bb00 100644
--- a/gogo/src/aQute/shell/runtime/CommandShellImpl.java
+++ b/gogo/src/aQute/shell/runtime/CommandShellImpl.java
@@ -16,7 +16,9 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-
+// DWB11: add removeCommand: https://www.osgi.org/bugzilla/show_bug.cgi?id=49
+// DWB12: there is no API to list commands: https://www.osgi.org/bugzilla/show_bug.cgi?id=49
+// DWB13: addCommand() fails to add static methods (if target is Class)
 package aQute.shell.runtime;
 
 import java.io.*;
@@ -34,8 +36,9 @@
 
     public CommandShellImpl() {
         addCommand("shell", this, "addCommand" );
+        addCommand("shell", this, "removeCommand" );    // derek
     }
-
+    
     public CommandSession createSession(InputStream in, PrintStream out,
             PrintStream err) {
 
@@ -61,7 +64,7 @@
             return null;
 
         String function = name.substring(n);
-
+        
         Object cmd = null;
 
         if (commands.containsKey(name)) {
@@ -76,7 +79,15 @@
                     }
                 }
             }
+            
+            // XXX: derek.baum@paremus.com
+            // there is no API to list commands
+            // so override otherwise illegal name ":"
+            if (cmd == null && name.equals(":")) {
+                return Collections.unmodifiableSet(commands.keySet());
+            }
         }
+        
         if (cmd == null)
             return null;
 
@@ -87,7 +98,9 @@
     }
 
     public void addCommand(String scope, Object target) {
-        addCommand(scope,target,target.getClass());
+        // derek - fix target class
+        Class<?> tc = (target instanceof Class) ? (Class<?>) target : target.getClass();
+        addCommand(scope, target, tc);
     }
 
     public void addCommand(String scope, Object target, Class<?> functions) {
@@ -103,12 +116,27 @@
     public void addCommand(String scope, Object target, String function) {
         commands.put((scope + ":" + function).toLowerCase(), target);
     }
+    
+    // derek.baum@paremus.com: need removeCommand, so stopped bundles can clean up.
+    public void removeCommand(String scope, String function) {
+        String func = (scope + ":" + function).toLowerCase();
+        commands.remove(func);
+    }
+    
+    public void removeCommand(Object target) {
+        for (Iterator<Object> i = commands.values().iterator(); i.hasNext();) {
+            if (i.next() == target)
+                i.remove();
+        }
+    }
 
-    public String[] getFunctions(Class<?> target) {
+    private String[] getFunctions(Class<?> target) {
         String[] functions;
         Set<String> list = new TreeSet<String>();
         Method methods[] = target.getMethods();
         for (Method m : methods) {
+            if (m.getDeclaringClass().equals(Object.class))    // derek
+                continue;
             list.add(m.getName());
             if (m.getName().startsWith("get")) {
                 String s = m.getName().substring(3);
@@ -116,6 +144,7 @@
                     list.add(s.substring(0, 1).toLowerCase() + s.substring(1));
             }
         }
+        
         functions = list.toArray(new String[list.size()]);
         return functions;
     }
diff --git a/gogo/src/aQute/shell/runtime/Parser.java b/gogo/src/aQute/shell/runtime/Parser.java
index 8585847..10e28db 100644
--- a/gogo/src/aQute/shell/runtime/Parser.java
+++ b/gogo/src/aQute/shell/runtime/Parser.java
@@ -16,6 +16,8 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+// DWB14: parser loops if // comment at start of program
+// DWB15: allow program to have trailing ';'
 package aQute.shell.runtime;
 
 import java.util.*;
@@ -31,11 +33,16 @@
 	}
 
 	void ws() {
-		while (!eof() && Character.isWhitespace(peek())) {
-			current++;
+	        // derek: BUGFIX: loop if comment  at beginning of input
+		//while (!eof() && Character.isWhitespace(peek())) {
+		while (!eof() && (Character.isWhitespace(peek()) || current == 0)) {
+        		if (current != 0 || Character.isWhitespace(peek()))
+        			current++;
 			if (peek() == '/' && current < text.length()-2 && text.charAt(current + 1) == '/') {
 				comment();
 			}
+			if (current == 0)
+			    break;
 		}
 	}
 
@@ -57,7 +64,11 @@
 
 		if (c == '\\') {
 			escaped = true;
-			c = text.charAt(++current);
+			++current;
+			if (eof())
+        			throw new RuntimeException("Eof found after \\"); // derek
+			    
+			c = text.charAt(current);
 
 			switch (c) {
 			case 't':
@@ -117,14 +128,17 @@
 		statements.add(statement());
 		while (peek() == ';') {
 			current++;
-			statements.add(statement());
+			// derek: BUGFIX: allow trailing ;
+			ws();
+			if (!eof())
+        			statements.add(statement());
 		}
 		return statements;
 	}
 
 	public List<CharSequence> statement() {
 		List<CharSequence> statement = new ArrayList<CharSequence>();
-		statement.add(value());
+        	statement.add(value());
 		while (!eof()) {
 			ws();
 			if (peek() == '|' || peek() == ';')
@@ -192,6 +206,7 @@
 					break;
 			}
 		}
+		
 		return text.subSequence(start, current);
 	}
 
diff --git a/gogo/src/aQute/shell/runtime/Pipe.java b/gogo/src/aQute/shell/runtime/Pipe.java
index c6f04e8..5ac8cdb 100644
--- a/gogo/src/aQute/shell/runtime/Pipe.java
+++ b/gogo/src/aQute/shell/runtime/Pipe.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-
+// DWB16: redirect System.err when creating pipe
 package aQute.shell.runtime;
 
 import java.io.*;
@@ -27,6 +27,7 @@
 public class Pipe extends Thread {
 	InputStream in;
 	PrintStream out;
+        PrintStream err;    // derek
 	PipedOutputStream pout;
 	Closure closure;
 	Exception exception;
@@ -47,6 +48,10 @@
 		this.out = out;
 	}
 
+	public void setErr(PrintStream err) {
+		this.err = err;
+	}
+
 	public Pipe connect(Pipe next) throws IOException {
 		next.setOut(out);
 		pout = new PipedOutputStream();
@@ -57,7 +62,8 @@
 	}
 
 	public void run() {
-		closure.session.service.threadIO.setStreams(in, out, System.err);
+		//closure.session.service.threadIO.setStreams(in, out, System.err);
+		closure.session.service.threadIO.setStreams(in, out, err);    // derek
 		try {
 			for (List<CharSequence> statement : statements) {
 				result = closure.executeStatement(statement);
diff --git a/gogo/src/aQute/shell/runtime/Reflective.java b/gogo/src/aQute/shell/runtime/Reflective.java
index 284191b..1b90b63 100644
--- a/gogo/src/aQute/shell/runtime/Reflective.java
+++ b/gogo/src/aQute/shell/runtime/Reflective.java
@@ -1,5 +1,5 @@
 /*
- * Licensed to the Apache Software Foundation (ASF) under one
+* 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
@@ -16,6 +16,10 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+// DWB16: coerce() doesn't support static methods
+// DWB17: coerce() doesn't support static void main(String[]) in rfc132
+// DWB18: coerce() doesn't extract cause from InvocationTargetException
+// DWB19: coerce() won't add empty array to satisfy Object[] argument
 package aQute.shell.runtime;
 
 import java.lang.reflect.*;
@@ -39,7 +43,10 @@
     public Object method(CommandSession session, Object target, String name,
             List<Object> args) throws IllegalArgumentException,
             IllegalAccessException, InvocationTargetException, Exception {
-        Method[] methods = target.getClass().getMethods();
+        // derek - support static methods
+        //Method[] methods = target.getClass().getMethods();
+        Class<?> tc = (target instanceof Class) ? (Class<?>)target : target.getClass();
+        Method[] methods = tc.getMethods();
         name = name.toLowerCase();
 
         String get = "get" + name;
@@ -52,26 +59,36 @@
         Method bestMethod = null;
         Object[] bestArgs = null;
         int match = -1;
+        ArrayList<Class<?>[]> possibleTypes = new ArrayList<Class<?>[]>();    // derek
+
         for (Method m : methods) {
             String mname = m.getName().toLowerCase();
             if (mname.equals(name) || mname.equals(get) || mname.equals(set)
-                    || mname.equals(is)) {
+                    || mname.equals(is) || mname.equals("_main")) {    // derek - added _main
                 Class<?>[] types = m.getParameterTypes();
+                ArrayList<Object> xargs = new ArrayList<Object>(args); // derek - BUGFIX don't modify args
 
                 // Check if the command takes a session
                 if (types.length > 0
                         && CommandSession.class.isAssignableFrom(types[0])) {
-                    args.add(0, session);
+                    xargs.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;
+                int local = coerce(session, target, types, parms, xargs);
+                if ((local >= xargs.size()) && (local >= types.length)) {       // derek - stop no-args
+                    boolean exact = (local == xargs.size() && local == types.length);
+                    if (exact || local > match) {
+                        bestMethod = m;
+                        bestArgs = parms;
+                        match = local;
+                    }
+                    if (exact)
+                        break;
                 }
+                else
+                    possibleTypes.add(types);    // derek
                 // }
                 // if (match == -1 && types.length == 1
                 // && types[0] == Object[].class) {
@@ -84,10 +101,36 @@
 
         if (bestMethod != null) {
             bestMethod.setAccessible(true);
-            return bestMethod.invoke(target, bestArgs);
-        } else
-            throw new IllegalArgumentException("Cannot find command:" + name
-                    + " with args:" + args);
+            // derek: BUGFIX catch InvocationTargetException
+            // return bestMethod.invoke(target, bestArgs);
+            try {
+                return bestMethod.invoke(target, bestArgs);
+            } catch (InvocationTargetException e) {
+                Throwable cause = e.getCause();
+                if (cause instanceof Exception)
+                    throw (Exception)cause;
+                throw e;
+            }
+        } else {
+            //throw new IllegalArgumentException("Cannot find command:" + name + " with args:" + args);
+            // { derek
+            ArrayList<String> list = new ArrayList<String>();
+            for (Class<?>[] types : possibleTypes) {
+                StringBuilder buf = new StringBuilder();
+                buf.append('(');
+                for (Class<?> type : types) {
+                    if (buf.length() > 1)
+                        buf.append(", ");
+                    buf.append(type.getSimpleName());
+                }
+                buf.append(')');
+                list.add(buf.toString());
+            }
+
+            throw new IllegalArgumentException(
+                    String.format("Cannot coerce %s%s to any of %s", name,  args, list));
+            // } derek
+        }
     }
 
     /**
@@ -104,6 +147,7 @@
      * @return
      * @throws Exception
      */
+    @SuppressWarnings("unchecked")
     private int coerce(CommandSession session, Object target, Class<?> types[],
             Object out[], List<Object> in) throws Exception {
         int i = 0;
@@ -111,11 +155,29 @@
             out[i] = null;
             try {
                 // Try to convert one argument
-                out[i] = coerce(session, target, types[i], in.get(i));
+                // derek: add empty array as extra argument
+                //out[i] = coerce(session, target, types[i], in.get(i));
+                if (i == in.size())
+                    out[i] = NO_MATCH;
+                else
+                    out[i] = coerce(session, target, types[i], in.get(i));
+                
                 if (out[i] == NO_MATCH) {
                     // Failed
                     // No match, check for varargs
                     if (types[i].isArray() && i == types.length - 1) {
+                        
+                        // derek - expand final array arg
+                        if (i < in.size()) {
+                            Object arg = in.get(i);
+                            if (arg instanceof List) {
+                                List<Object> args = (List<Object>)arg;
+                                in = new ArrayList<Object>(in);
+                                in.remove(i);
+                                in.addAll(args);
+                            }
+                        }
+                    
                         // Try to parse the remaining arguments in an array
                         Class<?> component = types[i].getComponentType();
                         Object components = Array.newInstance(component, in
@@ -132,14 +194,18 @@
                         }
                         out[n] = components;
                         // Is last element, so we will quite hereafter
-                        return n;
+                        // return n;
+                        if (i == in.size())
+                            ++i;
+                        return i;    // derek - return number of args converted
                     }
                     return -1;
                 }
                 i++;
             } catch (Exception e) {
-                // e.printStackTrace();
-                System.err.println(e);
+                System.err.println("Reflective:" + e);
+                e.printStackTrace();
+                
                 // should get rid of those exceptions, but requires
                 // reg ex matching to see if it throws an exception.
                 // dont know what is better
@@ -175,23 +241,28 @@
                 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);
+        
+        try {
+            if (type == boolean.class)
+                return new Boolean(string);
+            else if (type == byte.class)
+                return new Byte(string);
+            else if (type == char.class) {
+                if (string.length() == 1)
+                    return string.charAt(0);
+            } else if (type == short.class)
+                return new Short(string);
+            else if (type == int.class)
+                return new Integer(string);
+            else if (type == float.class)
+                return new Float(string);
+            else if (type == double.class)
+                return new Double(string);
+            else if (type == long.class)
+                return new Long(string);
+        }
+        catch (NumberFormatException e) {
+        }
 
         return NO_MATCH;
     }
diff --git a/gogo/src/aQute/threadio/ThreadIOImpl.java b/gogo/src/aQute/threadio/ThreadIOImpl.java
index 314671a..55b5ae3 100644
--- a/gogo/src/aQute/threadio/ThreadIOImpl.java
+++ b/gogo/src/aQute/threadio/ThreadIOImpl.java
@@ -16,14 +16,17 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+// DWB20: ThreadIO should check and reset IO if something (e.g. jetty) overrides
 package aQute.threadio;
 
 import java.io.*;
+import java.util.logging.Logger;
 
 import org.osgi.service.component.*;
 import org.osgi.service.threadio.*;
 
 public class ThreadIOImpl implements ThreadIO {
+    static private final Logger log = Logger.getLogger(ThreadIOImpl.class.getName());
 	ThreadPrintStream err = new ThreadPrintStream(System.err);
 	ThreadPrintStream out = new ThreadPrintStream(System.out);
 	ThreadInputStream in = new ThreadInputStream(System.in);
@@ -51,7 +54,25 @@
         System.setErr(err);
 	}
 	
+	private void checkIO() {    // derek
+	    if (System.in != in) {
+	        log.fine("ThreadIO: eek! who's set System.in=" + System.in);
+		System.setIn(in);
+	    }
+	    
+	    if (System.out != out) {
+	        log.fine("ThreadIO: eek! who's set System.out=" + System.out);
+		System.setOut(out);
+	    }
+	    
+	    if (System.err != err) {
+	        log.fine("ThreadIO: eek! who's set System.err=" + System.err);
+		System.setErr(err);
+	    }
+	}
+	
 	public void close() {
+	    checkIO(); // derek
 	    Marker top = this.current.get();
 	    if ( top == null )
 	        throw new IllegalStateException("No thread io active");
@@ -71,6 +92,7 @@
         assert in != null;
         assert out != null;
         assert err != null;
+        checkIO(); // derek
         Marker marker = new Marker(this,in,out,err, current.get());
 	    this.current.set(marker);
 	    marker.activate();
diff --git a/gogo/test/test/aQute/shell/runtime/TestParser.java b/gogo/test/test/aQute/shell/runtime/TestParser.java
index d957554..5f880bb 100644
--- a/gogo/test/test/aQute/shell/runtime/TestParser.java
+++ b/gogo/test/test/aQute/shell/runtime/TestParser.java
@@ -194,18 +194,18 @@
 
     public void ls() {
         beentheredonethat++;
-        System.out.println("Yes!");
+        System.out.println("ls(): Yes!");
     }
 
     public int ls(int onoff) {
         beentheredonethat += onoff;
-        System.out.println("ls " + onoff);
+        System.out.println("ls(int) " + onoff);
         return onoff;
     }
 
     public void ls(Object args[]) {
         beentheredonethat = args.length;
-        System.out.print("ls [");
+        System.out.print("ls(Object[]) [");
         for (Object i : args)
             System.out.print(i + " ");
         System.out.println("]");