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 }