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