| /* |
| * 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.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 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(); |
| } |
| } |
| |
| } |