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