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