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