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
020 package org.crsh.command;
021
022 import org.crsh.cli.impl.descriptor.CommandDescriptorImpl;
023 import org.crsh.cli.descriptor.CommandDescriptor;
024 import org.crsh.cli.impl.descriptor.HelpDescriptor;
025 import org.crsh.cli.impl.completion.CompletionMatch;
026 import org.crsh.cli.impl.Delimiter;
027 import org.crsh.cli.impl.descriptor.IntrospectionException;
028 import org.crsh.cli.impl.completion.CompletionException;
029 import org.crsh.cli.impl.completion.CompletionMatcher;
030 import org.crsh.cli.impl.lang.CommandFactory;
031 import org.crsh.cli.impl.invocation.InvocationException;
032 import org.crsh.cli.impl.invocation.InvocationMatch;
033 import org.crsh.cli.impl.invocation.InvocationMatcher;
034 import org.crsh.cli.impl.invocation.Resolver;
035 import org.crsh.cli.spi.Completer;
036 import org.crsh.cli.spi.Completion;
037 import org.crsh.shell.InteractionContext;
038 import org.crsh.util.TypeResolver;
039
040 import java.io.IOException;
041 import java.io.PrintWriter;
042 import java.io.StringWriter;
043 import java.lang.reflect.Type;
044 import java.util.List;
045 import java.util.Map;
046 import java.util.logging.Level;
047 import java.util.logging.Logger;
048
049 public abstract class CRaSHCommand extends GroovyCommand implements ShellCommand {
050
051 /** . */
052 private final Logger log = Logger.getLogger(getClass().getName());
053
054 /** . */
055 private final CommandDescriptorImpl<?> descriptor;
056
057 /** The unmatched text, only valid during an invocation. */
058 protected String unmatched;
059
060 protected CRaSHCommand() throws IntrospectionException {
061 this.descriptor = HelpDescriptor.create(new CommandFactory(getClass().getClassLoader()).create(getClass()));
062 this.unmatched = null;
063 }
064
065 /**
066 * Returns the command descriptor.
067 *
068 * @return the command descriptor
069 */
070 public CommandDescriptor<?> getDescriptor() {
071 return descriptor;
072 }
073
074 protected final String readLine(String msg) {
075 return readLine(msg, true);
076 }
077
078 protected final String readLine(String msg, boolean echo) {
079 if (context instanceof InvocationContext) {
080 return ((InvocationContext)context).readLine(msg, echo);
081 } else {
082 throw new IllegalStateException("Cannot invoke read line without an invocation context");
083 }
084 }
085
086 public final String getUnmatched() {
087 return unmatched;
088 }
089
090 public final CompletionMatch complete(CommandContext context, String line) {
091
092 // WTF
093 CompletionMatcher analyzer = descriptor.completer("main");
094
095 //
096 Completer completer = this instanceof Completer ? (Completer)this : null;
097
098 //
099 this.context = context;
100 try {
101 return analyzer.match(completer, line);
102 }
103 catch (CompletionException e) {
104 log.log(Level.SEVERE, "Error during completion of line " + line, e);
105 return new CompletionMatch(Delimiter.EMPTY, Completion.create());
106 }
107 finally {
108 this.context = null;
109 }
110 }
111
112 public final String describe(String line, DescriptionFormat mode) {
113
114 // WTF
115 InvocationMatcher analyzer = descriptor.invoker("main");
116
117 //
118 InvocationMatch match;
119 try {
120 match = analyzer.match(line);
121 }
122 catch (org.crsh.cli.SyntaxException e) {
123 throw new org.crsh.command.SyntaxException(e.getMessage());
124 }
125
126 //
127 try {
128 switch (mode) {
129 case DESCRIBE:
130 return match.getDescriptor().getUsage();
131 case MAN:
132 StringWriter sw = new StringWriter();
133 PrintWriter pw = new PrintWriter(sw);
134 match.getDescriptor().printMan(pw);
135 return sw.toString();
136 case USAGE:
137 StringWriter sw2 = new StringWriter();
138 PrintWriter pw2 = new PrintWriter(sw2);
139 match.getDescriptor().printUsage(pw2);
140 return sw2.toString();
141 }
142 }
143 catch (IOException e) {
144 throw new AssertionError(e);
145 }
146
147 //
148 return null;
149 }
150
151 static ScriptException toScript(Throwable cause) {
152 if (cause instanceof ScriptException) {
153 return (ScriptException)cause;
154 } if (cause instanceof groovy.util.ScriptException) {
155 // Special handling for groovy.util.ScriptException
156 // which may be thrown by scripts because it is imported by default
157 // by groovy imports
158 String msg = cause.getMessage();
159 ScriptException translated;
160 if (msg != null) {
161 translated = new ScriptException(msg);
162 } else {
163 translated = new ScriptException();
164 }
165 translated.setStackTrace(cause.getStackTrace());
166 return translated;
167 } else {
168 return new ScriptException(cause);
169 }
170 }
171
172 public CommandInvoker<?, ?> resolveInvoker(String name, Map<String, ?> options, List<?> args) {
173 if (options.containsKey("h") || options.containsKey("help")) {
174 throw new UnsupportedOperationException("Implement me");
175 } else {
176
177 InvocationMatcher matcher = descriptor.invoker("main");
178 InvocationMatch<CRaSHCommand> match = null;
179 try {
180 match = matcher.match(name, options, args);
181 }
182 catch (org.crsh.cli.SyntaxException e) {
183 throw new org.crsh.command.SyntaxException(e.getMessage());
184 }
185 return resolveInvoker(match);
186 }
187 }
188
189 public CommandInvoker<?, ?> resolveInvoker(String line) {
190 InvocationMatcher analyzer = descriptor.invoker("main");
191 InvocationMatch<CRaSHCommand> match;
192 try {
193 match = analyzer.match(line);
194 }
195 catch (org.crsh.cli.SyntaxException e) {
196 throw new org.crsh.command.SyntaxException(e.getMessage());
197 }
198 return resolveInvoker(match);
199 }
200
201 public final void execute(String s) throws ScriptException, IOException {
202 InvocationContext<?> context = peekContext();
203 CommandInvoker invoker = context.resolve(s);
204 invoker.open(context);
205 invoker.flush();
206 invoker.close();
207 }
208
209 public final CommandInvoker<?, ?> resolveInvoker(final InvocationMatch<CRaSHCommand> match) {
210
211 //
212 final org.crsh.cli.impl.invocation.CommandInvoker invoker = match.getInvoker();
213
214 //
215 Class consumedType;
216 Class producedType;
217 if (PipeCommand.class.isAssignableFrom(invoker.getReturnType())) {
218 Type ret = invoker.getGenericReturnType();
219 consumedType = TypeResolver.resolveToClass(ret, PipeCommand.class, 0);
220 producedType = TypeResolver.resolveToClass(ret, PipeCommand.class, 1);
221 } else {
222 consumedType = Void.class;
223 producedType = Object.class;
224 Class<?>[] parameterTypes = invoker.getParameterTypes();
225 for (int i = 0;i < parameterTypes.length;i++) {
226 Class<?> parameterType = parameterTypes[i];
227 if (InvocationContext.class.isAssignableFrom(parameterType)) {
228 Type contextGenericParameterType = invoker.getGenericParameterTypes()[i];
229 producedType = TypeResolver.resolveToClass(contextGenericParameterType, InvocationContext.class, 0);
230 break;
231 }
232 }
233 }
234 final Class _consumedType = consumedType;
235 final Class _producedType = producedType;
236
237 //
238 if (consumedType == Void.class) {
239
240 return new CommandInvoker<Object, Object>() {
241
242 /** . */
243 private CommandContext session;
244
245 public void setSession(CommandContext session) {
246 this.session = session;
247 }
248
249 public Class<Object> getProducedType() {
250 return _producedType;
251 }
252
253 public Class<Object> getConsumedType() {
254 return _consumedType;
255 }
256
257 public void open(final InteractionContext<Object> consumer) {
258
259 //
260 pushContext(new InvocationContextImpl<Object>(consumer, session));
261 CRaSHCommand.this.unmatched = match.getRest();
262 final Resolver resolver = new Resolver() {
263 public <T> T resolve(Class<T> type) {
264 if (type.equals(InvocationContext.class)) {
265 return type.cast(peekContext());
266 } else {
267 return null;
268 }
269 }
270 };
271
272 //
273 Object o;
274 try {
275 o = invoker.invoke(resolver, CRaSHCommand.this);
276 } catch (org.crsh.cli.SyntaxException e) {
277 throw new org.crsh.command.SyntaxException(e.getMessage());
278 } catch (InvocationException e) {
279 throw toScript(e.getCause());
280 }
281 if (o != null) {
282 peekContext().getWriter().print(o);
283 }
284 }
285 public void setPiped(boolean piped) {
286 }
287 public void provide(Object element) throws IOException {
288 // We just drop the elements
289 }
290 public void flush() throws IOException {
291 peekContext().flush();
292 }
293 public void close() {
294 CRaSHCommand.this.unmatched = null;
295 popContext();
296 }
297 };
298 } else {
299 return new CommandInvoker<Object, Object>() {
300
301 /** . */
302 PipeCommand real;
303
304 /** . */
305 boolean piped;
306
307 /** . */
308 private CommandContext session;
309
310 public Class<Object> getProducedType() {
311 return _producedType;
312 }
313
314 public Class<Object> getConsumedType() {
315 return _consumedType;
316 }
317
318 public void setSession(CommandContext session) {
319 this.session = session;
320 }
321
322 public void setPiped(boolean piped) {
323 this.piped = piped;
324 }
325
326 public void open(final InteractionContext<Object> consumer) {
327
328 //
329 final InvocationContextImpl<Object> invocationContext = new InvocationContextImpl<Object>(consumer, session);
330
331 //
332 pushContext(invocationContext);
333 CRaSHCommand.this.unmatched = match.getRest();
334 final Resolver resolver = new Resolver() {
335 public <T> T resolve(Class<T> type) {
336 if (type.equals(InvocationContext.class)) {
337 return type.cast(invocationContext);
338 } else {
339 return null;
340 }
341 }
342 };
343 try {
344 real = (PipeCommand)invoker.invoke(resolver, CRaSHCommand.this);
345 }
346 catch (org.crsh.cli.SyntaxException e) {
347 throw new org.crsh.command.SyntaxException(e.getMessage());
348 } catch (InvocationException e) {
349 throw toScript(e.getCause());
350 }
351
352 //
353 real.setPiped(piped);
354 real.doOpen(invocationContext);
355 }
356
357 public void provide(Object element) throws IOException {
358 real.provide(element);
359 }
360
361 public void flush() throws IOException {
362 real.flush();
363 }
364
365 public void close() {
366 try {
367 real.close();
368 }
369 finally {
370 popContext();
371 }
372 }
373 };
374 }
375 }
376 }