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 */
019package org.crsh.lang.groovy.closure;
020
021import groovy.lang.Closure;
022import groovy.lang.GroovyObjectSupport;
023import groovy.lang.MissingMethodException;
024import groovy.lang.MissingPropertyException;
025import groovy.lang.Tuple;
026import org.codehaus.groovy.runtime.MetaClassHelper;
027import org.crsh.shell.impl.command.spi.CommandCreationException;
028import org.crsh.shell.impl.command.spi.CommandInvoker;
029import org.crsh.command.InvocationContext;
030import org.crsh.shell.impl.command.spi.ShellCommand;
031import org.crsh.util.Utils;
032
033import java.util.ArrayList;
034import java.util.Arrays;
035import java.util.Collections;
036import java.util.HashMap;
037import java.util.LinkedList;
038import java.util.List;
039import java.util.Map;
040
041/** @author Julien Viet */
042public 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 IllegalArgumentException("Cannot append to a pipeline: " + t);
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}