Skip to content

Instantly share code, notes, and snippets.

@ANierbeck
Created January 6, 2015 12:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ANierbeck/ec27838a728f1e29defc to your computer and use it in GitHub Desktop.
Save ANierbeck/ec27838a728f1e29defc to your computer and use it in GitHub Desktop.
Diff for easier understanding of changes on Karaf for CQL-Shell completion
diff --git a/shell/core/pom.xml b/shell/core/pom.xml
index c7089b6..7622da2 100644
--- a/shell/core/pom.xml
+++ b/shell/core/pom.xml
@@ -151,7 +151,7 @@
org.apache.felix.utils.extender,
org.apache.felix.utils.manifest,
org.apache.felix.gogo.api,
- org.apache.felix.gogo.runtime,
+ org.apache.felix.gogo.runtime;-split-package:=merge-first,
org.apache.felix.gogo.runtime.threadio,
org.apache.felix.service.command,
org.apache.felix.service.threadio,
diff --git a/shell/core/src/main/java/org/apache/felix/gogo/runtime/Closure.java b/shell/core/src/main/java/org/apache/felix/gogo/runtime/Closure.java
new file mode 100644
index 0000000..4994ec3
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/felix/gogo/runtime/Closure.java
@@ -0,0 +1,686 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.runtime;
+
+import java.io.EOFException;
+import java.util.*;
+import java.util.Map.Entry;
+
+import org.apache.felix.gogo.runtime.Tokenizer.Type;
+import org.apache.felix.service.command.CommandSession;
+import org.apache.felix.service.command.Function;
+
+public class Closure implements Function, Evaluate
+{
+ public static final String LOCATION = ".location";
+ private static final String DEFAULT_LOCK = ".defaultLock";
+
+ private static final long serialVersionUID = 1L;
+ private static final ThreadLocal<String> location = new ThreadLocal<String>();
+
+ private final CommandSessionImpl session;
+ private final Closure parent;
+ private final CharSequence source;
+ private final List<List<List<Token>>> program;
+ private final Object script;
+
+ private Token errTok;
+ private Token errTok2;
+ private List<Object> parms = null;
+ private List<Object> parmv = null;
+
+ public Closure(CommandSessionImpl session, Closure parent, CharSequence source) throws Exception
+ {
+ this.session = session;
+ this.parent = parent;
+ this.source = source;
+ script = session.get("0"); // by convention, $0 is script name
+
+ try
+ {
+ program = new Parser(source, isExpansionEnabled()).program(); //CQL-Handling
+ }
+ catch (Exception e)
+ {
+ throw setLocation(e);
+ }
+ }
+
+ public CommandSessionImpl session()
+ {
+ return session;
+ }
+
+ private Exception setLocation(Exception e)
+ {
+ if (session.get(DEFAULT_LOCK) == null)
+ {
+ String loc = location.get();
+ if (null == loc)
+ {
+ loc = (null == script ? "" : script + ":");
+
+ if (e instanceof SyntaxError)
+ {
+ SyntaxError se = (SyntaxError) e;
+ loc += se.line() + "." + se.column();
+ }
+ else if (null != errTok)
+ {
+ loc += errTok.line + "." + errTok.column;
+ }
+
+ location.set(loc);
+ }
+ else if (null != script && !loc.contains(":"))
+ {
+ location.set(script + ":" + loc);
+ }
+
+ session.put(LOCATION, location.get());
+ }
+
+ if (e instanceof EOFError)
+ { // map to public exception, so interactive clients can provide more input
+ EOFException eofe = new EOFException(e.getMessage());
+ eofe.initCause(e);
+ return eofe;
+ }
+
+ return e;
+ }
+
+ // implements Function interface
+ public Object execute(CommandSession x, List<Object> values) throws Exception
+ {
+ try
+ {
+ location.remove();
+ session.variables.remove(LOCATION);
+ return execute(values);
+ }
+ catch (Exception e)
+ {
+ throw setLocation(e);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private Object execute(List<Object> values) throws Exception
+ {
+ if (null != values)
+ {
+ parmv = values;
+ parms = new ArgList(parmv);
+ }
+ else if (null != parent)
+ {
+ // inherit parent closure parameters
+ parms = parent.parms;
+ parmv = parent.parmv;
+ }
+ else
+ {
+ // inherit session parameters
+ Object args = session.get("args");
+ if (null != args && args instanceof List<?>)
+ {
+ parmv = (List<Object>) args;
+ parms = new ArgList(parmv);
+ }
+ }
+
+ Pipe last = null;
+ Object[] mark = Pipe.mark();
+
+ for (List<List<Token>> pipeline : program)
+ {
+ ArrayList<Pipe> pipes = new ArrayList<Pipe>();
+
+ for (List<Token> statement : pipeline)
+ {
+ Pipe current = new Pipe(this, statement);
+
+ if (pipes.isEmpty())
+ {
+ if (current.out == null)
+ {
+ current.setIn(session.in);
+ current.setOut(session.out);
+ current.setErr(session.err);
+ }
+ }
+ else
+ {
+ Pipe previous = pipes.get(pipes.size() - 1);
+ previous.connect(current);
+ }
+ pipes.add(current);
+ }
+
+ if (pipes.size() == 1)
+ {
+ pipes.get(0).run();
+ }
+ else if (pipes.size() > 1)
+ {
+ for (Pipe pipe : pipes)
+ {
+ pipe.start();
+ }
+ try
+ {
+ for (Pipe pipe : pipes)
+ {
+ pipe.join();
+ }
+ }
+ catch (InterruptedException e)
+ {
+ for (Pipe pipe : pipes)
+ {
+ pipe.interrupt();
+ }
+ throw e;
+ }
+ }
+
+ last = pipes.remove(pipes.size() - 1);
+
+ for (Pipe pipe : pipes)
+ {
+ if (pipe.exception != null)
+ {
+ // can't throw exception, as result is defined by last pipe
+ Object oloc = session.get(LOCATION);
+ String loc = (String.valueOf(oloc).contains(":") ? oloc + ": "
+ : "pipe: ");
+ session.err.println(loc + pipe.exception);
+ session.put("pipe-exception", pipe.exception);
+ }
+ }
+
+ if (last.exception != null)
+ {
+ Pipe.reset(mark);
+ throw last.exception;
+ }
+ }
+
+ Pipe.reset(mark); // reset IO in case same thread used for new client
+
+ return last == null ? null : last.result;
+ }
+
+ private Object eval(Object v)
+ {
+ String s = v.toString();
+ if ("null".equals(s))
+ {
+ v = null;
+ }
+ else if ("false".equals(s))
+ {
+ v = false;
+ }
+ else if ("true".equals(s))
+ {
+ v = true;
+ }
+ else
+ {
+ try
+ {
+ v = s;
+ v = Double.parseDouble(s); // if it parses as double
+ v = Long.parseLong(s); // see whether it is integral
+ }
+ catch (NumberFormatException e)
+ {
+ }
+ }
+ return v;
+ }
+
+ public Object eval(final Token t) throws Exception
+ {
+ Object v = null;
+
+ switch (t.type)
+ {
+ case WORD:
+ v = Tokenizer.expand(t, this);
+
+ if (t == v)
+ {
+ v = eval(v);
+ }
+ break;
+
+ case CLOSURE:
+ v = new Closure(session, this, t);
+ break;
+
+ case EXECUTION:
+ v = new Closure(session, this, t).execute(session, parms);
+ break;
+
+ case ARRAY:
+ v = array(t);
+ break;
+
+ case ASSIGN:
+ v = t.type;
+ break;
+
+ case EXPR:
+ v = expr(t.value);
+ break;
+
+ default:
+ throw new SyntaxError(t.line, t.column, "unexpected token: " + t.type);
+ }
+
+ return v;
+ }
+
+ //extra handling for CQL-Shell
+ private boolean isExpansionEnabled() {
+ Object v = session.get("org.apache.felix.gogo.expansion");
+ if (v != null) {
+ return Boolean.parseBoolean(v.toString());
+ }
+ return true;
+ }
+
+ /*
+ * executeStatement handles the following cases:
+ * <string> '=' word // simple assignment
+ * <string> '=' word word.. // complex assignment
+ * <bareword> word.. // command invocation
+ * <object> // value of <object>
+ * <object> word.. // method call
+ */
+ public Object executeStatement(List<Token> statement) throws Exception
+ {
+ Object echo = session.get("echo");
+ String xtrace = null;
+
+ if (echo != null && !"false".equals(echo.toString()))
+ {
+ // set -x execution trace
+ StringBuilder buf = new StringBuilder("+");
+ for (Token token : statement)
+ {
+ buf.append(' ');
+ buf.append(token.source());
+ }
+ xtrace = buf.toString();
+ session.err.println(xtrace);
+ }
+
+ List<Object> values = new ArrayList<Object>();
+ errTok = statement.get(0);
+
+ if ((statement.size() > 3) && Type.ASSIGN.equals(statement.get(1).type))
+ {
+ errTok2 = statement.get(2);
+ }
+
+ for (Token t : statement)
+ {
+ Object v = isExpansionEnabled() ? eval(t) : t.toString();
+
+ if ((Type.EXECUTION == t.type) && (statement.size() == 1))
+ {
+ return v;
+ }
+
+ if (parms == v && parms != null)
+ {
+ values.addAll(parms); // explode $args array
+ }
+ else
+ {
+ values.add(v);
+ }
+ }
+
+ Object cmd = values.remove(0);
+ if (cmd == null)
+ {
+ if (values.isEmpty())
+ {
+ return null;
+ }
+
+ throw new RuntimeException("Command name evaluates to null: " + errTok);
+ }
+
+ if (cmd instanceof CharSequence && values.size() > 0
+ && Type.ASSIGN.equals(values.get(0)))
+ {
+ values.remove(0);
+ String scmd = cmd.toString();
+ Object value;
+
+ if (values.size() == 0)
+ {
+ return session.variables.remove(scmd);
+ }
+
+ if (values.size() == 1)
+ {
+ value = values.get(0);
+ }
+ else
+ {
+ cmd = values.remove(0);
+ if (null == cmd)
+ {
+ throw new RuntimeException("Command name evaluates to null: "
+ + errTok2);
+ }
+
+ trace2(xtrace, cmd, values);
+
+ value = bareword(statement.get(2)) ? executeCmd(cmd.toString(), values)
+ : executeMethod(cmd, values);
+ }
+
+ return assignment(scmd, value);
+ }
+
+ trace2(xtrace, cmd, values);
+
+ return bareword(statement.get(0)) ? executeCmd(cmd.toString(), values)
+ : executeMethod(cmd, values);
+ }
+
+ // second level expanded execution trace
+ private void trace2(String trace1, Object cmd, List<Object> values)
+ {
+ if ("verbose".equals(session.get("echo")))
+ {
+ StringBuilder buf = new StringBuilder("+ " + cmd);
+
+ for (Object value : values)
+ {
+ buf.append(' ');
+ buf.append(value);
+ }
+
+ String trace2 = buf.toString();
+
+ if (!trace2.equals(trace1))
+ {
+ session.err.println("+" + trace2);
+ }
+ }
+ }
+
+ private boolean bareword(Token t) throws Exception
+ {
+ return ((t.type == Type.WORD) && (t == Tokenizer.expand(t, this)) && (eval((Object) t) instanceof String));
+ }
+
+ private Object executeCmd(String scmd, List<Object> values) throws Exception
+ {
+ 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))
+ {
+ // try default command handler
+ if (session.get(DEFAULT_LOCK) == null)
+ {
+ x = get("default");
+ if (x == null)
+ {
+ x = get("*:default");
+ }
+
+ if (x instanceof Function)
+ {
+ try
+ {
+ session.put(DEFAULT_LOCK, true);
+ values.add(0, scmd);
+ return ((Function) x).execute(session, values);
+ }
+ finally
+ {
+ session.variables.remove(DEFAULT_LOCK);
+ }
+ }
+ }
+
+ throw new CommandNotFoundException(scmd);
+ }
+ }
+ return ((Function) x).execute(session, values);
+ }
+
+ private Object executeMethod(Object cmd, List<Object> values) throws Exception
+ {
+ if (values.isEmpty())
+ {
+ return cmd;
+ }
+
+ boolean dot = values.size() > 1 && ".".equals(String.valueOf(values.get(0)));
+
+ // FELIX-1473 - allow method chaining using dot pseudo-operator, e.g.
+ // (bundle 0) . loadClass java.net.InetAddress . localhost . hostname
+ // (((bundle 0) loadClass java.net.InetAddress ) localhost ) hostname
+ if (dot)
+ {
+ Object target = cmd;
+ ArrayList<Object> args = new ArrayList<Object>();
+ values.remove(0);
+
+ for (Object arg : values)
+ {
+ if (".".equals(arg))
+ {
+ target = Reflective.invoke(session, target,
+ args.remove(0).toString(), args);
+ args.clear();
+ }
+ else
+ {
+ args.add(arg);
+ }
+ }
+
+ if (args.size() == 0)
+ {
+ return target;
+ }
+
+ return Reflective.invoke(session, target, args.remove(0).toString(), args);
+ }
+ else if (cmd.getClass().isArray() && values.size() == 1)
+ {
+ Object[] cmdv = (Object[]) cmd;
+ String index = values.get(0).toString();
+ return "length".equals(index) ? cmdv.length : cmdv[Integer.parseInt(index)];
+ }
+ else
+ {
+ return Reflective.invoke(session, cmd, values.remove(0).toString(), values);
+ }
+ }
+
+ private Object assignment(String name, Object value)
+ {
+ session.variables.put(name, value);
+ return value;
+ }
+
+ private Object expr(CharSequence expr) throws Exception
+ {
+ return session.expr(expr);
+ }
+
+ private Object array(Token array) throws Exception
+ {
+ List<Token> list = new ArrayList<Token>();
+ Map<Token, Token> map = new LinkedHashMap<Token, Token>();
+ (new Parser(array, isExpansionEnabled())).array(list, map); //CQL-Handling
+
+ if (map.isEmpty())
+ {
+ List<Object> olist = new ArrayList<Object>();
+ for (Token t : list)
+ {
+ Object oval = eval(t);
+ if (oval.getClass().isArray())
+ {
+ for (Object o : (Object[]) oval)
+ {
+ olist.add(o);
+ }
+ }
+ else
+ {
+ olist.add(oval);
+ }
+ }
+ return olist;
+ }
+ else
+ {
+ Map<Object, Object> omap = new LinkedHashMap<Object, Object>();
+ for (Entry<Token, Token> e : map.entrySet())
+ {
+ Token key = e.getKey();
+ Object k = eval(key);
+ if (!(k instanceof String))
+ {
+ throw new SyntaxError(key.line, key.column,
+ "map key null or not String: " + key);
+ }
+ omap.put(k, eval(e.getValue()));
+ }
+ return omap;
+ }
+ }
+
+ public Object get(String name)
+ {
+ if (parms != null)
+ {
+ if ("args".equals(name))
+ {
+ return parms;
+ }
+
+ if ("argv".equals(name))
+ {
+ return parmv;
+ }
+
+ if ("it".equals(name))
+ {
+ return parms.get(0);
+ }
+
+ if (name.length() == 1 && Character.isDigit(name.charAt(0)))
+ {
+ int i = name.charAt(0) - '0';
+ if (i > 0)
+ {
+ return parms.get(i - 1);
+ }
+ }
+ }
+
+ return session.get(name);
+ }
+
+ public Object put(String key, Object value)
+ {
+ return session.variables.put(key, value);
+ }
+
+ @Override
+ public String toString()
+ {
+ return source.toString().trim().replaceAll("\n+", "\n").replaceAll(
+ "([^\\\\{(\\[])\n", "\\1;").replaceAll("[ \\\\\t\n]+", " ");
+ }
+
+ /**
+ * List that overrides toString() for implicit $args expansion.
+ * Also checks for index out of bounds, so that $1 evaluates to null
+ * rather than throwing IndexOutOfBoundsException.
+ * e.g. x = { a$args }; x 1 2 => a1 2 and not a[1, 2]
+ */
+ class ArgList extends AbstractList<Object>
+ {
+ private List<Object> list;
+
+ public ArgList(List<Object> args)
+ {
+ this.list = args;
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder buf = new StringBuilder();
+ for (Object o : list)
+ {
+ if (buf.length() > 0)
+ buf.append(' ');
+ buf.append(o);
+ }
+ return buf.toString();
+ }
+
+ @Override
+ public Object get(int index)
+ {
+ return index < list.size() ? list.get(index) : null;
+ }
+
+ @Override
+ public Object remove(int index)
+ {
+ return list.remove(index);
+ }
+
+ @Override
+ public int size()
+ {
+ return list.size();
+ }
+ }
+
+}
diff --git a/shell/core/src/main/java/org/apache/felix/gogo/runtime/CommandSessionImpl.java b/shell/core/src/main/java/org/apache/felix/gogo/runtime/CommandSessionImpl.java
new file mode 100644
index 0000000..ee3c09b
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/felix/gogo/runtime/CommandSessionImpl.java
@@ -0,0 +1,404 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+// DWB8: throw IllegatlStateException if session used after closed (as per rfc132)
+// DWB9: there is no API to list all variables: https://www.osgi.org/bugzilla/show_bug.cgi?id=49
+// DWB10: add SCOPE support: https://www.osgi.org/bugzilla/show_bug.cgi?id=51
+package org.apache.felix.gogo.runtime;
+
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Formatter;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.felix.service.command.CommandSession;
+import org.apache.felix.service.command.Converter;
+import org.apache.felix.service.command.Function;
+import org.apache.felix.service.threadio.ThreadIO;
+
+public class CommandSessionImpl implements CommandSession, Converter
+{
+ public static final String SESSION_CLOSED = "session is closed";
+ public static final String VARIABLES = ".variables";
+ public static final String COMMANDS = ".commands";
+ private static final String COLUMN = "%-20s %s\n";
+
+ protected InputStream in;
+ protected PrintStream out;
+ PrintStream err;
+
+ private final CommandProcessorImpl processor;
+ protected final Map<String, Object> variables = new HashMap<String, Object>();
+ private boolean closed;
+
+ protected CommandSessionImpl(CommandProcessorImpl shell, InputStream in, PrintStream out, PrintStream err)
+ {
+ this.processor = shell;
+ this.in = in;
+ this.out = out;
+ this.err = err;
+ }
+
+ ThreadIO threadIO()
+ {
+ return processor.threadIO;
+ }
+
+ public void close()
+ {
+ this.closed = true;
+ }
+
+ public Object execute(CharSequence commandline) throws Exception
+ {
+ assert processor != null;
+ assert processor.threadIO != null;
+
+ if (closed)
+ {
+ throw new IllegalStateException(SESSION_CLOSED);
+ }
+
+ processor.beforeExecute(this, commandline);
+
+ try
+ {
+ Closure impl = new Closure(this, null, commandline);
+ Object result = impl.execute(this, null);
+ processor.afterExecute(this, commandline, result);
+ return result;
+ }
+ catch (Exception e)
+ {
+ processor.afterExecute(this, commandline, e);
+ throw e;
+ }
+ }
+
+ public InputStream getKeyboard()
+ {
+ return in;
+ }
+
+ public Object get(String name)
+ {
+ // there is no API to list all variables, so overload name == null
+ if (name == null || VARIABLES.equals(name))
+ {
+ return Collections.unmodifiableSet(variables.keySet());
+ }
+
+ if (COMMANDS.equals(name))
+ {
+ return processor.getCommands();
+ }
+
+ Object val = processor.constants.get(name);
+ if( val != null )
+ {
+ return val;
+ }
+
+ val = variables.get("#" + name);
+ if (val instanceof Function)
+ {
+ try
+ {
+ val = ((Function) val).execute(this, null);
+ }
+ catch (Exception e)
+ {
+ // Ignore
+ }
+ return val;
+ }
+ else if( val != null )
+ {
+ return val;
+ }
+
+ val = variables.get(name);
+ if( val != null )
+ {
+ return val;
+ }
+
+ return processor.getCommand(name, variables.get("SCOPE"));
+ }
+
+ public void put(String name, Object value)
+ {
+ synchronized (variables)
+ {
+ 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 : processor.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();
+ Collection<?> c = (Collection<?>) target;
+ sb.append("[");
+ for (Object o : c)
+ {
+ if (sb.length() > 1)
+ {
+ sb.append(", ");
+ }
+ sb.append(format(o, level + 1, this));
+ }
+ 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();
+ Map<?, ?> c = (Map<?, ?>) target;
+ sb.append("[");
+ for (Map.Entry<?, ?> entry : c.entrySet())
+ {
+ if (sb.length() > 1)
+ {
+ sb.append(", ");
+ }
+ sb.append(format(entry, level + 1, this));
+ }
+ 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 processor.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;
+ }
+ }
+
+ public Object expr(CharSequence expr)
+ {
+ return processor.expr(this, expr);
+ }
+
+}
diff --git a/shell/core/src/main/java/org/apache/felix/gogo/runtime/Parser.java b/shell/core/src/main/java/org/apache/felix/gogo/runtime/Parser.java
new file mode 100644
index 0000000..b036830
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/felix/gogo/runtime/Parser.java
@@ -0,0 +1,181 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+// DWB14: parser loops if // comment at start of program
+// DWB15: allow program to have trailing ';'
+package org.apache.felix.gogo.runtime;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.felix.gogo.runtime.Tokenizer.Type;
+
+public class Parser
+{
+ private final Tokenizer tz;
+
+ public Parser(CharSequence program) {
+ tz = new Tokenizer(program, null, false, true);
+ }
+
+ public Parser(CharSequence program, boolean isExpansionEnabled)
+ {
+ tz = new Tokenizer(program, null, false, isExpansionEnabled);
+ }
+
+ public List<List<List<Token>>> program()
+ {
+ List<List<List<Token>>> program = new ArrayList<List<List<Token>>>();
+
+ while (tz.next() != Type.EOT)
+ {
+ program.add(pipeline());
+
+ switch (tz.type())
+ {
+ case SEMICOLON:
+ case NEWLINE:
+ continue;
+ }
+
+ break;
+ }
+
+ if (tz.next() != Type.EOT)
+ throw new RuntimeException("Program has trailing text: " + tz.value());
+
+ return program;
+ }
+
+ private List<List<Token>> pipeline()
+ {
+ List<List<Token>> pipeline = new ArrayList<List<Token>>();
+
+ while (true)
+ {
+ pipeline.add(command());
+ switch (tz.type())
+ {
+ case PIPE:
+ if (tz.next() == Type.EOT)
+ {
+ Token t = tz.token();
+ throw new EOFError(t.line, t.column, "unexpected EOT after pipe '|'");
+ }
+ break;
+
+ default:
+ return pipeline;
+ }
+ }
+ }
+
+ private List<Token> command()
+ {
+ List<Token> command = new ArrayList<Token>();
+
+ while (true)
+ {
+ Token t = tz.token();
+
+ switch (t.type)
+ {
+ case WORD:
+ case CLOSURE:
+ case EXECUTION:
+ case ARRAY:
+ case ASSIGN:
+ case EXPR:
+ break;
+
+ default:
+ throw new SyntaxError(t.line, t.column, "unexpected token: " + t.type);
+ }
+
+ command.add(t);
+
+ switch (tz.next())
+ {
+ case PIPE:
+ case SEMICOLON:
+ case NEWLINE:
+ case EOT:
+ return command;
+ }
+ }
+ }
+
+ public void array(List<Token> list, Map<Token, Token> map) throws Exception
+ {
+ Token lt = null;
+ boolean isMap = false;
+
+ while (tz.next() != Type.EOT)
+ {
+ if (isMap)
+ {
+ Token key = lt;
+ lt = null;
+ if (null == key)
+ {
+ key = tz.token();
+
+ if (tz.next() != Type.ASSIGN)
+ {
+ Token t = tz.token();
+ throw new SyntaxError(t.line, t.column,
+ "map expected '=', found: " + t);
+ }
+
+ tz.next();
+ }
+
+ Token k = (list.isEmpty() ? key : list.remove(0));
+ Token v = tz.token();
+ map.put(k, v);
+ }
+ else
+ {
+ switch (tz.type())
+ {
+ case WORD:
+ case CLOSURE:
+ case EXECUTION:
+ case ARRAY:
+ lt = tz.token();
+ list.add(lt);
+ break;
+
+ case ASSIGN:
+ if (list.size() == 1)
+ {
+ isMap = true;
+ break;
+ }
+ // fall through
+ default:
+ lt = tz.token();
+ throw new SyntaxError(lt.line, lt.column,
+ "unexpected token in list: " + lt);
+ }
+ }
+ }
+ }
+
+}
diff --git a/shell/core/src/main/java/org/apache/felix/gogo/runtime/Tokenizer.java b/shell/core/src/main/java/org/apache/felix/gogo/runtime/Tokenizer.java
new file mode 100644
index 0000000..866db9e
--- /dev/null
+++ b/shell/core/src/main/java/org/apache/felix/gogo/runtime/Tokenizer.java
@@ -0,0 +1,813 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.gogo.runtime;
+
+/**
+ * Bash-like tokenizer.
+ *
+ * Single and double quotes are just like Bash - single quotes escape everything
+ * (including backslashes), newlines are allowed in quotes.
+ * backslash-newline indicates a line continuation and is removed.
+ *
+ * Variable expansion is just like Bash: $NAME or ${NAME[[:][-+=?WORD]},
+ * except it can yield any Object. Variables expanded within double-quotes,
+ * or adjacent to a String are converted to String.
+ *
+ * Unlike bash, indirect variable expansion is supported using ${$NAME}.
+ *
+ * Only a single variable assignment is recognized, with '=' being the second token.
+ * (Bash allows name1=value1 name2=value2 ... command args)
+ *
+ * Comments can only start where white space is allowed:
+ * # or // starts a line comment, /* starts a block comment.
+ * The following common uses do NOT start comments:
+ * ls http://example.com#anchor
+ * ls $dir/*.java
+ *
+ * @see http://wiki.bash-hackers.org/syntax/basicgrammar
+ */
+public class Tokenizer
+{
+ public enum Type
+ {
+ ASSIGN('='), PIPE('|'), SEMICOLON(';'), NEWLINE, ARRAY, CLOSURE, EXPR, EXECUTION, WORD, EOT;
+
+ private char c;
+
+ Type()
+ {
+ }
+
+ Type(char c)
+ {
+ this.c = c;
+ }
+
+ @Override
+ public String toString()
+ {
+ return (c == 0 ? super.toString() : "'" + c + "'");
+ }
+ }
+
+ private static final boolean DEBUG = false;
+ private static final char EOT = (char) -1;
+
+ private final CharSequence text;
+ private final Evaluate evaluate;
+ private final boolean inArray;
+ private final boolean inQuote;
+ //extra to disable some special handling of brackets etc. needed for CQL-Completion
+ private final boolean isExpansionEnabled;
+
+ private Type type = Type.NEWLINE;
+ private CharSequence value;
+ private Token token;
+
+ private short line;
+ private short column;
+ private char ch;
+ private int index;
+ private boolean firstWord;
+
+ public Tokenizer(CharSequence text)
+ {
+ this(text, null, false, true);
+ }
+
+ public Tokenizer(CharSequence text, Evaluate evaluate, boolean inQuote)
+ {
+ this(text, evaluate, inQuote, true);
+ }
+
+ //This constructor gets the isExpansionEnabled flag for extra handling of a CQL-Like shell
+ public Tokenizer(CharSequence text, Evaluate evaluate, boolean inQuote, boolean isExpansionEnabled)
+ {
+ this.text = text;
+ this.evaluate = evaluate;
+ this.inQuote = inQuote;
+ this.isExpansionEnabled = isExpansionEnabled;
+ index = 0;
+ line = column = 1;
+
+ boolean array = false;
+
+ if (text instanceof Token)
+ {
+ Token t = (Token) text;
+ line = t.line;
+ column = t.column;
+ array = (Type.ARRAY == t.type);
+ }
+
+ inArray = array;
+ getch();
+
+ if (DEBUG)
+ {
+ if (inArray)
+ System.err.println("Tokenizer[" + text + "]");
+ else
+ System.err.println("Tokenizer<" + text + ">");
+ }
+ }
+
+ public Type type()
+ {
+ return type;
+ }
+
+ public CharSequence value()
+ {
+ return value;
+ }
+
+ public Token token()
+ {
+ return token;
+ }
+
+ public Type next()
+ {
+ final Type prevType = type;
+ token = null;
+ value = null;
+
+ short tLine;
+ short tColumn;
+
+ while (true)
+ {
+ skipSpace();
+ tLine = line;
+ tColumn = column;
+
+ switch (ch)
+ {
+ case EOT:
+ type = Type.EOT;
+ break;
+
+ case '\n':
+ getch();
+ if (inArray)
+ continue;
+ // only return NEWLINE once and not if not preceded by ; or |
+ switch (prevType)
+ {
+ case PIPE:
+ case SEMICOLON:
+ case NEWLINE:
+ continue;
+
+ default:
+ type = Type.NEWLINE;
+ break;
+ }
+ break;
+
+ case '{':
+ case '(':
+ case '[':
+ if (isExpansionEnabled) { //Disabled for CQL
+ value = group();
+ getch();
+ } else {
+ //treat it as normal word
+ value = word();
+ type = Type.WORD;
+ }
+ break;
+
+ case ';':
+ getch();
+ type = Type.SEMICOLON;
+ break;
+
+ case '|':
+ getch();
+ type = Type.PIPE;
+ break;
+
+ case '=':
+ if (firstWord || inArray)
+ {
+ getch();
+ type = Type.ASSIGN;
+ break;
+ }
+ // fall through
+ default:
+ value = word();
+ type = Type.WORD;
+ }
+
+ firstWord = (Type.WORD == type && (Type.WORD != prevType && Type.ASSIGN != prevType));
+ token = new Token(type, value, tLine, tColumn);
+
+ if (DEBUG)
+ {
+ System.err.print("<" + type + ">");
+ if (Type.EOT == type)
+ {
+ System.err.println();
+ }
+ }
+
+ return type;
+ }
+ }
+
+ private CharSequence word()
+ {
+ int start = index - 1;
+ int skipCR = 0;
+
+ do
+ {
+ switch (ch)
+ {
+ case '\n':
+ if (index >= 2 && text.charAt(index - 2) == '\r')
+ skipCR = 1;
+ // fall through
+ case '=':
+ if ((Type.WORD == type || Type.ASSIGN == type) && '=' == ch
+ && !inArray)
+ continue;
+ // fall through
+ case ' ':
+ case '\t':
+ case '|':
+ case ';':
+ return text.subSequence(start, index - 1 - skipCR);
+
+ case '{':
+ group();
+ break;
+
+ case '\\':
+ escape();
+ break;
+
+ case '\'':
+ case '"':
+ skipQuote();
+ break;
+ }
+ }
+ while (getch() != EOT);
+
+ return text.subSequence(start, index - 1);
+ }
+
+ private CharSequence group()
+ {
+ final char push = ch;
+ final char push2;
+ final char pop;
+ final char pop2;
+
+ switch (ch)
+ {
+ case '{':
+ type = Type.CLOSURE;
+ push2 = 0;
+ pop = '}';
+ pop2 = 0;
+ break;
+ case '(':
+ if (peek() == '(') {
+ getch();
+ push2 = '(';
+ type = Type.EXPR;
+ pop = ')';
+ pop2 = ')';
+ } else {
+ type = Type.EXECUTION;
+ push2 = 0;
+ pop = ')';
+ pop2 = 0;
+ }
+ break;
+ case '[':
+ type = Type.ARRAY;
+ push2 = 0;
+ pop = ']';
+ pop2 = 0;
+ break;
+ default:
+ assert false;
+ push2 = 0;
+ pop = 0;
+ pop2 = 0;
+ }
+
+ short sLine = line;
+ short sCol = column;
+ int start = index;
+ int depth = 1;
+
+ while (true)
+ {
+ boolean comment = false;
+
+ switch (ch)
+ {
+ case '{':
+ case '(':
+ case '[':
+ case '\n':
+ comment = true;
+ break;
+ }
+
+ if (getch() == EOT)
+ {
+ throw new EOFError(sLine, sCol, "unexpected EOT looking for matching '"
+ + pop + "'");
+ }
+
+ // don't recognize comments that start within a word
+ if (comment || isBlank(ch))
+ skipSpace();
+
+ switch (ch)
+ {
+ case '"':
+ case '\'':
+ skipQuote();
+ break;
+
+ case '\\':
+ ch = escape();
+ break;
+
+ default:
+ if (push == ch) {
+ depth++;
+ }
+ else if (pop == ch && --depth == 0) {
+ if (pop2 == 0)
+ return text.subSequence(start, index - 1);
+ else if (pop2 == peek()) {
+ getch();
+ return text.subSequence(start, index - 2);
+ }
+ }
+ }
+ }
+
+ }
+
+ private char escape()
+ {
+ assert '\\' == ch;
+
+ switch (getch())
+ {
+ case 'u':
+ getch();
+ getch();
+ getch();
+ getch();
+
+ if (EOT == ch)
+ {
+ throw new EOFError(line, column, "unexpected EOT in \\u escape");
+ }
+
+ String u = text.subSequence(index - 4, index).toString();
+
+ try
+ {
+ return (char) Integer.parseInt(u, 16);
+ }
+ catch (NumberFormatException e)
+ {
+ throw new SyntaxError(line, column, "bad unicode escape: \\u" + u);
+ }
+
+ case EOT:
+ throw new EOFError(line, column, "unexpected EOT in \\ escape");
+
+ case '\n':
+ return '\0'; // line continuation
+
+ case '\\':
+ case '\'':
+ case '"':
+ case '$':
+ return ch;
+
+ default:
+ return ch;
+ }
+ }
+
+ private void skipQuote()
+ {
+ assert '\'' == ch || '"' == ch;
+ final char quote = ch;
+ final short sLine = line;
+ final short sCol = column;
+
+ while (getch() != EOT)
+ {
+ if (quote == ch)
+ return;
+
+ if ((quote == '"') && ('\\' == ch))
+ escape();
+ }
+
+ throw new EOFError(sLine, sCol, "unexpected EOT looking for matching quote: "
+ + quote);
+ }
+
+ private void skipSpace()
+ {
+ while (true)
+ {
+ while (isBlank(ch))
+ {
+ getch();
+ }
+
+ // skip continuation lines, but not other escapes
+ if (('\\' == ch) && (peek() == '\n'))
+ {
+ getch();
+ getch();
+ continue;
+ }
+
+ // skip comments
+ if (('/' == ch) || ('#' == ch))
+ {
+ if (('#' == ch) || (peek() == '/'))
+ {
+ while ((getch() != EOT) && ('\n' != ch))
+ {
+ }
+ continue;
+ }
+ else if ('*' == peek())
+ {
+ short sLine = line;
+ short sCol = column;
+ getch();
+
+ while ((getch() != EOT) && !(('*' == ch) && (peek() == '/')))
+ {
+ }
+
+ if (EOT == ch)
+ {
+ throw new EOFError(sLine, sCol,
+ "unexpected EOT looking for closing comment: */");
+ }
+
+ getch();
+ getch();
+ continue;
+ }
+ }
+
+ break;
+ }
+ }
+
+ private boolean isBlank(char ch)
+ {
+ return ' ' == ch || '\t' == ch;
+ }
+
+ private boolean isName(char ch)
+ {
+ return Character.isJavaIdentifierPart(ch) && (ch != '$') || ('.' == ch);
+ }
+
+ /**
+ * expand variables, quotes and escapes in word.
+ * @param vars
+ * @return
+ * @throws Exception
+ */
+ public static Object expand(CharSequence word, Evaluate eval) throws Exception
+ {
+ return expand(word, eval, false);
+ }
+
+ private static Object expand(CharSequence word, Evaluate eval, boolean inQuote) throws Exception
+ {
+ final String special = "$\\\"'";
+ int i = word.length();
+
+ while ((--i >= 0) && (special.indexOf(word.charAt(i)) == -1))
+ {
+ }
+
+ // shortcut if word doesn't contain any special characters
+ if (i < 0)
+ return word;
+
+ return new Tokenizer(word, eval, inQuote).expand();
+ }
+
+ public Object expand(CharSequence word, short line, short column) throws Exception
+ {
+ return expand(new Token(Type.WORD, word, line, column), evaluate, inQuote);
+ }
+
+ private Token word(CharSequence value)
+ {
+ return new Token(Type.WORD, value, line, column);
+ }
+
+ private Object expand() throws Exception
+ {
+ StringBuilder buf = new StringBuilder();
+
+ while (ch != EOT)
+ {
+ int start = index;
+
+ switch (ch)
+ {
+ case '$':
+ Object val = expandVar();
+
+ if (EOT == ch && buf.length() == 0)
+ {
+ return val;
+ }
+
+ if (null != val)
+ {
+ buf.append(val);
+ }
+
+ continue; // expandVar() has already read next char
+
+ case '\\':
+ ch = (inQuote && ("u$\\\n\"".indexOf(peek()) == -1)) ? '\\'
+ : escape();
+
+ if (ch != '\0') // ignore line continuation
+ {
+ buf.append(ch);
+ }
+
+ break;
+
+ case '"':
+ Token ww = word(null);
+ skipQuote();
+ ww.value = text.subSequence(start, index - 1);
+ value = ww;
+ Object expand = expand(value, evaluate, true);
+
+ if (eot() && buf.length() == 0 && value == expand)
+ {
+ // FELIX-2468 avoid returning CharSequence implementation
+ return ww.value.toString();
+ }
+
+ if (null != expand)
+ {
+ buf.append(expand.toString());
+ }
+ break;
+
+ case '\'':
+ if (!inQuote)
+ {
+ skipQuote();
+ value = text.subSequence(start, index - 1);
+
+ if (eot() && buf.length() == 0)
+ {
+ return value;
+ }
+
+ buf.append(value);
+ break;
+ }
+ // else fall through
+ default:
+ buf.append(ch);
+ }
+
+ getch();
+ }
+
+ return buf.toString();
+ }
+
+ private Object expandVar() throws Exception
+ {
+ assert '$' == ch;
+ Object val;
+
+ if (getch() != '{')
+ {
+ if ('(' == ch)
+ {
+ short sLine = line;
+ short sCol = column;
+ if ('(' == peek())
+ {
+ val = evaluate.eval(new Token(Type.EXPR, group(), sLine, sCol));
+ getch();
+ }
+ else
+ {
+ // support $(...) FELIX-2433
+ val = evaluate.eval(new Token(Type.EXECUTION, group(), sLine, sCol));
+ getch();
+ }
+ }
+ else
+ {
+ int start = index - 1;
+ while (isName(ch))
+ {
+ getch();
+ }
+
+ if (index - 1 == start)
+ {
+ val = "$";
+ }
+ else
+ {
+ String name = text.subSequence(start, index - 1).toString();
+ val = evaluate.get(name);
+ }
+ }
+ }
+ else
+ {
+ // ${NAME[[:]-+=?]WORD}
+ short sLine = line;
+ short sCol = column;
+ CharSequence group = group();
+ char c;
+ int i = 0;
+
+ while (i < group.length())
+ {
+ switch (group.charAt(i))
+ {
+ case ':':
+ case '-':
+ case '+':
+ case '=':
+ case '?':
+ break;
+
+ default:
+ ++i;
+ continue;
+ }
+ break;
+ }
+
+ sCol += i;
+
+ String name = String.valueOf(expand(group.subSequence(0, i), sLine, sCol));
+
+ for (int j = 0; j < name.length(); ++j)
+ {
+ if (!isName(name.charAt(j)))
+ {
+ throw new SyntaxError(sLine, sCol, "bad name: ${" + group + "}");
+ }
+ }
+
+ val = evaluate.get(name);
+
+ if (i < group.length())
+ {
+ c = group.charAt(i++);
+ if (':' == c)
+ {
+ c = (i < group.length() ? group.charAt(i++) : EOT);
+ }
+
+ CharSequence word = group.subSequence(i, group.length());
+
+ switch (c)
+ {
+ case '-':
+ case '=':
+ if (null == val)
+ {
+ val = expand(word, evaluate, false);
+ if ('=' == c)
+ {
+ evaluate.put(name, val);
+ }
+ }
+ break;
+
+ case '+':
+ if (null != val)
+ {
+ val = expand(word, evaluate, false);
+ }
+ break;
+
+ case '?':
+ if (null == val)
+ {
+ val = expand(word, evaluate, false);
+ if (null == val || val.toString().length() == 0)
+ {
+ val = "parameter not set";
+ }
+ throw new IllegalArgumentException(name + ": " + val);
+ }
+ break;
+
+ default:
+ throw new SyntaxError(sLine, sCol, "bad substitution: ${" + group
+ + "}");
+ }
+ }
+ getch();
+ }
+
+ return val;
+ }
+
+ /**
+ * returns true if getch() will return EOT
+ * @return
+ */
+ private boolean eot()
+ {
+ return index >= text.length();
+ }
+
+ private char getch()
+ {
+ return ch = getch(false);
+ }
+
+ private char peek()
+ {
+ return getch(true);
+ }
+
+ private char getch(boolean peek)
+ {
+ if (eot())
+ {
+ if (!peek)
+ {
+ ++index;
+ ch = EOT;
+ }
+ return EOT;
+ }
+
+ int current = index;
+ char c = text.charAt(index++);
+
+ if (('\r' == c) && !eot() && (text.charAt(index) == '\n'))
+ c = text.charAt(index++);
+
+ if (peek)
+ {
+ index = current;
+ }
+ else if ('\n' == c)
+ {
+ ++line;
+ column = 0;
+ }
+ else
+ ++column;
+
+ return c;
+ }
+
+}
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/CompleterAsCompletor.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/CompleterAsCompletor.java
index fb8ebfb..e732dfa 100644
--- a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/CompleterAsCompletor.java
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/CompleterAsCompletor.java
@@ -34,8 +34,17 @@ public class CompleterAsCompletor implements jline.console.completer.Completer {
this.completer = completer;
}
- public int complete(String buffer, int cursor, List candidates) {
- return completer.complete(session, CommandLineImpl.build(buffer, cursor), candidates);
+ @SuppressWarnings("unchecked")
+ public int complete(String buffer, int cursor, @SuppressWarnings("rawtypes") List candidates) {
+ return completer.complete(session, CommandLineImpl.build(buffer, cursor, isExpansionEnabled()), candidates); //CQL-Handling
}
+ //special handling for CQL-Shell, disables the brackets from completion
+ private boolean isExpansionEnabled() {
+ Object v = session.get("org.apache.felix.gogo.expansion");
+ if (v != null) {
+ return Boolean.parseBoolean(v.toString());
+ }
+ return true;
+ }
}
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/parsing/CommandLineImpl.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/parsing/CommandLineImpl.java
index 6a603d0..9124795 100644
--- a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/parsing/CommandLineImpl.java
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/parsing/CommandLineImpl.java
@@ -34,7 +34,11 @@ public class CommandLineImpl implements CommandLine {
private final String buffer;
public static CommandLine build(String buffer, int cursor) {
- Parser parser = new Parser(buffer, cursor);
+ return build(buffer, cursor, true);
+ }
+
+ public static CommandLine build(String buffer, int cursor, boolean expansionEnabled) { //CQL-Handling
+ Parser parser = new Parser(buffer, cursor, expansionEnabled);
try {
List<List<List<String>>> program = parser.program();
List<String> pipe = program.get(parser.c0).get(parser.c1);
@@ -42,7 +46,7 @@ public class CommandLineImpl implements CommandLine {
} catch (Throwable t) {
return new CommandLineImpl(new String[] { buffer }, 0, cursor, cursor, buffer);
}
- }
+ }
/**
* @param arguments the array of tokens
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/parsing/Parser.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/parsing/Parser.java
index 16f6a21..73f4b98 100644
--- a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/parsing/Parser.java
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/parsing/Parser.java
@@ -39,13 +39,21 @@ public class Parser {
int c1;
int c2;
int c3;
+
+ private boolean isExpansionEnabled; //CQL-Handling
public Parser(String text, int cursor) {
- this.text = text;
- this.cursor = cursor;
+ this(text, cursor, true);
}
- void ws() {
+ //CQL-Handling
+ public Parser(String text, int cursor, boolean expansionEnabled) {
+ this.text = text;
+ this.cursor = cursor;
+ this.isExpansionEnabled = expansionEnabled; //CQL-Handling
+ }
+
+ void ws() {
// derek: BUGFIX: loop if comment at beginning of input
//while (!eof() && Character.isWhitespace(peek())) {
while (!eof() && (!escaped && Character.isWhitespace(peek()) || current == 0)) {
@@ -226,7 +234,7 @@ public class Parser {
start = current;
try {
char c = next();
- if (!escaped) {
+ if (!escaped && isExpansionEnabled) { //CQL-Handling
switch (c) {
case '{':
return text.substring(start, find('}', '{'));
@@ -252,11 +260,11 @@ public class Parser {
if (Character.isWhitespace(c) || c == ';' || c == '|' || c == '=') {
break;
}
- else if (c == '{') {
+ else if (c == '{' && isExpansionEnabled) { //CQL-Handling
next();
find('}', '{');
}
- else if (c == '(') {
+ else if (c == '(' && isExpansionEnabled) { //CQL-Handling
next();
find(')', '(');
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment