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