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