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    
020    package org.crsh.command;
021    
022    import groovy.lang.Closure;
023    import groovy.lang.MissingMethodException;
024    import groovy.lang.MissingPropertyException;
025    import org.codehaus.groovy.runtime.InvokerInvocationException;
026    import org.crsh.io.Consumer;
027    import org.crsh.util.Safe;
028    
029    import java.io.IOException;
030    import java.util.ArrayList;
031    import java.util.Collections;
032    import java.util.HashMap;
033    import java.util.List;
034    import java.util.Map;
035    
036    final class ClassDispatcher extends CommandClosure {
037    
038      /** . */
039      final Object owner;
040    
041      /** . */
042      final ShellCommand command;
043    
044      ClassDispatcher(ShellCommand command, Object owner) {
045        super(new Object());
046    
047        //
048        this.command = command;
049        this.owner = owner;
050      }
051    
052      @Override
053      public Object getProperty(String property) {
054        try {
055          return super.getProperty(property);
056        }
057        catch (MissingPropertyException e) {
058          return new MethodDispatcher(this, property);
059        }
060      }
061    
062      @Override
063      public Object invokeMethod(String name, Object args) {
064        try {
065          return super.invokeMethod(name, args);
066        }
067        catch (MissingMethodException e) {
068          return dispatch(name, unwrapArgs(args));
069        }
070      }
071    
072      /**
073       * Closure invocation.
074       *
075       * @param arguments the closure arguments
076       */
077      public Object call(Object[] arguments) {
078        return dispatch("", arguments);
079      }
080    
081      Object dispatch(String methodName, Object[] arguments) {
082        PipeCommandProxy pipe = resolvePipe(methodName, arguments, false);
083    
084        //
085        try {
086          pipe.fire();
087          return null;
088        }
089        catch (ScriptException e) {
090          Throwable cause = e.getCause();
091          if (cause != null) {
092            throw new InvokerInvocationException(cause);
093          } else {
094            throw e;
095          }
096        }
097        finally {
098          Safe.close(pipe);
099        }
100      }
101    
102      private PipeCommandProxy<?, Object> resolvePipe(String name, Object[] args, boolean piped) {
103        final Closure closure;
104        int to = args.length;
105        if (to > 0 && args[to - 1] instanceof Closure) {
106          closure = (Closure)args[--to];
107        } else {
108          closure = null;
109        }
110    
111        //
112        Map<String, Object> invokerOptions = this.options != null ? this.options : Collections.<String, Object>emptyMap();
113        List<Object> invokerArgs = this.args != null ? this.args : Collections.emptyList();
114    
115        //
116        if (to > 0) {
117          Object first = args[0];
118          int from;
119          if (first instanceof Map<?, ?>) {
120            from = 1;
121            Map<?, ?> options = (Map<?, ?>)first;
122            if (options.size() > 0) {
123              invokerOptions = new HashMap<String, Object>(invokerOptions);
124              for (Map.Entry<?, ?> option : options.entrySet()) {
125                String optionName = option.getKey().toString();
126                Object optionValue = option.getValue();
127                invokerOptions.put(optionName, optionValue);
128              }
129            }
130          } else {
131            from = 0;
132          }
133    
134          if (from < to) {
135            invokerArgs = new ArrayList<Object>(invokerArgs);
136            while (from < to) {
137              Object o = args[from++];
138              if (o != null) {
139                invokerArgs.add(o);
140              }
141            }
142          }
143        }
144    
145        //
146        CommandInvoker<Void, Void> invoker = (CommandInvoker<Void, Void>)command.resolveInvoker(name, invokerOptions, invokerArgs);
147    
148        //
149        InvocationContext context;
150        if (owner instanceof CRaSHCommand) {
151          context = ((CRaSHCommand)owner).peekContext();
152        } else if (owner instanceof GroovyScriptCommand) {
153          context = (InvocationContext)((GroovyScriptCommand)owner).peekContext();
154        } else {
155          throw new UnsupportedOperationException("todo");
156        }
157    
158        //
159        Consumer producer;
160        if (closure != null) {
161          CommandInvoker producerPipe;
162          if (closure instanceof MethodDispatcher) {
163            MethodDispatcher commandClosure = (MethodDispatcher)closure;
164            producerPipe = commandClosure.dispatcher.resolvePipe(commandClosure.name, new Object[0], true);
165          } else if (closure instanceof ClassDispatcher) {
166            ClassDispatcher dispatcherClosure = (ClassDispatcher)closure;
167            producerPipe = dispatcherClosure.resolvePipe(name, new Object[0], true);
168          } else {
169    
170            // That's the type we cast to
171            Class[] pt = closure.getParameterTypes();
172            final Class type;
173            if (pt.length > 0) {
174              type = pt[0];
175            } else {
176              type = Void.class;
177            }
178    
179            //
180            producerPipe = new CommandInvoker<Object, Void>() {
181              public Class<Void> getProducedType() {
182                return Void.class;
183              }
184              public Class<Object> getConsumedType() {
185                return type;
186              }
187              public void open(CommandContext<Void> consumer) {
188              }
189              public void close() {
190              }
191              public void provide(Object element) throws IOException {
192                if (type.isInstance(element)) {
193                  closure.call(element);
194                }
195              }
196              public void flush() throws IOException {
197              }
198            };
199          }
200          producer = producerPipe;
201        } else {
202          producer = context;
203        }
204    
205        //
206        InnerInvocationContext inner = new InnerInvocationContext(context, producer, piped);
207        return new PipeCommandProxy(inner, invoker, producer);
208      }
209    }