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.command;
020    
021    import groovy.lang.Binding;
022    import groovy.lang.Closure;
023    import groovy.lang.MissingMethodException;
024    import groovy.lang.MissingPropertyException;
025    import groovy.lang.Script;
026    import org.codehaus.groovy.runtime.InvokerInvocationException;
027    import org.crsh.cli.impl.completion.CompletionMatch;
028    import org.crsh.cli.impl.Delimiter;
029    import org.crsh.cli.spi.Completion;
030    import org.crsh.shell.impl.command.CRaSH;
031    import org.crsh.text.RenderPrintWriter;
032    import org.crsh.util.Strings;
033    
034    import java.io.IOException;
035    import java.util.LinkedList;
036    import java.util.List;
037    import java.util.Map;
038    
039    public abstract class GroovyScriptCommand extends Script implements ShellCommand, CommandInvoker<Object, Object> {
040    
041      /** . */
042      private LinkedList<InvocationContext<?>> stack;
043    
044      /** The current context. */
045      protected InvocationContext context;
046    
047      /** The current output. */
048      protected RenderPrintWriter out;
049    
050      /** . */
051      private String[] args;
052    
053      /** . */
054      private boolean piped;
055    
056      protected GroovyScriptCommand() {
057        this.stack = null;
058        this.piped = false;
059      }
060    
061      public final void pushContext(InvocationContext<?> context) throws NullPointerException {
062        if (context == null) {
063          throw new NullPointerException();
064        }
065    
066        //
067        if (stack == null) {
068          stack = new LinkedList<InvocationContext<?>>();
069        }
070    
071        // Save current context (is null the first time)
072        stack.addLast((InvocationContext)this.context);
073    
074        // Set new context
075        this.context = context;
076        this.out = context.getWriter();
077      }
078    
079      public final InvocationContext<?> popContext() {
080        if (stack == null || stack.isEmpty()) {
081          throw new IllegalStateException("Cannot pop a context anymore from the stack");
082        }
083        InvocationContext context = this.context;
084        this.context = stack.removeLast();
085        this.out = this.context != null ? this.context.getWriter() : null;
086        return context;
087      }
088    
089      public final void execute(String s) throws ScriptException, IOException {
090        InvocationContext<?> context = peekContext();
091        CommandInvoker invoker = context.resolve(s);
092        invoker.open(context);
093        invoker.flush();
094        invoker.close();
095      }
096    
097      public final InvocationContext<?> peekContext() {
098        return (InvocationContext<?>)context;
099      }
100    
101      public final Class<Object> getProducedType() {
102        return Object.class;
103      }
104    
105      public final Class<Object> getConsumedType() {
106        return Object.class;
107      }
108    
109      @Override
110      public final Object invokeMethod(String name, Object args) {
111    
112        //
113        try {
114          return super.invokeMethod(name, args);
115        }
116        catch (MissingMethodException e) {
117          if (context instanceof InvocationContext) {
118            InvocationContext ic = (InvocationContext)context;
119            CRaSH crash = (CRaSH)context.getSession().get("crash");
120            if (crash != null) {
121              ShellCommand cmd;
122              try {
123                cmd = crash.getCommand(name);
124              }
125              catch (NoSuchCommandException ce) {
126                throw new InvokerInvocationException(ce);
127              }
128              if (cmd != null) {
129                ClassDispatcher dispatcher = new ClassDispatcher(cmd, this);
130                return dispatcher.dispatch("", CommandClosure.unwrapArgs(args));
131              }
132            }
133          }
134    
135          //
136          throw e;
137        }
138      }
139    
140      @Override
141      public final Object getProperty(String property) {
142        if ("out".equals(property)) {
143          if (context instanceof InvocationContext<?>) {
144            return ((InvocationContext<?>)context).getWriter();
145          } else {
146            return null;
147          }
148        } else if ("context".equals(property)) {
149          return context;
150        } else {
151          if (context instanceof InvocationContext<?>) {
152            CRaSH crash = (CRaSH)context.getSession().get("crash");
153            if (crash != null) {
154              try {
155                ShellCommand cmd = crash.getCommand(property);
156                if (cmd != null) {
157                  return new ClassDispatcher(cmd, this);
158                }
159              } catch (NoSuchCommandException e) {
160                throw new InvokerInvocationException(e);
161              }
162            }
163          }
164    
165          //
166          try {
167            return super.getProperty(property);
168          }
169          catch (MissingPropertyException e) {
170            return null;
171          }
172        }
173      }
174    
175      public final CompletionMatch complete(RuntimeContext context, String line) {
176        return new CompletionMatch(Delimiter.EMPTY, Completion.create());
177      }
178    
179      public final String describe(String line, DescriptionFormat mode) {
180        return null;
181      }
182    
183      public final void open(CommandContext<Object> consumer) {
184    
185        // Set up current binding
186        Binding binding = new Binding(consumer.getSession());
187    
188        // Set the args on the script
189        binding.setProperty("args", args);
190    
191        //
192        setBinding(binding);
193    
194        //
195        pushContext(new InvocationContextImpl<Object>(consumer));
196    
197        //
198        try {
199          //
200          Object res = run();
201    
202          // Evaluate the closure
203          if (res instanceof Closure) {
204            Closure closure = (Closure)res;
205            res = closure.call(args);
206          }
207    
208          //
209          if (res != null) {
210            RenderPrintWriter writer = peekContext().getWriter();
211            if (writer.isEmpty()) {
212              writer.print(res);
213            }
214          }
215        }
216        catch (Exception t) {
217          throw CRaSHCommand.toScript(t);
218        }
219      }
220    
221      public final void provide(Object element) throws IOException {
222        // Should never be called
223      }
224    
225      public final void flush() throws IOException {
226        peekContext().flush();
227      }
228    
229      public final void close() {
230        popContext();
231      }
232    
233      public final CommandInvoker<?, ?> resolveInvoker(String line) {
234        List<String> chunks = Strings.chunks(line);
235        this.args = chunks.toArray(new String[chunks.size()]);
236        return this;
237      }
238    
239      public final CommandInvoker<?, ?> resolveInvoker(String name, Map<String, ?> options, List<?> args) {
240        String[] tmp = new String[args.size()];
241        for (int i = 0;i < tmp.length;i++) {
242          tmp[i] = args.get(i).toString();
243        }
244        this.args = tmp;
245        return this;
246      }
247    }