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}