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.shell.InteractionContext;
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);
083    
084        //
085        try {
086          pipe.fire();
087          pipe.close();
088          return null;
089        }
090        catch (ScriptException e) {
091          Throwable cause = e.getCause();
092          if (cause != null) {
093            throw new InvokerInvocationException(cause);
094          } else {
095            throw e;
096          }
097        }
098      }
099    
100      private PipeCommandProxy<?, Object> resolvePipe(String name, Object[] args) {
101        final Closure closure;
102        int to = args.length;
103        if (to > 0 && args[to - 1] instanceof Closure) {
104          closure = (Closure)args[--to];
105        } else {
106          closure = null;
107        }
108    
109        //
110        Map<String, Object> invokerOptions = this.options != null ? this.options : Collections.<String, Object>emptyMap();
111        List<Object> invokerArgs = this.args != null ? this.args : Collections.emptyList();
112    
113        //
114        if (to > 0) {
115          Object first = args[0];
116          int from;
117          if (first instanceof Map<?, ?>) {
118            from = 1;
119            Map<?, ?> options = (Map<?, ?>)first;
120            if (options.size() > 0) {
121              invokerOptions = new HashMap<String, Object>(invokerOptions);
122              for (Map.Entry<?, ?> option : options.entrySet()) {
123                String optionName = option.getKey().toString();
124                Object optionValue = option.getValue();
125                invokerOptions.put(optionName, optionValue);
126              }
127            }
128          } else {
129            from = 0;
130          }
131    
132          if (from < to) {
133            invokerArgs = new ArrayList<Object>(invokerArgs);
134            while (from < to) {
135              Object o = args[from++];
136              if (o != null) {
137                invokerArgs.add(o);
138              }
139            }
140          }
141        }
142    
143        //
144        CommandInvoker<Void, Void> invoker = (CommandInvoker<Void, Void>)command.resolveInvoker(name, invokerOptions, invokerArgs);
145    
146        //
147        InvocationContext context;
148        if (owner instanceof CRaSHCommand) {
149          context = ((CRaSHCommand)owner).peekContext();
150        } else if (owner instanceof GroovyScriptCommand) {
151          context = (InvocationContext)((GroovyScriptCommand)owner).peekContext();
152        } else {
153          throw new UnsupportedOperationException("todo");
154        }
155    
156        //
157        Consumer producer;
158        if (closure != null) {
159          CommandInvoker producerPipe;
160          if (closure instanceof MethodDispatcher) {
161            MethodDispatcher commandClosure = (MethodDispatcher)closure;
162            producerPipe = commandClosure.dispatcher.resolvePipe(commandClosure.name, new Object[0]);
163          } else if (closure instanceof ClassDispatcher) {
164            ClassDispatcher dispatcherClosure = (ClassDispatcher)closure;
165            producerPipe = dispatcherClosure.resolvePipe(name, new Object[0]);
166          } else {
167    
168            // That's the type we cast to
169            Class[] pt = closure.getParameterTypes();
170            final Class type;
171            if (pt.length > 0) {
172              type = pt[0];
173            } else {
174              type = Void.class;
175            }
176    
177            //
178            producerPipe = new CommandInvoker<Object, Void>() {
179              public Class<Void> getProducedType() {
180                return Void.class;
181              }
182              public Class<Object> getConsumedType() {
183                return type;
184              }
185              public void setSession(CommandContext session) {
186              }
187              public void setPiped(boolean piped) {
188              }
189              public void open(InteractionContext<Void> consumer) {
190              }
191              public void close() {
192              }
193              public void provide(Object element) throws IOException {
194                if (type.isInstance(element)) {
195                  closure.call(element);
196                }
197              }
198              public void flush() throws IOException {
199              }
200            };
201          }
202          producerPipe.setPiped(true);
203          producer = producerPipe;
204        } else {
205          producer = context;
206        }
207    
208        //
209        InnerInvocationContext inner = new InnerInvocationContext(context, producer);
210        PipeCommandProxy pipe = new PipeCommandProxy(inner, invoker, producer);
211        pipe.setSession(context);
212        return pipe;
213      }
214    }