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