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.Pipe;
027    
028    import java.io.IOException;
029    import java.util.ArrayList;
030    import java.util.Arrays;
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        PipeCommand pipe = resolvePipe(methodName, arguments);
083        try {
084          pipe.open();
085          pipe.close();
086          return null;
087        }
088        catch (ScriptException e) {
089          Throwable cause = e.getCause();
090          if (cause != null) {
091            throw new InvokerInvocationException(cause);
092          } else {
093            throw e;
094          }
095        }
096      }
097    
098      private PipeCommand<?> resolvePipe(String name, Object[] args) {
099        final Closure closure;
100        int to = args.length;
101        if (to > 0 && args[to - 1] instanceof Closure) {
102          closure = (Closure)args[--to];
103        } else {
104          closure = null;
105        }
106    
107        //
108        Map<String, Object> invokerOptions = this.options != null ? this.options : Collections.<String, Object>emptyMap();
109        List<Object> invokerArgs = this.args != null ? this.args : Collections.emptyList();
110    
111        //
112        if (to > 0) {
113          Object first = args[0];
114          int from;
115          if (first instanceof Map<?, ?>) {
116            from = 1;
117            Map<?, ?> options = (Map<?, ?>)first;
118            if (options.size() > 0) {
119              invokerOptions = new HashMap<String, Object>(invokerOptions);
120              for (Map.Entry<?, ?> option : options.entrySet()) {
121                String optionName = option.getKey().toString();
122                Object optionValue = option.getValue();
123                invokerOptions.put(optionName, optionValue);
124              }
125            }
126          } else {
127            from = 0;
128          }
129    
130          if (from < to) {
131            invokerArgs = new ArrayList<Object>(invokerArgs);
132            while (from < to) {
133              Object o = args[from++];
134              if (o != null) {
135                invokerArgs.add(o);
136              }
137            }
138          }
139        }
140    
141        //
142        CommandInvoker<Void, Void> invoker = (CommandInvoker<Void, Void>)command.resolveInvoker(name, invokerOptions, invokerArgs);
143    
144        //
145        InvocationContext context;
146        if (owner instanceof CRaSHCommand) {
147          context = ((CRaSHCommand)owner).peekContext();
148        } else if (owner instanceof GroovyScriptCommand) {
149          context = (InvocationContext)((GroovyScriptCommand)owner).peekContext();
150        } else {
151          throw new UnsupportedOperationException("todo");
152        }
153    
154        //
155        Pipe producer;
156        if (closure != null) {
157          PipeCommand producerPipe;
158          if (closure instanceof MethodDispatcher) {
159            MethodDispatcher commandClosure = (MethodDispatcher)closure;
160            producerPipe = commandClosure.dispatcher.resolvePipe(commandClosure.name, new Object[0]);
161          } else if (closure instanceof ClassDispatcher) {
162            ClassDispatcher dispatcherClosure = (ClassDispatcher)closure;
163            producerPipe = dispatcherClosure.resolvePipe(name, new Object[0]);
164          } else {
165            producerPipe = new PipeCommand() {
166              @Override
167              public void provide(Object element) throws ScriptException, IOException {
168                Class[] parameterTypes = closure.getParameterTypes();
169                if (parameterTypes != null && parameterTypes.length > 0 && parameterTypes[0].isInstance(element)) {
170                  closure.call(element);
171                }
172              }
173            };
174          }
175          producerPipe.setPiped(true);
176          producer = producerPipe;
177        } else {
178          producer = context;
179        }
180    
181        //
182        InnerInvocationContext inner = new InnerInvocationContext(context, producer);
183        PipeCommand<Void> abc = invoker.invoke(inner);
184        return new PipeCommandProxy(abc, producer);
185      }
186    }