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 void setPiped(boolean piped) {
180        this.piped = piped;
181      }
182    
183      public final String describe(String line, DescriptionFormat mode) {
184        return null;
185      }
186    
187      public final void open(CommandContext<Object> consumer) {
188    
189        // Set up current binding
190        Binding binding = new Binding(consumer.getSession());
191    
192        // Set the args on the script
193        binding.setProperty("args", args);
194    
195        //
196        setBinding(binding);
197    
198        //
199        pushContext(new InvocationContextImpl<Object>(consumer));
200    
201        //
202        try {
203          //
204          Object res = run();
205    
206          // Evaluate the closure
207          if (res instanceof Closure) {
208            Closure closure = (Closure)res;
209            res = closure.call(args);
210          }
211    
212          //
213          if (res != null) {
214            RenderPrintWriter writer = peekContext().getWriter();
215            if (writer.isEmpty()) {
216              writer.print(res);
217            }
218          }
219        }
220        catch (Exception t) {
221          throw CRaSHCommand.toScript(t);
222        }
223      }
224    
225      public final void provide(Object element) throws IOException {
226        // Should never be called
227      }
228    
229      public final void flush() throws IOException {
230        peekContext().flush();
231      }
232    
233      public final void close() {
234        popContext();
235      }
236    
237      public final CommandInvoker<?, ?> resolveInvoker(String line) {
238        List<String> chunks = Strings.chunks(line);
239        this.args = chunks.toArray(new String[chunks.size()]);
240        return this;
241      }
242    
243      public final CommandInvoker<?, ?> resolveInvoker(String name, Map<String, ?> options, List<?> args) {
244        String[] tmp = new String[args.size()];
245        for (int i = 0;i < tmp.length;i++) {
246          tmp[i] = args.get(i).toString();
247        }
248        this.args = tmp;
249        return this;
250      }
251    }