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 }