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