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