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}