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.impl.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.Command;
028import org.crsh.shell.impl.command.spi.CommandException;
029import org.crsh.shell.impl.command.spi.CommandInvoker;
030import org.crsh.command.InvocationContext;
031import org.crsh.util.Utils;
032
033import java.io.IOException;
034import java.util.ArrayList;
035import java.util.Arrays;
036import java.util.Collections;
037import java.util.HashMap;
038import java.util.LinkedList;
039import java.util.List;
040import java.util.Map;
041
042/** @author Julien Viet */
043public 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}