Created
January 6, 2015 12:35
-
-
Save ANierbeck/ec27838a728f1e29defc to your computer and use it in GitHub Desktop.
Diff for easier understanding of changes on Karaf for CQL-Shell completion
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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