001 /*
002 * Copyright (C) 2010 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.util.TypeResolver;
036 import org.slf4j.Logger;
037 import org.slf4j.LoggerFactory;
038
039 import java.io.IOException;
040 import java.io.PrintWriter;
041 import java.io.StringWriter;
042 import java.lang.reflect.Method;
043 import java.lang.reflect.Type;
044
045 /**
046 * A real CRaSH command, the most powerful kind of command.
047 *
048 * @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a>
049 * @version $Revision$
050 */
051 public abstract class CRaSHCommand extends GroovyCommand implements ShellCommand {
052
053 /** . */
054 private final Logger log = LoggerFactory.getLogger(getClass());
055
056 /** . */
057 private CommandContext context;
058
059 /** . */
060 private boolean unquoteArguments;
061
062 /** . */
063 private final ClassDescriptor<?> descriptor;
064
065 /** The unmatched text, only valid during an invocation. */
066 private 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.context = null;
076 this.unquoteArguments = true;
077 this.descriptor = CommandFactory.create(getClass());
078 this.help = false;
079 this.unmatched = null;
080 }
081
082 /**
083 * Returns the command descriptor.
084 *
085 * @return the command descriptor
086 */
087 public ClassDescriptor<?> getDescriptor() {
088 return descriptor;
089 }
090
091 /**
092 * Returns true if the command wants its arguments to be unquoted.
093 *
094 * @return true if arguments must be unquoted
095 */
096 public final boolean getUnquoteArguments() {
097 return unquoteArguments;
098 }
099
100 public final void setUnquoteArguments(boolean unquoteArguments) {
101 this.unquoteArguments = unquoteArguments;
102 }
103
104 protected final String readLine(String msg) {
105 return readLine(msg, true);
106 }
107
108 protected final String readLine(String msg, boolean echo) {
109 if (context instanceof InvocationContext) {
110 return ((InvocationContext)context).readLine(msg, echo);
111 } else {
112 throw new IllegalStateException("No current context of interaction with the term");
113 }
114 }
115
116 public final String getUnmatched() {
117 return unmatched;
118 }
119
120 @Override
121 protected final CommandContext getContext() {
122 return context;
123 }
124
125 public final CommandCompletion complete(CommandContext context, String line) {
126
127 // WTF
128 Matcher analyzer = Matcher.createMatcher("main", descriptor);
129
130 //
131 Completer completer = this instanceof Completer ? (Completer)this : null;
132
133 //
134 try {
135 this.context = context;
136
137 //
138 return analyzer.complete(completer, line);
139 }
140 catch (CmdCompletionException e) {
141 log.error("Error during completion of line " + line, e);
142 return new CommandCompletion(Delimiter.EMPTY, ValueCompletion.create());
143 } finally {
144 this.context = null;
145 }
146 }
147
148 public final String describe(String line, DescriptionFormat mode) {
149
150 // WTF
151 Matcher analyzer = Matcher.createMatcher("main", descriptor);
152
153 //
154 CommandMatch match = analyzer.match(line);
155
156 //
157 try {
158 switch (mode) {
159 case DESCRIBE:
160 return match.getDescriptor().getUsage();
161 case MAN:
162 StringWriter sw = new StringWriter();
163 PrintWriter pw = new PrintWriter(sw);
164 match.printMan(pw);
165 return sw.toString();
166 case USAGE:
167 StringWriter sw2 = new StringWriter();
168 PrintWriter pw2 = new PrintWriter(sw2);
169 match.printUsage(pw2);
170 return sw2.toString();
171 }
172 }
173 catch (IOException e) {
174 throw new AssertionError(e);
175 }
176
177 //
178 return null;
179 }
180
181 static ScriptException toScript(Throwable cause) {
182 if (cause instanceof ScriptException) {
183 return (ScriptException)cause;
184 } if (cause instanceof groovy.util.ScriptException) {
185 // Special handling for groovy.util.ScriptException
186 // which may be thrown by scripts because it is imported by default
187 // by groovy imports
188 String msg = cause.getMessage();
189 ScriptException translated;
190 if (msg != null) {
191 translated = new ScriptException(msg);
192 } else {
193 translated = new ScriptException();
194 }
195 translated.setStackTrace(cause.getStackTrace());
196 return translated;
197 } else {
198 return new ScriptException(cause);
199 }
200 }
201
202 public final CommandInvoker<?, ?> createInvoker(final String line) {
203
204 // Remove surrounding quotes if there are
205 if (unquoteArguments) {
206 // todo ?
207 }
208
209 // WTF
210 Matcher analyzer = Matcher.createMatcher("main", descriptor);
211
212 //
213 final CommandMatch<CRaSHCommand, ?, ?> match = analyzer.match(line);
214
215 //
216 if (match instanceof MethodMatch) {
217
218 //
219 final MethodMatch<CRaSHCommand> methodMatch = (MethodMatch<CRaSHCommand>)match;
220
221 //
222 boolean help = false;
223 for (OptionMatch optionMatch : methodMatch.getOwner().getOptionMatches()) {
224 ParameterDescriptor<?> parameterDesc = optionMatch.getParameter();
225 if (parameterDesc instanceof OptionDescriptor<?>) {
226 OptionDescriptor<?> optionDesc = (OptionDescriptor<?>)parameterDesc;
227 if (optionDesc.getNames().contains("h")) {
228 help = true;
229 }
230 }
231 }
232 final boolean doHelp = help;
233
234 //
235 return new CommandInvoker() {
236
237 Class consumedType = Void.class;
238 Class producedType = Void.class;
239
240 {
241 // Try to find a command context argument
242 Method m = methodMatch.getDescriptor().getMethod();
243
244 //
245 Class<?>[] parameterTypes = m.getParameterTypes();
246 for (int i = 0;i < parameterTypes.length;i++) {
247 Class<?> parameterType = parameterTypes[i];
248 if (InvocationContext.class.isAssignableFrom(parameterType)) {
249 Type contextGenericParameterType = m.getGenericParameterTypes()[i];
250 consumedType = TypeResolver.resolveToClass(contextGenericParameterType, InvocationContext.class, 0);
251 producedType = TypeResolver.resolveToClass(contextGenericParameterType, InvocationContext.class, 1);
252 }
253 }
254 }
255
256 public void invoke(InvocationContext context) throws ScriptException {
257
258 if (doHelp) {
259 try {
260 match.printUsage(context.getWriter());
261 }
262 catch (IOException e) {
263 throw new AssertionError(e);
264 }
265 } else {
266 CRaSHCommand.this.context = context;
267 CRaSHCommand.this.unmatched = methodMatch.getRest();
268 try {
269 org.crsh.cmdline.matcher.InvocationContext invocationContext = new org.crsh.cmdline.matcher.InvocationContext();
270 invocationContext.setAttribute(InvocationContext.class, context);
271 Object o = methodMatch.invoke(invocationContext, CRaSHCommand.this);
272 if (o != null) {
273 context.getWriter().print(o);
274 }
275 } catch (CmdSyntaxException e) {
276 throw new SyntaxException(e.getMessage());
277 } catch (CmdInvocationException e) {
278 throw toScript(e.getCause());
279 } finally {
280 CRaSHCommand.this.context = null;
281 CRaSHCommand.this.unmatched = null;
282 }
283 }
284 }
285
286 public Class getProducedType() {
287 return producedType;
288 }
289
290 public Class getConsumedType() {
291 return consumedType;
292 }
293 };
294 } else if (match instanceof ClassMatch) {
295
296 //
297 final ClassMatch<?> classMatch = (ClassMatch)match;
298
299 //
300 boolean help = false;
301 for (OptionMatch optionMatch : classMatch.getOptionMatches()) {
302 ParameterDescriptor<?> parameterDesc = optionMatch.getParameter();
303 if (parameterDesc instanceof OptionDescriptor<?>) {
304 OptionDescriptor<?> optionDesc = (OptionDescriptor<?>)parameterDesc;
305 if (optionDesc.getNames().contains("h")) {
306 help = true;
307 }
308 }
309 }
310 final boolean doHelp = help;
311
312 //
313 return new CommandInvoker<Void, Void>() {
314 public void invoke(InvocationContext<Void, Void> context) throws ScriptException {
315 try {
316 if (doHelp) {
317 match.printUsage(context.getWriter());
318 } else {
319 classMatch.printUsage(context.getWriter());
320 }
321 }
322 catch (IOException e) {
323 throw new AssertionError(e);
324 }
325 }
326
327 public Class<Void> getProducedType() {
328 return Void.class;
329 }
330
331 public Class<Void> getConsumedType() {
332 return Void.class;
333 }
334 };
335
336 } else {
337 return null;
338 }
339 }
340 }