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    package org.crsh.lang.impl.groovy.closure;
020    
021    import groovy.lang.Closure;
022    import groovy.lang.GroovyObjectSupport;
023    import groovy.lang.MissingMethodException;
024    import groovy.lang.MissingPropertyException;
025    import groovy.lang.Tuple;
026    import org.codehaus.groovy.runtime.MetaClassHelper;
027    import org.crsh.shell.impl.command.spi.Command;
028    import org.crsh.shell.impl.command.spi.CommandException;
029    import org.crsh.shell.impl.command.spi.CommandInvoker;
030    import org.crsh.command.InvocationContext;
031    import org.crsh.util.Utils;
032    
033    import java.io.IOException;
034    import java.util.ArrayList;
035    import java.util.Arrays;
036    import java.util.Collections;
037    import java.util.HashMap;
038    import java.util.LinkedList;
039    import java.util.List;
040    import java.util.Map;
041    
042    /** @author Julien Viet */
043    public class PipeLineClosure extends Closure {
044    
045      /** . */
046      private static final Object[] EMPTY_ARGS = new Object[0];
047    
048      /** . */
049      private final InvocationContext<Object> context;
050    
051      /** . */
052      private PipeLineElement[] elements;
053    
054      public PipeLineClosure(InvocationContext<Object> context, String name, Command<?> command) {
055        this(context, new CommandElement[]{new CommandElement(name, command, null)});
056      }
057    
058      public PipeLineClosure(InvocationContext<Object> context, PipeLineElement[] elements) {
059        super(new Object());
060    
061        //
062        this.context = context;
063        this.elements = elements;
064      }
065    
066      public Object find() {
067        return _gdk("find", EMPTY_ARGS);
068      }
069    
070      public Object find(Closure closure) {
071        return _gdk("find", new Object[]{closure});
072      }
073    
074      private Object _gdk(String name, Object[] args) {
075        PipeLineClosure find = _sub(name);
076        if (find != null) {
077          return find.call(args);
078        } else {
079          throw new MissingMethodException(name, PipeLineClosure.class, args);
080        }
081      }
082    
083      public Object or(Object t) {
084        if (t instanceof PipeLineClosure) {
085          PipeLineClosure next = (PipeLineClosure)t;
086          PipeLineElement[] combined = Arrays.copyOf(elements, elements.length + next.elements.length);
087          System.arraycopy(next.elements, 0, combined, elements.length, next.elements.length);
088          return new PipeLineClosure(context, combined);
089        } else if (t instanceof Closure) {
090          Closure closure = (Closure)t;
091          PipeLineElement[] combined = new PipeLineElement[elements.length + 1];
092          System.arraycopy(elements, 0, combined, 0, elements.length);
093          combined[elements.length] = new ClosureElement(closure);
094          return new PipeLineClosure(context, combined);
095        } else {
096          throw new IllegalArgumentException("Cannot append to a pipeline: " + t);
097        }
098      }
099    
100      private PipeLineClosure _sub(String name) {
101        if (elements.length == 1) {
102          CommandElement element = (CommandElement)elements[0];
103          if (element.subordinate == null) {
104            return new PipeLineClosure(context, new CommandElement[]{
105                element.subordinate(name)
106            });
107          }
108        }
109        return null;
110      }
111    
112      public Object getProperty(String property) {
113        try {
114          return super.getProperty(property);
115        }
116        catch (MissingPropertyException e) {
117          PipeLineClosure sub = _sub(property);
118          if (sub != null) {
119            return sub;
120          } else {
121            throw e;
122          }
123        }
124      }
125    
126      @Override
127      public Object invokeMethod(String name, Object args) {
128        try {
129          return super.invokeMethod(name, args);
130        }
131        catch (MissingMethodException e) {
132          PipeLineClosure sub = _sub(name);
133          if (sub != null) {
134            return sub.call((Object[])args);
135          } else {
136            throw e;
137          }
138        }
139      }
140    
141      private static Object[] unwrapArgs(Object arguments) {
142        if (arguments == null) {
143          return MetaClassHelper.EMPTY_ARRAY;
144        } else if (arguments instanceof Tuple) {
145          Tuple tuple = (Tuple) arguments;
146          return tuple.toArray();
147        } else if (arguments instanceof Object[]) {
148          return (Object[])arguments;
149        } else {
150          return new Object[]{arguments};
151        }
152      }
153    
154      private PipeLineClosure options(Map<String, ?> options, Object[] arguments) {
155        CommandElement first = (CommandElement)elements[0];
156        PipeLineElement[] ret = elements.clone();
157        ret[0] = first.merge(options, arguments != null && arguments.length > 0 ? Arrays.asList(arguments) : Collections.emptyList());
158        return new PipeLineClosure(context, ret);
159      }
160    
161      @Override
162      public Object call(Object... args) {
163    
164        final Closure closure;
165        int to = args.length;
166        if (to > 0 && args[to - 1] instanceof Closure) {
167          closure = (Closure)args[--to];
168        } else {
169          closure = null;
170        }
171    
172        // Configure the command with the closure
173        if (closure != null) {
174          final HashMap<String, Object> closureOptions = new HashMap<String, Object>();
175          GroovyObjectSupport delegate = new GroovyObjectSupport() {
176            @Override
177            public void setProperty(String property, Object newValue) {
178              closureOptions.put(property, newValue);
179            }
180          };
181          closure.setResolveStrategy(Closure.DELEGATE_ONLY);
182          closure.setDelegate(delegate);
183          Object ret = closure.call();
184          Object[] closureArgs;
185          if (ret != null) {
186            if (ret instanceof Object[]) {
187              closureArgs = (Object[])ret;
188            }
189            else if (ret instanceof Iterable) {
190              closureArgs = Utils.list((Iterable)ret).toArray();
191            }
192            else {
193              boolean use = true;
194              for (Object value : closureOptions.values()) {
195                if (value == ret) {
196                  use = false;
197                  break;
198                }
199              }
200              // Avoid the case : foo { bar = "juu" } that will make "juu" as an argument
201              closureArgs = use ? new Object[]{ret} : EMPTY_ARGS;
202            }
203          } else {
204            closureArgs = EMPTY_ARGS;
205          }
206          return options(closureOptions, closureArgs);
207        } else {
208          if (context != null) {
209            try {
210              PipeLineInvoker binding = bind(args);
211              binding.invoke(context);
212              return null;
213            }
214            catch (IOException e) {
215              return throwRuntimeException(e);
216            }
217            catch (CommandException e) {
218              return throwRuntimeException(e.getCause());
219            }
220          } else {
221            return super.call(args);
222          }
223        }
224      }
225    
226      public PipeLineClosure bind(InvocationContext<Object> context) {
227        return new PipeLineClosure(context, elements);
228      }
229    
230      public PipeLineInvoker bind(Object args) {
231        return bind(unwrapArgs(args));
232      }
233    
234      public PipeLineInvoker bind(Object[] args) {
235        return new PipeLineInvoker(this, args);
236      }
237    
238      LinkedList<CommandInvoker> resolve2(Object[] args) throws CommandException {
239    
240        // Resolve options and arguments
241        Map<String, Object> invokerOptions = Collections.emptyMap();
242        List<Object> invokerArgs = Collections.emptyList();
243        if (args.length > 0) {
244          Object first = args[0];
245          int from;
246          if (first instanceof Map<?, ?>) {
247            from = 1;
248            Map<?, ?> options = (Map<?, ?>)first;
249            if (options.size() > 0) {
250              invokerOptions = new HashMap<String, Object>(invokerOptions);
251              for (Map.Entry<?, ?> option : options.entrySet()) {
252                String optionName = option.getKey().toString();
253                Object optionValue = option.getValue();
254                invokerOptions.put(optionName, optionValue);
255              }
256            }
257          } else {
258            from = 0;
259          }
260          if (from < args.length) {
261            invokerArgs = new ArrayList<Object>(invokerArgs);
262            while (from < args.length) {
263              Object o = args[from++];
264              if (o != null) {
265                invokerArgs.add(o);
266              }
267            }
268          }
269        }
270    
271        //
272        CommandElement first = (CommandElement)elements[0];
273        PipeLineElement[] a = elements.clone();
274        a[0] = first.merge(invokerOptions, invokerArgs);
275    
276        //
277        LinkedList<CommandInvoker> ret = new LinkedList<CommandInvoker>();
278        for (PipeLineElement _elt : a) {
279          ret.add(_elt.create());
280        }
281    
282        //
283        return ret;
284      }
285    
286      @Override
287      public String toString() {
288        StringBuilder sb = new StringBuilder();
289        for (int i = 0;i < elements.length;i++) {
290          if (i > 0) {
291            sb.append(" | ");
292          }
293          elements[i].toString(sb);
294        }
295        return sb.toString();
296      }
297    }