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