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