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 */
019package org.crsh.lang.impl.groovy.command;
020
021import groovy.lang.Binding;
022import groovy.lang.Closure;
023import org.crsh.cli.descriptor.CommandDescriptor;
024import org.crsh.cli.impl.descriptor.HelpDescriptor;
025import org.crsh.cli.impl.descriptor.IntrospectionException;
026import org.crsh.cli.impl.invocation.InvocationMatch;
027import org.crsh.cli.impl.lang.CommandFactory;
028import org.crsh.cli.impl.lang.Instance;
029import org.crsh.cli.spi.Completer;
030import org.crsh.command.CommandContext;
031import org.crsh.groovy.GroovyCommand;
032import org.crsh.shell.ErrorKind;
033import org.crsh.shell.impl.command.spi.CommandException;
034import org.crsh.lang.impl.groovy.ast.ScriptLastStatementTransformer;
035import org.crsh.shell.impl.command.spi.CommandMatch;
036import org.crsh.shell.impl.command.spi.CommandInvoker;
037import org.crsh.shell.impl.command.InvocationContextImpl;
038import org.crsh.command.RuntimeContext;
039import org.crsh.shell.impl.command.spi.Command;
040import org.crsh.util.Utils;
041
042import java.io.IOException;
043import java.util.List;
044
045/** @author Julien Viet */
046public 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}