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.command;
020
021 import groovy.lang.Binding;
022 import groovy.lang.Closure;
023 import groovy.lang.GroovyRuntimeException;
024 import groovy.lang.MissingMethodException;
025 import groovy.lang.MissingPropertyException;
026 import groovy.lang.Script;
027 import org.codehaus.groovy.runtime.InvokerInvocationException;
028 import org.crsh.cli.impl.completion.CompletionMatch;
029 import org.crsh.cli.impl.Delimiter;
030 import org.crsh.cli.spi.Completion;
031 import org.crsh.command.CommandContext;
032 import org.crsh.command.CommandInvoker;
033 import org.crsh.command.DescriptionFormat;
034 import org.crsh.command.InvocationContext;
035 import org.crsh.command.InvocationContextImpl;
036 import org.crsh.command.NoSuchCommandException;
037 import org.crsh.command.RuntimeContext;
038 import org.crsh.command.ScriptException;
039 import org.crsh.command.ShellCommand;
040 import org.crsh.lang.groovy.closure.PipeLineClosure;
041 import org.crsh.lang.groovy.closure.PipeLineInvoker;
042 import org.crsh.shell.impl.command.CRaSH;
043 import org.crsh.text.RenderPrintWriter;
044 import org.crsh.util.Strings;
045
046 import java.io.IOException;
047 import java.lang.reflect.UndeclaredThrowableException;
048 import java.util.LinkedList;
049 import java.util.List;
050 import java.util.Map;
051
052 public abstract class GroovyScriptCommand extends Script implements ShellCommand {
053
054 /** . */
055 private LinkedList<InvocationContext<?>> stack;
056
057 /** The current context. */
058 protected InvocationContext context;
059
060 /** The current output. */
061 protected RenderPrintWriter out;
062
063 /** . */
064 private String[] args;
065
066 protected GroovyScriptCommand() {
067 this.stack = null;
068 }
069
070 public final void pushContext(InvocationContext<?> context) throws NullPointerException {
071 if (context == null) {
072 throw new NullPointerException();
073 }
074
075 //
076 if (stack == null) {
077 stack = new LinkedList<InvocationContext<?>>();
078 }
079
080 // Save current context (is null the first time)
081 stack.addLast((InvocationContext)this.context);
082
083 // Set new context
084 this.context = context;
085 this.out = context.getWriter();
086 }
087
088 public final InvocationContext<?> popContext() {
089 if (stack == null || stack.isEmpty()) {
090 throw new IllegalStateException("Cannot pop a context anymore from the stack");
091 }
092 InvocationContext context = this.context;
093 this.context = stack.removeLast();
094 this.out = this.context != null ? this.context.getWriter() : null;
095 return context;
096 }
097
098 public final void execute(String s) throws ScriptException, IOException {
099 InvocationContext<?> context = peekContext();
100 CommandInvoker invoker = context.resolve(s);
101 invoker.invoke(context);
102 }
103
104 public final InvocationContext<?> peekContext() {
105 return (InvocationContext<?>)context;
106 }
107
108 @Override
109 public final Object invokeMethod(String name, Object args) {
110
111 //
112 try {
113 return super.invokeMethod(name, args);
114 }
115 catch (MissingMethodException missing) {
116 if (context instanceof InvocationContext) {
117 CRaSH crash = (CRaSH)context.getSession().get("crash");
118 if (crash != null) {
119 ShellCommand cmd;
120 try {
121 cmd = crash.getCommand(name);
122 }
123 catch (NoSuchCommandException ce) {
124 throw new InvokerInvocationException(ce);
125 }
126 if (cmd != null) {
127 InvocationContext<Object> ic = (InvocationContext<Object>)peekContext();
128 PipeLineClosure closure = new PipeLineClosure(ic, name, cmd);
129 PipeLineInvoker evaluation = closure.bind(args);
130 try {
131 evaluation.invoke(ic);
132 return null;
133 }
134 catch (IOException e) {
135 throw new GroovyRuntimeException(e);
136 }
137 catch (UndeclaredThrowableException e) {
138 throw new GroovyRuntimeException(e.getCause());
139 }
140 }
141 }
142 }
143
144 //
145 throw missing;
146 }
147 }
148
149 @Override
150 public final Object getProperty(String property) {
151 if ("out".equals(property)) {
152 if (context instanceof InvocationContext<?>) {
153 return ((InvocationContext<?>)context).getWriter();
154 } else {
155 return null;
156 }
157 } else if ("context".equals(property)) {
158 return context;
159 } else {
160 if (context instanceof InvocationContext<?>) {
161 CRaSH crash = (CRaSH)context.getSession().get("crash");
162 if (crash != null) {
163 try {
164 ShellCommand cmd = crash.getCommand(property);
165 if (cmd != null) {
166 InvocationContext<Object> ic = (InvocationContext<Object>)peekContext();
167 return new PipeLineClosure(ic, property, cmd);
168 }
169 } catch (NoSuchCommandException e) {
170 throw new InvokerInvocationException(e);
171 }
172 }
173 }
174
175 //
176 try {
177 return super.getProperty(property);
178 }
179 catch (MissingPropertyException e) {
180 return null;
181 }
182 }
183 }
184
185 public final CompletionMatch complete(RuntimeContext context, String line) {
186 return new CompletionMatch(Delimiter.EMPTY, Completion.create());
187 }
188
189 public final String describe(String line, DescriptionFormat mode) {
190 return null;
191 }
192
193 private final CommandInvoker<Object, Object> invoker = new CommandInvoker<Object, Object>() {
194
195 public final Class<Object> getProducedType() {
196 return Object.class;
197 }
198
199 public final Class<Object> getConsumedType() {
200 return Object.class;
201 }
202
203 public void open(CommandContext<? super Object> consumer) {
204 // Set up current binding
205 Binding binding = new Binding(consumer.getSession());
206
207 // Set the args on the script
208 binding.setProperty("args", args);
209
210 //
211 setBinding(binding);
212
213 //
214 pushContext(new InvocationContextImpl<Object>((CommandContext<Object>)consumer));
215
216 //
217 try {
218 //
219 Object res = run();
220
221 // Evaluate the closure
222 if (res instanceof Closure) {
223 Closure closure = (Closure)res;
224 res = closure.call(args);
225 }
226
227 //
228 if (res != null) {
229 RenderPrintWriter writer = peekContext().getWriter();
230 if (writer.isEmpty()) {
231 writer.print(res);
232 }
233 }
234 }
235 catch (Exception t) {
236 throw GroovyCommand.unwrap(t);
237 }
238 }
239
240 public void provide(Object element) throws IOException {
241 // Should never be called
242 }
243
244 public void flush() throws IOException {
245 peekContext().flush();
246 }
247
248 public void close() throws IOException, UndeclaredThrowableException {
249 popContext();
250 }
251 };
252
253 public final CommandInvoker<?, ?> resolveInvoker(String line) {
254 List<String> chunks = Strings.chunks(line);
255 this.args = chunks.toArray(new String[chunks.size()]);
256 return invoker;
257 }
258
259 public final CommandInvoker<?, ?> resolveInvoker(String name, Map<String, ?> options, List<?> args) {
260 String[] tmp = new String[args.size()];
261 for (int i = 0;i < tmp.length;i++) {
262 tmp[i] = args.get(i).toString();
263 }
264 this.args = tmp;
265 return invoker;
266 }
267 }