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