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