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.cmdline.ClassDescriptor;
023 import org.crsh.cmdline.CommandCompletion;
024 import org.crsh.cmdline.CommandFactory;
025 import org.crsh.cmdline.Delimiter;
026 import org.crsh.cmdline.IntrospectionException;
027 import org.crsh.cmdline.annotations.Man;
028 import org.crsh.cmdline.annotations.Option;
029 import org.crsh.cmdline.OptionDescriptor;
030 import org.crsh.cmdline.ParameterDescriptor;
031 import org.crsh.cmdline.annotations.Usage;
032 import org.crsh.cmdline.matcher.ClassMatch;
033 import org.crsh.cmdline.matcher.CmdCompletionException;
034 import org.crsh.cmdline.matcher.CmdInvocationException;
035 import org.crsh.cmdline.matcher.CmdSyntaxException;
036 import org.crsh.cmdline.matcher.CommandMatch;
037 import org.crsh.cmdline.matcher.Matcher;
038 import org.crsh.cmdline.matcher.MethodMatch;
039 import org.crsh.cmdline.matcher.OptionMatch;
040 import org.crsh.cmdline.matcher.Resolver;
041 import org.crsh.cmdline.spi.Completer;
042 import org.crsh.cmdline.spi.Completion;
043 import org.crsh.io.ProducerContext;
044 import org.crsh.util.TypeResolver;
045
046 import java.io.IOException;
047 import java.io.PrintWriter;
048 import java.io.StringWriter;
049 import java.lang.reflect.Method;
050 import java.lang.reflect.Type;
051 import java.util.List;
052 import java.util.Map;
053 import java.util.logging.Level;
054 import java.util.logging.Logger;
055
056 public abstract class CRaSHCommand extends GroovyCommand implements ShellCommand {
057
058 /** . */
059 private final Logger log = Logger.getLogger(getClass().getName());
060
061 /** . */
062 private final ClassDescriptor<?> descriptor;
063
064 /** The unmatched text, only valid during an invocation. */
065 protected String unmatched;
066
067 /** . */
068 @Option(names = {"h","help"})
069 @Usage("command usage")
070 @Man("Provides command usage")
071 private boolean help;
072
073 protected CRaSHCommand() throws IntrospectionException {
074 this.descriptor = new CommandFactory(getClass().getClassLoader()).create(getClass());
075 this.help = false;
076 this.unmatched = null;
077 }
078
079 /**
080 * Returns the command descriptor.
081 *
082 * @return the command descriptor
083 */
084 public ClassDescriptor<?> getDescriptor() {
085 return descriptor;
086 }
087
088 protected final String readLine(String msg) {
089 return readLine(msg, true);
090 }
091
092 protected final String readLine(String msg, boolean echo) {
093 if (context instanceof InvocationContext) {
094 return ((InvocationContext)context).readLine(msg, echo);
095 } else {
096 throw new IllegalStateException("Cannot invoke read line without an invocation context");
097 }
098 }
099
100 public final String getUnmatched() {
101 return unmatched;
102 }
103
104 public final CommandCompletion complete(CommandContext context, String line) {
105
106 // WTF
107 Matcher analyzer = descriptor.matcher("main");
108
109 //
110 Completer completer = this instanceof Completer ? (Completer)this : null;
111
112 //
113 this.context = context;
114 try {
115 return analyzer.complete(completer, line);
116 }
117 catch (CmdCompletionException e) {
118 log.log(Level.SEVERE, "Error during completion of line " + line, e);
119 return new CommandCompletion(Delimiter.EMPTY, Completion.create());
120 }
121 finally {
122 this.context = null;
123 }
124 }
125
126 public final String describe(String line, DescriptionFormat mode) {
127
128 // WTF
129 Matcher analyzer = descriptor.matcher("main");
130
131 //
132 CommandMatch match = analyzer.match(line);
133
134 //
135 try {
136 switch (mode) {
137 case DESCRIBE:
138 return match.getDescriptor().getUsage();
139 case MAN:
140 StringWriter sw = new StringWriter();
141 PrintWriter pw = new PrintWriter(sw);
142 match.printMan(pw);
143 return sw.toString();
144 case USAGE:
145 StringWriter sw2 = new StringWriter();
146 PrintWriter pw2 = new PrintWriter(sw2);
147 match.printUsage(pw2);
148 return sw2.toString();
149 }
150 }
151 catch (IOException e) {
152 throw new AssertionError(e);
153 }
154
155 //
156 return null;
157 }
158
159 static ScriptException toScript(Throwable cause) {
160 if (cause instanceof ScriptException) {
161 return (ScriptException)cause;
162 } if (cause instanceof groovy.util.ScriptException) {
163 // Special handling for groovy.util.ScriptException
164 // which may be thrown by scripts because it is imported by default
165 // by groovy imports
166 String msg = cause.getMessage();
167 ScriptException translated;
168 if (msg != null) {
169 translated = new ScriptException(msg);
170 } else {
171 translated = new ScriptException();
172 }
173 translated.setStackTrace(cause.getStackTrace());
174 return translated;
175 } else {
176 return new ScriptException(cause);
177 }
178 }
179
180 public CommandInvoker<?, ?> resolveInvoker(String name, Map<String, ?> options, List<?> args) {
181 if (options.containsKey("h") || options.containsKey("help")) {
182 throw new UnsupportedOperationException("Implement me");
183 } else {
184
185 Matcher matcher = descriptor.matcher("main");
186 CommandMatch<CRaSHCommand, ?, ?> match = matcher.match(name, options, args);
187 return resolveInvoker(match);
188 }
189 }
190
191 public CommandInvoker<?, ?> resolveInvoker(String line) {
192 Matcher analyzer = descriptor.matcher("main");
193 final CommandMatch<CRaSHCommand, ?, ?> match = analyzer.match(line);
194 return resolveInvoker(match);
195 }
196
197 public final void execute(String s) throws ScriptException, IOException {
198 InvocationContext<?> context = peekContext();
199 CommandInvoker invoker = context.resolve(s);
200 invoker.open(context);
201 invoker.flush();
202 invoker.close();
203 }
204
205 public final CommandInvoker<?, ?> resolveInvoker(final CommandMatch<CRaSHCommand, ?, ?> match) {
206 if (match instanceof MethodMatch) {
207
208 //
209 final MethodMatch<CRaSHCommand> methodMatch = (MethodMatch<CRaSHCommand>)match;
210
211 //
212 boolean help = false;
213 for (OptionMatch optionMatch : methodMatch.getOwner().getOptionMatches()) {
214 ParameterDescriptor<?> parameterDesc = optionMatch.getParameter();
215 if (parameterDesc instanceof OptionDescriptor<?>) {
216 OptionDescriptor<?> optionDesc = (OptionDescriptor<?>)parameterDesc;
217 if (optionDesc.getNames().contains("h")) {
218 help = true;
219 }
220 }
221 }
222 final boolean doHelp = help;
223
224 //
225 Class consumedType;
226 Class producedType;
227 Method m = methodMatch.getDescriptor().getMethod();
228 if (PipeCommand.class.isAssignableFrom(m.getReturnType())) {
229 Type ret = m.getGenericReturnType();
230 consumedType = TypeResolver.resolveToClass(ret, PipeCommand.class, 0);
231 producedType = TypeResolver.resolveToClass(ret, PipeCommand.class, 1);
232 } else {
233 consumedType = Void.class;
234 producedType = Object.class;
235 Class<?>[] parameterTypes = m.getParameterTypes();
236 for (int i = 0;i < parameterTypes.length;i++) {
237 Class<?> parameterType = parameterTypes[i];
238 if (InvocationContext.class.isAssignableFrom(parameterType)) {
239 Type contextGenericParameterType = m.getGenericParameterTypes()[i];
240 producedType = TypeResolver.resolveToClass(contextGenericParameterType, InvocationContext.class, 0);
241 break;
242 }
243 }
244 }
245 final Class _consumedType = consumedType;
246 final Class _producedType = producedType;
247
248 if (doHelp) {
249 return new CommandInvoker<Object, Object>() {
250
251 /** . */
252 private CommandContext session;
253
254 /** . */
255 private InvocationContextImpl context;
256
257 public Class<Object> getProducedType() {
258 return _producedType;
259 }
260
261 public void setSession(CommandContext session) {
262 this.session = session;
263 }
264
265 public Class<Object> getConsumedType() {
266 return _consumedType;
267 }
268
269 public void open(ProducerContext<Object> context) {
270 this.context = new InvocationContextImpl(context, session);
271 try {
272 match.printUsage(this.context.getWriter());
273 }
274 catch (IOException e) {
275 throw new AssertionError(e);
276 }
277 }
278
279 public void setPiped(boolean piped) {
280 }
281
282 public void provide(Object element) throws IOException {
283 }
284
285 public void flush() throws IOException {
286 this.context.flush();
287 }
288
289 public void close() {
290 }
291 };
292 } else {
293 if (consumedType == Void.class) {
294
295 return new CommandInvoker<Object, Object>() {
296
297 /** . */
298 private CommandContext session;
299
300 public void setSession(CommandContext session) {
301 this.session = session;
302 }
303
304 public Class<Object> getProducedType() {
305 return _producedType;
306 }
307
308 public Class<Object> getConsumedType() {
309 return _consumedType;
310 }
311
312 public void open(final ProducerContext<Object> context) {
313
314 //
315 pushContext(new InvocationContextImpl<Object>(context, session));
316 CRaSHCommand.this.unmatched = methodMatch.getRest();
317 final Resolver resolver = new Resolver() {
318 public <T> T resolve(Class<T> type) {
319 if (type.equals(InvocationContext.class)) {
320 return type.cast(peekContext());
321 } else {
322 return null;
323 }
324 }
325 };
326
327 //
328 Object o;
329 try {
330 o = methodMatch.invoke(resolver, CRaSHCommand.this);
331 } catch (CmdSyntaxException e) {
332 throw new SyntaxException(e.getMessage());
333 } catch (CmdInvocationException e) {
334 throw toScript(e.getCause());
335 }
336 if (o != null) {
337 peekContext().getWriter().print(o);
338 }
339 }
340 public void setPiped(boolean piped) {
341 }
342 public void provide(Object element) throws IOException {
343 // We just drop the elements
344 }
345 public void flush() throws IOException {
346 peekContext().flush();
347 }
348 public void close() {
349 CRaSHCommand.this.unmatched = null;
350 popContext();
351 }
352 };
353 } else {
354 return new CommandInvoker<Object, Object>() {
355
356 /** . */
357 PipeCommand real;
358
359 /** . */
360 boolean piped;
361
362 /** . */
363 private CommandContext session;
364
365 public Class<Object> getProducedType() {
366 return _producedType;
367 }
368
369 public Class<Object> getConsumedType() {
370 return _consumedType;
371 }
372
373 public void setSession(CommandContext session) {
374 this.session = session;
375 }
376
377 public void setPiped(boolean piped) {
378 this.piped = piped;
379 }
380
381 public void open(final ProducerContext<Object> context) {
382
383 //
384 final InvocationContextImpl<Object> invocationContext = new InvocationContextImpl<Object>(context, session);
385
386 //
387 pushContext(invocationContext);
388 CRaSHCommand.this.unmatched = methodMatch.getRest();
389 final Resolver resolver = new Resolver() {
390 public <T> T resolve(Class<T> type) {
391 if (type.equals(InvocationContext.class)) {
392 return type.cast(invocationContext);
393 } else {
394 return null;
395 }
396 }
397 };
398 try {
399 real = (PipeCommand)methodMatch.invoke(resolver, CRaSHCommand.this);
400 }
401 catch (CmdSyntaxException e) {
402 throw new SyntaxException(e.getMessage());
403 } catch (CmdInvocationException e) {
404 throw toScript(e.getCause());
405 }
406
407 //
408 real.setPiped(piped);
409 real.doOpen(invocationContext);
410 }
411
412 public void provide(Object element) throws IOException {
413 real.provide(element);
414 }
415
416 public void flush() throws IOException {
417 real.flush();
418 }
419
420 public void close() {
421 try {
422 real.close();
423 }
424 finally {
425 popContext();
426 }
427 }
428 };
429 }
430 }
431 } else if (match instanceof ClassMatch) {
432
433 //
434 final ClassMatch<?> classMatch = (ClassMatch)match;
435
436 //
437 boolean help = false;
438 for (OptionMatch optionMatch : classMatch.getOptionMatches()) {
439 ParameterDescriptor<?> parameterDesc = optionMatch.getParameter();
440 if (parameterDesc instanceof OptionDescriptor<?>) {
441 OptionDescriptor<?> optionDesc = (OptionDescriptor<?>)parameterDesc;
442 if (optionDesc.getNames().contains("h")) {
443 help = true;
444 }
445 }
446 }
447 final boolean doHelp = help;
448
449 //
450 return new CommandInvoker<Void, Object>() {
451
452 /** . */
453 private CommandContext session;
454
455 /** . */
456 InvocationContext context;
457
458 public void open(ProducerContext<Object> producerContext) {
459 this.context = new InvocationContextImpl(producerContext, session);
460 try {
461 if (doHelp) {
462 match.printUsage(context.getWriter());
463 } else {
464 classMatch.printUsage(context.getWriter());
465 }
466 }
467 catch (IOException e) {
468 throw new AssertionError(e);
469 }
470 }
471
472 public void setSession(CommandContext session) {
473 this.session = session;
474 }
475
476 public void setPiped(boolean piped) {
477 }
478
479 public void close() {
480 this.context = null;
481 }
482
483 public void provide(Void element) throws IOException {
484 }
485
486 public void flush() throws IOException {
487 context.flush();
488 }
489
490 public Class<Object> getProducedType() {
491 return Object.class;
492 }
493
494 public Class<Void> getConsumedType() {
495 return Void.class;
496 }
497 };
498
499 } else {
500 return null;
501 }
502 }
503 }