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.CommandFactory;
024 import org.crsh.cmdline.IntrospectionException;
025 import org.crsh.cmdline.annotations.Man;
026 import org.crsh.cmdline.annotations.Option;
027 import org.crsh.cmdline.OptionDescriptor;
028 import org.crsh.cmdline.ParameterDescriptor;
029 import org.crsh.cmdline.annotations.Usage;
030 import org.crsh.cmdline.matcher.*;
031 import org.crsh.cmdline.spi.Completer;
032 import org.crsh.util.TypeResolver;
033 import org.slf4j.Logger;
034 import org.slf4j.LoggerFactory;
035
036 import java.io.IOException;
037 import java.io.PrintWriter;
038 import java.io.StringWriter;
039 import java.lang.reflect.Method;
040 import java.lang.reflect.Type;
041 import java.lang.reflect.UndeclaredThrowableException;
042 import java.util.Collections;
043 import java.util.Map;
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 Map<String, String> 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 Collections.emptyMap();
143 } finally {
144 this.context = null;
145 }
146 }
147
148 public final String describe(String line, DescriptionMode 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 public final CommandInvoker<?, ?> createInvoker(final String line) {
182
183 // Remove surrounding quotes if there are
184 if (unquoteArguments) {
185 // todo ?
186 }
187
188 // WTF
189 Matcher analyzer = Matcher.createMatcher("main", descriptor);
190
191 //
192 final CommandMatch<CRaSHCommand, ?, ?> match = analyzer.match(line);
193
194 //
195 if (match instanceof MethodMatch) {
196
197 //
198 final MethodMatch<CRaSHCommand> methodMatch = (MethodMatch<CRaSHCommand>)match;
199
200 //
201 boolean help = false;
202 for (OptionMatch optionMatch : methodMatch.getOwner().getOptionMatches()) {
203 ParameterDescriptor<?> parameterDesc = optionMatch.getParameter();
204 if (parameterDesc instanceof OptionDescriptor<?>) {
205 OptionDescriptor<?> optionDesc = (OptionDescriptor<?>)parameterDesc;
206 if (optionDesc.getNames().contains("h")) {
207 help = true;
208 }
209 }
210 }
211 final boolean doHelp = help;
212
213 //
214 return new CommandInvoker() {
215
216 Class consumedType = Void.class;
217 Class producedType = Void.class;
218
219 {
220 // Try to find a command context argument
221 Method m = methodMatch.getDescriptor().getMethod();
222
223 //
224 Class<?>[] parameterTypes = m.getParameterTypes();
225 for (int i = 0;i < parameterTypes.length;i++) {
226 Class<?> parameterType = parameterTypes[i];
227 if (InvocationContext.class.isAssignableFrom(parameterType)) {
228 Type contextGenericParameterType = m.getGenericParameterTypes()[i];
229 consumedType = (Class)TypeResolver.resolve(contextGenericParameterType, InvocationContext.class, 0);
230 producedType = (Class)TypeResolver.resolve(contextGenericParameterType, InvocationContext.class, 1);
231 }
232 }
233 }
234
235 public void invoke(InvocationContext context) throws Exception {
236
237 if (doHelp) {
238 try {
239 match.printUsage(context.getWriter());
240 }
241 catch (IOException e) {
242 throw new AssertionError(e);
243 }
244 } else {
245 CRaSHCommand.this.context = context;
246 CRaSHCommand.this.unmatched = methodMatch.getRest();
247 try {
248 org.crsh.cmdline.matcher.InvocationContext invocationContext = new org.crsh.cmdline.matcher.InvocationContext();
249 invocationContext.setAttribute(InvocationContext.class, context);
250 Object o = methodMatch.invoke(invocationContext, CRaSHCommand.this);
251 if (o != null) {
252 context.getWriter().print(o);
253 }
254 } catch (CmdInvocationException e) {
255 Throwable cause = e.getCause();
256 if (cause instanceof Exception) {
257 throw (Exception)cause;
258 } else {
259 throw new UndeclaredThrowableException(cause);
260 }
261 } finally {
262 CRaSHCommand.this.context = null;
263 CRaSHCommand.this.unmatched = null;
264 }
265 }
266
267 //
268 }
269
270 public Class getProducedType() {
271 return producedType;
272 }
273
274 public Class getConsumedType() {
275 return consumedType;
276 }
277 };
278 } else if (match instanceof ClassMatch) {
279
280 //
281 final ClassMatch<?> classMatch = (ClassMatch)match;
282
283 //
284 boolean help = false;
285 for (OptionMatch optionMatch : classMatch.getOptionMatches()) {
286 ParameterDescriptor<?> parameterDesc = optionMatch.getParameter();
287 if (parameterDesc instanceof OptionDescriptor<?>) {
288 OptionDescriptor<?> optionDesc = (OptionDescriptor<?>)parameterDesc;
289 if (optionDesc.getNames().contains("h")) {
290 help = true;
291 }
292 }
293 }
294 final boolean doHelp = help;
295
296 //
297 return new CommandInvoker<Void, Void>() {
298 public void invoke(InvocationContext<Void, Void> context) throws ScriptException {
299 try {
300 if (doHelp) {
301 match.printUsage(context.getWriter());
302 } else {
303 classMatch.printUsage(context.getWriter());
304 }
305 }
306 catch (IOException e) {
307 throw new AssertionError(e);
308 }
309 }
310
311 public Class<Void> getProducedType() {
312 return Void.class;
313 }
314
315 public Class<Void> getConsumedType() {
316 return Void.class;
317 }
318 };
319
320 } else {
321 return null;
322 }
323 }
324 }