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.command.CommandCreationException;
028import org.crsh.command.CommandInvoker;
029import org.crsh.command.InvocationContext;
030import org.crsh.command.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 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.name == null) {
103        return new PipeLineClosure(context, new CommandElement[]{
104            new CommandElement(element.commandName + "." + name, element.command, 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
155    //
156    CommandElement first = (CommandElement)elements[0];
157    Map<String, Object> firstOptions = first.options;
158    List<Object> firstArgs = first.args;
159
160    // We merge options
161    if (options != null && options.size() > 0) {
162      if (firstOptions == null) {
163        firstOptions = new HashMap<String, Object>();
164      } else {
165        firstOptions = new HashMap<String, Object>(options);
166      }
167      for (Map.Entry<?, ?> arg : options.entrySet()) {
168        firstOptions.put(arg.getKey().toString(), arg.getValue());
169      }
170    }
171
172    // We replace arguments
173    if (arguments != null) {
174      firstArgs = new ArrayList<Object>(Arrays.asList(arguments));
175    }
176
177    //
178    PipeLineElement[] ret = elements.clone();
179    ret[0] = new CommandElement(first.commandName, first.command, first.name, firstOptions, firstArgs);
180    return new PipeLineClosure(context, ret);
181  }
182
183  @Override
184  public Object call(Object... args) {
185
186    final Closure closure;
187    int to = args.length;
188    if (to > 0 && args[to - 1] instanceof Closure) {
189      closure = (Closure)args[--to];
190    } else {
191      closure = null;
192    }
193
194    // Configure the command with the closure
195    if (closure != null) {
196      final HashMap<String, Object> closureOptions = new HashMap<String, Object>();
197      GroovyObjectSupport delegate = new GroovyObjectSupport() {
198        @Override
199        public void setProperty(String property, Object newValue) {
200          closureOptions.put(property, newValue);
201        }
202      };
203      closure.setResolveStrategy(Closure.DELEGATE_ONLY);
204      closure.setDelegate(delegate);
205      Object ret = closure.call();
206      Object[] closureArgs;
207      if (ret != null) {
208        if (ret instanceof Object[]) {
209          closureArgs = (Object[])ret;
210        }
211        else if (ret instanceof Iterable) {
212          closureArgs = Utils.list((Iterable)ret).toArray();
213        }
214        else {
215          boolean use = true;
216          for (Object value : closureOptions.values()) {
217            if (value == ret) {
218              use = false;
219              break;
220            }
221          }
222          // Avoid the case : foo { bar = "juu" } that will make "juu" as an argument
223          closureArgs = use ? new Object[]{ret} : EMPTY_ARGS;
224        }
225      } else {
226        closureArgs = EMPTY_ARGS;
227      }
228      return options(closureOptions, closureArgs);
229    } else {
230      PipeLineInvoker binding = bind(args);
231      if (context != null) {
232        try {
233          binding.invoke(context);
234          return null;
235        }
236        catch (Exception e) {
237          return throwRuntimeException(e);
238        }
239      } else {
240        return binding;
241      }
242    }
243  }
244
245  public PipeLineClosure bind(InvocationContext<Object> context) {
246    return new PipeLineClosure(context, elements);
247  }
248
249  public PipeLineInvoker bind(Object args) {
250    return bind(unwrapArgs(args));
251  }
252
253  public PipeLineInvoker bind(Object[] args) {
254    return new PipeLineInvoker(this, args);
255  }
256
257  LinkedList<CommandInvoker> resolve2(Object[] args) throws CommandCreationException {
258
259    // Resolve options and arguments
260    CommandElement elt = (CommandElement)elements[0];
261    Map<String, Object> invokerOptions = elt.options != null ? elt.options : Collections.<String, Object>emptyMap();
262    List<Object> invokerArgs = elt.args != null ? elt.args : Collections.emptyList();
263    if (args.length > 0) {
264      Object first = args[0];
265      int from;
266      if (first instanceof Map<?, ?>) {
267        from = 1;
268        Map<?, ?> options = (Map<?, ?>)first;
269        if (options.size() > 0) {
270          invokerOptions = new HashMap<String, Object>(invokerOptions);
271          for (Map.Entry<?, ?> option : options.entrySet()) {
272            String optionName = option.getKey().toString();
273            Object optionValue = option.getValue();
274            invokerOptions.put(optionName, optionValue);
275          }
276        }
277      } else {
278        from = 0;
279      }
280      if (from < args.length) {
281        invokerArgs = new ArrayList<Object>(invokerArgs);
282        while (from < args.length) {
283          Object o = args[from++];
284          if (o != null) {
285            invokerArgs.add(o);
286          }
287        }
288      }
289    }
290
291    //
292    CommandElement first = (CommandElement)elements[0];
293    PipeLineElement[] a = elements.clone();
294    a[0] = new CommandElement(first.commandName, first.command, first.name, invokerOptions, invokerArgs);
295
296    //
297    LinkedList<CommandInvoker> ret = new LinkedList<CommandInvoker>();
298    for (PipeLineElement _elt : a) {
299      ret.add(_elt.make());
300    }
301
302    //
303    return ret;
304  }
305
306  @Override
307  public String toString() {
308    StringBuilder sb = new StringBuilder();
309    for (int i = 0;i < elements.length;i++) {
310      if (i > 0) {
311        sb.append(" | ");
312      }
313      elements[i].toString(sb);
314    }
315    return sb.toString();
316  }
317}