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