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