You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@karaf.apache.org by gn...@apache.org on 2010/12/10 17:04:48 UTC

svn commit: r1044414 - in /karaf/trunk/shell: commands/src/main/java/org/apache/karaf/shell/commands/ console/src/main/java/org/apache/felix/gogo/runtime/ console/src/main/java/org/apache/karaf/shell/console/jline/

Author: gnodet
Date: Fri Dec 10 16:04:48 2010
New Revision: 1044414

URL: http://svn.apache.org/viewvc?rev=1044414&view=rev
Log:
[KARAF-316][FELIX-2723] Fix problem with interrupting commands

Added:
    karaf/trunk/shell/console/src/main/java/org/apache/felix/gogo/runtime/
    karaf/trunk/shell/console/src/main/java/org/apache/felix/gogo/runtime/Closure.java
Modified:
    karaf/trunk/shell/commands/src/main/java/org/apache/karaf/shell/commands/TailAction.java
    karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/jline/Console.java

Modified: karaf/trunk/shell/commands/src/main/java/org/apache/karaf/shell/commands/TailAction.java
URL: http://svn.apache.org/viewvc/karaf/trunk/shell/commands/src/main/java/org/apache/karaf/shell/commands/TailAction.java?rev=1044414&r1=1044413&r2=1044414&view=diff
==============================================================================
--- karaf/trunk/shell/commands/src/main/java/org/apache/karaf/shell/commands/TailAction.java (original)
+++ karaf/trunk/shell/commands/src/main/java/org/apache/karaf/shell/commands/TailAction.java Fri Dec 10 16:04:48 2010
@@ -21,6 +21,7 @@ import java.io.File;
 import java.io.FileReader;
 import java.io.IOException;
 import java.io.InputStreamReader;
+import java.io.InterruptedIOException;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.LinkedList;

Added: karaf/trunk/shell/console/src/main/java/org/apache/felix/gogo/runtime/Closure.java
URL: http://svn.apache.org/viewvc/karaf/trunk/shell/console/src/main/java/org/apache/felix/gogo/runtime/Closure.java?rev=1044414&view=auto
==============================================================================
--- karaf/trunk/shell/console/src/main/java/org/apache/felix/gogo/runtime/Closure.java (added)
+++ karaf/trunk/shell/console/src/main/java/org/apache/felix/gogo/runtime/Closure.java Fri Dec 10 16:04:48 2010
@@ -0,0 +1,669 @@
+/*
+ * 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.AbstractList;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+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).program();
+        }
+        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();
+                }
+                // FELIX-2723
+                //for (Pipe pipe : pipes)
+                //{
+                //    pipe.join();
+                //}
+                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
+        {
+            v = s;
+        }
+        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;
+
+            default:
+                throw new SyntaxError(t.line, t.column, "unexpected token: " + t.type);
+        }
+
+        return v;
+    }
+
+    /*
+     * 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 = eval(t);
+
+            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.method(session, target,
+                        args.remove(0).toString(), args);
+                    args.clear();
+                }
+                else
+                {
+                    args.add(arg);
+                }
+            }
+
+            if (args.size() == 0)
+            {
+                return target;
+            }
+
+            return Reflective.method(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.method(session, cmd, values.remove(0).toString(), values);
+        }
+    }
+
+    private Object assignment(String name, Object value)
+    {
+        session.variables.put(name, value);
+        return value;
+    }
+
+    private Object array(Token array) throws Exception
+    {
+        List<Token> list = new ArrayList<Token>();
+        Map<Token, Token> map = new LinkedHashMap<Token, Token>();
+        (new Parser(array)).array(list, map);
+
+        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();
+        }
+    }
+
+}

Modified: karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/jline/Console.java
URL: http://svn.apache.org/viewvc/karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/jline/Console.java?rev=1044414&r1=1044413&r2=1044414&view=diff
==============================================================================
--- karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/jline/Console.java (original)
+++ karaf/trunk/shell/console/src/main/java/org/apache/karaf/shell/console/jline/Console.java Fri Dec 10 16:04:48 2010
@@ -388,6 +388,11 @@ public class Console implements Runnable
             }
             return nb;
         }
+
+        @Override
+        public int available() throws IOException {
+            return queue.size();
+        }
     }
 
     private class Pipe implements Runnable
@@ -408,14 +413,16 @@ public class Console implements Runnable
                         else if (c == 4)
                         {
                             err.println("^D");
+                            queue.put(c);
                         }
                         else if (c == 3)
                         {
                             err.println("^C");
                             reader.getCursorBuffer().clear();
                             interrupt();
+                        } else {
+                            queue.put(c);
                         }
-                        queue.put(c);
                     }
                     catch (Throwable t) {
                         return;