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.impl.groovy.command;
020    
021    import groovy.lang.Binding;
022    import groovy.lang.Closure;
023    import org.crsh.cli.descriptor.CommandDescriptor;
024    import org.crsh.cli.impl.descriptor.HelpDescriptor;
025    import org.crsh.cli.impl.descriptor.IntrospectionException;
026    import org.crsh.cli.impl.invocation.InvocationMatch;
027    import org.crsh.cli.impl.lang.CommandFactory;
028    import org.crsh.cli.impl.lang.Instance;
029    import org.crsh.cli.spi.Completer;
030    import org.crsh.command.CommandContext;
031    import org.crsh.groovy.GroovyCommand;
032    import org.crsh.shell.ErrorKind;
033    import org.crsh.shell.impl.command.spi.CommandException;
034    import org.crsh.lang.impl.groovy.ast.ScriptLastStatementTransformer;
035    import org.crsh.shell.impl.command.spi.CommandMatch;
036    import org.crsh.shell.impl.command.spi.CommandInvoker;
037    import org.crsh.shell.impl.command.InvocationContextImpl;
038    import org.crsh.command.RuntimeContext;
039    import org.crsh.shell.impl.command.spi.Command;
040    import org.crsh.util.Utils;
041    
042    import java.io.IOException;
043    import java.util.List;
044    
045    /** @author Julien Viet */
046    public class GroovyScriptShellCommand<T extends GroovyScriptCommand> extends Command<Instance<T>> {
047    
048      /** . */
049      private final Class<T> clazz;
050    
051      /** . */
052      private final boolean hasExplicitReturn;
053    
054      /** . */
055      private final CommandDescriptor<Instance<T>> descriptor;
056    
057      public GroovyScriptShellCommand(Class<T> clazz) throws IntrospectionException {
058    
059        //
060        CommandFactory factory = new CommandFactory(getClass().getClassLoader());
061    
062        boolean hasExplicitReturn;
063        try {
064          clazz.getDeclaredField(ScriptLastStatementTransformer.FIELD_NAME);
065          hasExplicitReturn = true;
066        }
067        catch (NoSuchFieldException e) {
068          hasExplicitReturn = false;
069        }
070    
071        //
072        this.clazz = clazz;
073        this.descriptor = HelpDescriptor.create(factory.create(clazz));
074        this.hasExplicitReturn = hasExplicitReturn;
075      }
076    
077      @Override
078      public CommandDescriptor<Instance<T>> getDescriptor() {
079        return descriptor;
080      }
081    
082      @Override
083      protected CommandMatch<?, ?> resolve(final InvocationMatch<Instance<T>> match) {
084        return new CommandMatch<Void, Object>() {
085          @Override
086          public CommandInvoker<Void, Object> getInvoker() throws CommandException {
087            List<String> chunks = Utils.chunks(match.getRest());
088            String[] args = chunks.toArray(new String[chunks.size()]);
089            return GroovyScriptShellCommand.this.getInvoker(args);
090          }
091    
092          @Override
093          public Class<Object> getProducedType() {
094            return Object.class;
095          }
096    
097          @Override
098          public Class<Void> getConsumedType() {
099            return Void.class;
100          }
101        };
102      }
103    
104      private T createCommand() throws CommandException {
105        T command;
106        try {
107          command = clazz.newInstance();
108        }
109        catch (Exception e) {
110          String name = clazz.getSimpleName();
111          throw new CommandException(ErrorKind.INTERNAL, "Could not create command " + name + " instance", e);
112        }
113        return command;
114      }
115    
116      @Override
117      protected Completer getCompleter(RuntimeContext context) throws CommandException {
118        return null;
119      }
120    
121      private CommandInvoker<Void, Object> getInvoker(final String[] args) throws CommandException {
122        final T command = createCommand();
123        return new CommandInvoker<Void, Object>() {
124    
125          /** . */
126          private org.crsh.command.InvocationContext<Object> context;
127    
128          public final Class<Object> getProducedType() {
129            return Object.class;
130          }
131    
132          public final Class<Void> getConsumedType() {
133            return Void.class;
134          }
135    
136          public void open(CommandContext<? super Object> consumer) throws IOException, CommandException {
137    
138            // Set the context
139            context = new InvocationContextImpl<Object>((CommandContext<Object>)consumer);
140    
141            // Set up current binding
142            Binding binding = new Binding(consumer.getSession());
143    
144            // Set the args on the script
145            binding.setProperty("args", args);
146    
147            //
148            command.setBinding(binding);
149    
150    
151            //
152            command.pushContext(context);
153    
154            //
155            try {
156              //
157              Object ret = command.run();
158    
159              // Evaluate the closure
160              if (ret instanceof Closure) {
161                Closure closure = (Closure)ret;
162                ret = closure.call(args);
163              }
164    
165              //
166              if (ret != null) {
167                if (hasExplicitReturn) {
168                  context.provide(ret);
169                }
170              }
171            }
172            catch (Exception t) {
173              throw new CommandException(ErrorKind.EVALUATION, GroovyCommand.unwrap(t));
174            }
175          }
176    
177          public void provide(Void element) {
178            // Should never be called
179          }
180    
181          public void flush() throws IOException {
182            context.flush();
183          }
184    
185          public void close() throws IOException {
186            context = null;
187            command.popContext();
188          }
189        };
190      }
191    
192    
193    }