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 import org.slf4j.Logger;
046 import org.slf4j.LoggerFactory;
047
048 import java.io.IOException;
049 import java.io.PrintWriter;
050 import java.io.StringWriter;
051 import java.lang.reflect.Method;
052 import java.lang.reflect.Type;
053 import java.util.List;
054 import java.util.Map;
055
056 public abstract class CRaSHCommand extends GroovyCommand implements ShellCommand {
057
058 /** . */
059 private final Logger log = LoggerFactory.getLogger(getClass());
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 return context.readLine(msg, echo);
094 }
095
096 public final String getUnmatched() {
097 return unmatched;
098 }
099
100 public final CommandCompletion complete(SessionContext context, String line) {
101
102 // WTF
103 Matcher analyzer = descriptor.matcher("main");
104
105 //
106 Completer completer = this instanceof Completer ? (Completer)this : null;
107
108 //
109 try {
110 return analyzer.complete(completer, line);
111 }
112 catch (CmdCompletionException e) {
113 log.error("Error during completion of line " + line, e);
114 return new CommandCompletion(Delimiter.EMPTY, Completion.create());
115 }
116 }
117
118 public final String describe(String line, DescriptionFormat mode) {
119
120 // WTF
121 Matcher analyzer = descriptor.matcher("main");
122
123 //
124 CommandMatch match = analyzer.match(line);
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.printMan(pw);
135 return sw.toString();
136 case USAGE:
137 StringWriter sw2 = new StringWriter();
138 PrintWriter pw2 = new PrintWriter(sw2);
139 match.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 Matcher matcher = descriptor.matcher("main");
178 CommandMatch<CRaSHCommand, ?, ?> match = matcher.match(name, options, args);
179 return resolveInvoker(match);
180 }
181 }
182
183 public CommandInvoker<?, ?> resolveInvoker(String line) {
184 Matcher analyzer = descriptor.matcher("main");
185 final CommandMatch<CRaSHCommand, ?, ?> match = analyzer.match(line);
186 return resolveInvoker(match);
187 }
188
189 public final void eval(String s) throws ScriptException, IOException {
190 InvocationContext<?> context = peekContext();
191 CommandInvoker invoker = context.resolve(s);
192 invoker.open(context);
193 invoker.flush();
194 invoker.close();
195 }
196
197 public final CommandInvoker<?, ?> resolveInvoker(final CommandMatch<CRaSHCommand, ?, ?> match) {
198 if (match instanceof MethodMatch) {
199
200 //
201 final MethodMatch<CRaSHCommand> methodMatch = (MethodMatch<CRaSHCommand>)match;
202
203 //
204 boolean help = false;
205 for (OptionMatch optionMatch : methodMatch.getOwner().getOptionMatches()) {
206 ParameterDescriptor<?> parameterDesc = optionMatch.getParameter();
207 if (parameterDesc instanceof OptionDescriptor<?>) {
208 OptionDescriptor<?> optionDesc = (OptionDescriptor<?>)parameterDesc;
209 if (optionDesc.getNames().contains("h")) {
210 help = true;
211 }
212 }
213 }
214 final boolean doHelp = help;
215
216 //
217 Class consumedType;
218 Class producedType;
219 Method m = methodMatch.getDescriptor().getMethod();
220 if (PipeCommand.class.isAssignableFrom(m.getReturnType())) {
221 Type ret = m.getGenericReturnType();
222 consumedType = TypeResolver.resolveToClass(ret, PipeCommand.class, 0);
223 producedType = TypeResolver.resolveToClass(ret, PipeCommand.class, 1);
224 } else {
225 consumedType = Void.class;
226 producedType = Object.class;
227 Class<?>[] parameterTypes = m.getParameterTypes();
228 for (int i = 0;i < parameterTypes.length;i++) {
229 Class<?> parameterType = parameterTypes[i];
230 if (InvocationContext.class.isAssignableFrom(parameterType)) {
231 Type contextGenericParameterType = m.getGenericParameterTypes()[i];
232 producedType = TypeResolver.resolveToClass(contextGenericParameterType, InvocationContext.class, 0);
233 break;
234 }
235 }
236 }
237 final Class _consumedType = consumedType;
238 final Class _producedType = producedType;
239
240 if (doHelp) {
241 return new CommandInvoker<Object, Object>() {
242
243 /** . */
244 private SessionContext session;
245
246 public Class<Object> getProducedType() {
247 return _producedType;
248 }
249
250 public void setSession(SessionContext session) {
251 this.session = session;
252 }
253
254 public Class<Object> getConsumedType() {
255 return _consumedType;
256 }
257
258 public void open(ProducerContext<Object> context) {
259 try {
260 match.printUsage(new InvocationContextImpl(context, session).getWriter());
261 }
262 catch (IOException e) {
263 throw new AssertionError(e);
264 }
265 }
266
267 public void setPiped(boolean piped) {
268 }
269
270 public void provide(Object element) throws IOException {
271 }
272
273 public void flush() throws IOException {
274 }
275
276 public void close() {
277 }
278 };
279 } else {
280 if (consumedType == Void.class) {
281
282 return new CommandInvoker<Object, Object>() {
283
284 /** . */
285 private SessionContext session;
286
287 public void setSession(SessionContext session) {
288 this.session = session;
289 }
290
291 public Class<Object> getProducedType() {
292 return _producedType;
293 }
294
295 public Class<Object> getConsumedType() {
296 return _consumedType;
297 }
298
299 public void open(final ProducerContext<Object> context) {
300
301 //
302 pushContext(new InvocationContextImpl<Object>(context, session));
303 CRaSHCommand.this.unmatched = methodMatch.getRest();
304 final Resolver resolver = new Resolver() {
305 public <T> T resolve(Class<T> type) {
306 if (type.equals(InvocationContext.class)) {
307 return type.cast(peekContext());
308 } else {
309 return null;
310 }
311 }
312 };
313
314 //
315 Object o;
316 try {
317 o = methodMatch.invoke(resolver, CRaSHCommand.this);
318 } catch (CmdSyntaxException e) {
319 throw new SyntaxException(e.getMessage());
320 } catch (CmdInvocationException e) {
321 throw toScript(e.getCause());
322 }
323 if (o != null) {
324 peekContext().getWriter().print(o);
325 }
326 }
327 public void setPiped(boolean piped) {
328 }
329 public void provide(Object element) throws IOException {
330 // We just drop the elements
331 }
332 public void flush() throws IOException {
333 peekContext().flush();
334 }
335 public void close() {
336 CRaSHCommand.this.unmatched = null;
337 popContext();
338 }
339 };
340 } else {
341 return new CommandInvoker<Object, Object>() {
342
343 /** . */
344 PipeCommand real;
345
346 /** . */
347 boolean piped;
348
349 /** . */
350 private SessionContext session;
351
352 public Class<Object> getProducedType() {
353 return _producedType;
354 }
355
356 public Class<Object> getConsumedType() {
357 return _consumedType;
358 }
359
360 public void setSession(SessionContext session) {
361 this.session = session;
362 }
363
364 public void setPiped(boolean piped) {
365 this.piped = piped;
366 }
367
368 public void open(final ProducerContext<Object> context) {
369
370 //
371 final InvocationContextImpl<Object> invocationContext = new InvocationContextImpl<Object>(context, session);
372
373 //
374 pushContext(invocationContext);
375 CRaSHCommand.this.unmatched = methodMatch.getRest();
376 final Resolver resolver = new Resolver() {
377 public <T> T resolve(Class<T> type) {
378 if (type.equals(InvocationContext.class)) {
379 return type.cast(invocationContext);
380 } else {
381 return null;
382 }
383 }
384 };
385 try {
386 real = (PipeCommand)methodMatch.invoke(resolver, CRaSHCommand.this);
387 }
388 catch (CmdSyntaxException e) {
389 throw new SyntaxException(e.getMessage());
390 } catch (CmdInvocationException e) {
391 throw toScript(e.getCause());
392 }
393
394 //
395 real.setPiped(piped);
396 real.doOpen(invocationContext);
397 }
398
399 public void provide(Object element) throws IOException {
400 real.provide(element);
401 }
402
403 public void flush() throws IOException {
404 real.flush();
405 }
406
407 public void close() {
408 try {
409 real.close();
410 }
411 finally {
412 popContext();
413 }
414 }
415 };
416 }
417 }
418 } else if (match instanceof ClassMatch) {
419
420 //
421 final ClassMatch<?> classMatch = (ClassMatch)match;
422
423 //
424 boolean help = false;
425 for (OptionMatch optionMatch : classMatch.getOptionMatches()) {
426 ParameterDescriptor<?> parameterDesc = optionMatch.getParameter();
427 if (parameterDesc instanceof OptionDescriptor<?>) {
428 OptionDescriptor<?> optionDesc = (OptionDescriptor<?>)parameterDesc;
429 if (optionDesc.getNames().contains("h")) {
430 help = true;
431 }
432 }
433 }
434 final boolean doHelp = help;
435
436 //
437 return new CommandInvoker<Void, Object>() {
438
439 /** . */
440 private SessionContext session;
441
442 /** . */
443 InvocationContext context;
444
445 public void open(ProducerContext<Object> producerContext) {
446 this.context = new InvocationContextImpl(producerContext, session);
447 try {
448 if (doHelp) {
449 match.printUsage(context.getWriter());
450 } else {
451 classMatch.printUsage(context.getWriter());
452 }
453 }
454 catch (IOException e) {
455 throw new AssertionError(e);
456 }
457 }
458
459 public void setSession(SessionContext session) {
460 this.session = session;
461 }
462
463 public void setPiped(boolean piped) {
464 }
465
466 public void close() {
467 this.context = null;
468 }
469
470 public void provide(Void element) throws IOException {
471 }
472
473 public void flush() throws IOException {
474 context.flush();
475 }
476
477 public Class<Object> getProducedType() {
478 return Object.class;
479 }
480
481 public Class<Void> getConsumedType() {
482 return Void.class;
483 }
484 };
485
486 } else {
487 return null;
488 }
489 }
490 }