001    /*
002     * Copyright (C) 2012 eXo Platform SAS.
003     *
004     * This is free software; you can redistribute it and/or modify it
005     * under the terms of the GNU Lesser General Public License as
006     * published by the Free Software Foundation; either version 2.1 of
007     * the License, or (at your option) any later version.
008     *
009     * This software is distributed in the hope that it will be useful,
010     * but WITHOUT ANY WARRANTY; without even the implied warranty of
011     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012     * Lesser General Public License for more details.
013     *
014     * You should have received a copy of the GNU Lesser General Public
015     * License along with this software; if not, write to the Free
016     * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
017     * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
018     */
019    package org.crsh.lang.groovy.command;
020    
021    import groovy.lang.Binding;
022    import groovy.lang.Closure;
023    import groovy.lang.GroovyRuntimeException;
024    import groovy.lang.MissingMethodException;
025    import groovy.lang.MissingPropertyException;
026    import groovy.lang.Script;
027    import org.codehaus.groovy.runtime.InvokerInvocationException;
028    import org.crsh.cli.impl.completion.CompletionMatch;
029    import org.crsh.cli.impl.Delimiter;
030    import org.crsh.cli.spi.Completion;
031    import org.crsh.command.CommandContext;
032    import org.crsh.command.CommandInvoker;
033    import org.crsh.command.DescriptionFormat;
034    import org.crsh.command.InvocationContext;
035    import org.crsh.command.InvocationContextImpl;
036    import org.crsh.command.NoSuchCommandException;
037    import org.crsh.command.RuntimeContext;
038    import org.crsh.command.ScriptException;
039    import org.crsh.command.ShellCommand;
040    import org.crsh.lang.groovy.closure.PipeLineClosure;
041    import org.crsh.lang.groovy.closure.PipeLineInvoker;
042    import org.crsh.shell.impl.command.CRaSH;
043    import org.crsh.text.RenderPrintWriter;
044    import org.crsh.util.Strings;
045    
046    import java.io.IOException;
047    import java.lang.reflect.UndeclaredThrowableException;
048    import java.util.LinkedList;
049    import java.util.List;
050    import java.util.Map;
051    
052    public abstract class GroovyScriptCommand extends Script implements ShellCommand {
053    
054      /** . */
055      private LinkedList<InvocationContext<?>> stack;
056    
057      /** The current context. */
058      protected InvocationContext context;
059    
060      /** The current output. */
061      protected RenderPrintWriter out;
062    
063      /** . */
064      private String[] args;
065    
066      protected GroovyScriptCommand() {
067        this.stack = null;
068      }
069    
070      public final void pushContext(InvocationContext<?> context) throws NullPointerException {
071        if (context == null) {
072          throw new NullPointerException();
073        }
074    
075        //
076        if (stack == null) {
077          stack = new LinkedList<InvocationContext<?>>();
078        }
079    
080        // Save current context (is null the first time)
081        stack.addLast((InvocationContext)this.context);
082    
083        // Set new context
084        this.context = context;
085        this.out = context.getWriter();
086      }
087    
088      public final InvocationContext<?> popContext() {
089        if (stack == null || stack.isEmpty()) {
090          throw new IllegalStateException("Cannot pop a context anymore from the stack");
091        }
092        InvocationContext context = this.context;
093        this.context = stack.removeLast();
094        this.out = this.context != null ? this.context.getWriter() : null;
095        return context;
096      }
097    
098      public final void execute(String s) throws ScriptException, IOException {
099        InvocationContext<?> context = peekContext();
100        CommandInvoker invoker = context.resolve(s);
101        invoker.invoke(context);
102      }
103    
104      public final InvocationContext<?> peekContext() {
105        return (InvocationContext<?>)context;
106      }
107    
108      @Override
109      public final Object invokeMethod(String name, Object args) {
110    
111        //
112        try {
113          return super.invokeMethod(name, args);
114        }
115        catch (MissingMethodException missing) {
116          if (context instanceof InvocationContext) {
117            CRaSH crash = (CRaSH)context.getSession().get("crash");
118            if (crash != null) {
119              ShellCommand cmd;
120              try {
121                cmd = crash.getCommand(name);
122              }
123              catch (NoSuchCommandException ce) {
124                throw new InvokerInvocationException(ce);
125              }
126              if (cmd != null) {
127                InvocationContext<Object> ic = (InvocationContext<Object>)peekContext();
128                PipeLineClosure closure = new PipeLineClosure(ic, name, cmd);
129                PipeLineInvoker evaluation = closure.bind(args);
130                try {
131                  evaluation.invoke(ic);
132                  return null;
133                }
134                catch (IOException e) {
135                  throw new GroovyRuntimeException(e);
136                }
137                catch (UndeclaredThrowableException e) {
138                  throw new GroovyRuntimeException(e.getCause());
139                }
140              }
141            }
142          }
143    
144          //
145          throw missing;
146        }
147      }
148    
149      @Override
150      public final Object getProperty(String property) {
151        if ("out".equals(property)) {
152          if (context instanceof InvocationContext<?>) {
153            return ((InvocationContext<?>)context).getWriter();
154          } else {
155            return null;
156          }
157        } else if ("context".equals(property)) {
158          return context;
159        } else {
160          if (context instanceof InvocationContext<?>) {
161            CRaSH crash = (CRaSH)context.getSession().get("crash");
162            if (crash != null) {
163              try {
164                ShellCommand cmd = crash.getCommand(property);
165                if (cmd != null) {
166                  InvocationContext<Object> ic = (InvocationContext<Object>)peekContext();
167                  return new PipeLineClosure(ic, property, cmd);
168                }
169              } catch (NoSuchCommandException e) {
170                throw new InvokerInvocationException(e);
171              }
172            }
173          }
174    
175          //
176          try {
177            return super.getProperty(property);
178          }
179          catch (MissingPropertyException e) {
180            return null;
181          }
182        }
183      }
184    
185      public final CompletionMatch complete(RuntimeContext context, String line) {
186        return new CompletionMatch(Delimiter.EMPTY, Completion.create());
187      }
188    
189      public final String describe(String line, DescriptionFormat mode) {
190        return null;
191      }
192    
193      private final CommandInvoker<Object, Object> invoker = new CommandInvoker<Object, Object>() {
194    
195        public final Class<Object> getProducedType() {
196          return Object.class;
197        }
198    
199        public final Class<Object> getConsumedType() {
200          return Object.class;
201        }
202    
203        public void open(CommandContext<? super Object> consumer) {
204          // Set up current binding
205          Binding binding = new Binding(consumer.getSession());
206    
207          // Set the args on the script
208          binding.setProperty("args", args);
209    
210          //
211          setBinding(binding);
212    
213          //
214          pushContext(new InvocationContextImpl<Object>((CommandContext<Object>)consumer));
215    
216          //
217          try {
218            //
219            Object res = run();
220    
221            // Evaluate the closure
222            if (res instanceof Closure) {
223              Closure closure = (Closure)res;
224              res = closure.call(args);
225            }
226    
227            //
228            if (res != null) {
229              RenderPrintWriter writer = peekContext().getWriter();
230              if (writer.isEmpty()) {
231                writer.print(res);
232              }
233            }
234          }
235          catch (Exception t) {
236            throw GroovyCommand.unwrap(t);
237          }
238        }
239    
240        public void provide(Object element) throws IOException {
241          // Should never be called
242        }
243    
244        public void flush() throws IOException {
245          peekContext().flush();
246        }
247    
248        public void close() throws IOException, UndeclaredThrowableException {
249          popContext();
250        }
251      };
252    
253      public final CommandInvoker<?, ?> resolveInvoker(String line) {
254        List<String> chunks = Strings.chunks(line);
255        this.args = chunks.toArray(new String[chunks.size()]);
256        return invoker;
257      }
258    
259      public final CommandInvoker<?, ?> resolveInvoker(String name, Map<String, ?> options, List<?> args) {
260        String[] tmp = new String[args.size()];
261        for (int i = 0;i < tmp.length;i++) {
262          tmp[i] = args.get(i).toString();
263        }
264        this.args = tmp;
265        return invoker;
266      }
267    }