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