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