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.cli.impl.descriptor.CommandDescriptorImpl;
023 import org.crsh.cli.descriptor.CommandDescriptor;
024 import org.crsh.cli.impl.descriptor.HelpDescriptor;
025 import org.crsh.cli.impl.completion.CompletionMatch;
026 import org.crsh.cli.impl.Delimiter;
027 import org.crsh.cli.impl.descriptor.IntrospectionException;
028 import org.crsh.cli.impl.completion.CompletionException;
029 import org.crsh.cli.impl.completion.CompletionMatcher;
030 import org.crsh.cli.impl.lang.CommandFactory;
031 import org.crsh.cli.impl.invocation.InvocationException;
032 import org.crsh.cli.impl.invocation.InvocationMatch;
033 import org.crsh.cli.impl.invocation.InvocationMatcher;
034 import org.crsh.cli.impl.invocation.Resolver;
035 import org.crsh.cli.spi.Completer;
036 import org.crsh.cli.spi.Completion;
037 import org.crsh.util.TypeResolver;
038
039 import java.io.IOException;
040 import java.io.PrintWriter;
041 import java.io.StringWriter;
042 import java.lang.reflect.Type;
043 import java.util.List;
044 import java.util.Map;
045 import java.util.logging.Level;
046 import java.util.logging.Logger;
047
048 public abstract class CRaSHCommand extends GroovyCommand implements ShellCommand {
049
050 /** . */
051 private final Logger log = Logger.getLogger(getClass().getName());
052
053 /** . */
054 private final CommandDescriptorImpl<?> descriptor;
055
056 /** The unmatched text, only valid during an invocation. */
057 protected String unmatched;
058
059 protected CRaSHCommand() throws IntrospectionException {
060 this.descriptor = HelpDescriptor.create(new CommandFactory(getClass().getClassLoader()).create(getClass()));
061 this.unmatched = null;
062 }
063
064 /**
065 * Returns the command descriptor.
066 *
067 * @return the command descriptor
068 */
069 public CommandDescriptor<?> getDescriptor() {
070 return descriptor;
071 }
072
073 protected final String readLine(String msg) {
074 return readLine(msg, true);
075 }
076
077 protected final String readLine(String msg, boolean echo) {
078 if (context instanceof InvocationContext) {
079 return ((InvocationContext)context).readLine(msg, echo);
080 } else {
081 throw new IllegalStateException("Cannot invoke read line without an invocation context");
082 }
083 }
084
085 public final String getUnmatched() {
086 return unmatched;
087 }
088
089 public final CompletionMatch complete(RuntimeContext context, String line) {
090
091 // WTF
092 CompletionMatcher analyzer = descriptor.completer("main");
093
094 //
095 Completer completer = this instanceof Completer ? (Completer)this : null;
096
097 //
098 this.context = context;
099 try {
100 return analyzer.match(completer, line);
101 }
102 catch (CompletionException e) {
103 log.log(Level.SEVERE, "Error during completion of line " + line, e);
104 return new CompletionMatch(Delimiter.EMPTY, Completion.create());
105 }
106 finally {
107 this.context = null;
108 }
109 }
110
111 public final String describe(String line, DescriptionFormat mode) {
112
113 // WTF
114 InvocationMatcher analyzer = descriptor.invoker("main");
115
116 //
117 InvocationMatch match;
118 try {
119 match = analyzer.match(line);
120 }
121 catch (org.crsh.cli.SyntaxException e) {
122 throw new org.crsh.command.SyntaxException(e.getMessage());
123 }
124
125 //
126 try {
127 switch (mode) {
128 case DESCRIBE:
129 return match.getDescriptor().getUsage();
130 case MAN:
131 StringWriter sw = new StringWriter();
132 PrintWriter pw = new PrintWriter(sw);
133 match.getDescriptor().printMan(pw);
134 return sw.toString();
135 case USAGE:
136 StringWriter sw2 = new StringWriter();
137 PrintWriter pw2 = new PrintWriter(sw2);
138 match.getDescriptor().printUsage(pw2);
139 return sw2.toString();
140 }
141 }
142 catch (IOException e) {
143 throw new AssertionError(e);
144 }
145
146 //
147 return null;
148 }
149
150 static ScriptException toScript(Throwable cause) {
151 if (cause instanceof ScriptException) {
152 return (ScriptException)cause;
153 } if (cause instanceof groovy.util.ScriptException) {
154 // Special handling for groovy.util.ScriptException
155 // which may be thrown by scripts because it is imported by default
156 // by groovy imports
157 String msg = cause.getMessage();
158 ScriptException translated;
159 if (msg != null) {
160 translated = new ScriptException(msg);
161 } else {
162 translated = new ScriptException();
163 }
164 translated.setStackTrace(cause.getStackTrace());
165 return translated;
166 } else {
167 return new ScriptException(cause);
168 }
169 }
170
171 public CommandInvoker<?, ?> resolveInvoker(String name, Map<String, ?> options, List<?> args) {
172 if (options.containsKey("h") || options.containsKey("help")) {
173 throw new UnsupportedOperationException("Implement me");
174 } else {
175
176 InvocationMatcher matcher = descriptor.invoker("main");
177 InvocationMatch<CRaSHCommand> match = null;
178 try {
179 match = matcher.match(name, options, args);
180 }
181 catch (org.crsh.cli.SyntaxException e) {
182 throw new org.crsh.command.SyntaxException(e.getMessage());
183 }
184 return resolveInvoker(match);
185 }
186 }
187
188 public CommandInvoker<?, ?> resolveInvoker(String line) {
189 InvocationMatcher analyzer = descriptor.invoker("main");
190 InvocationMatch<CRaSHCommand> match;
191 try {
192 match = analyzer.match(line);
193 }
194 catch (org.crsh.cli.SyntaxException e) {
195 throw new org.crsh.command.SyntaxException(e.getMessage());
196 }
197 return resolveInvoker(match);
198 }
199
200 public final void execute(String s) throws ScriptException, IOException {
201 InvocationContext<?> context = peekContext();
202 CommandInvoker invoker = context.resolve(s);
203 invoker.open(context);
204 invoker.flush();
205 invoker.close();
206 }
207
208 public final CommandInvoker<?, ?> resolveInvoker(final InvocationMatch<CRaSHCommand> match) {
209
210 //
211 final org.crsh.cli.impl.invocation.CommandInvoker invoker = match.getInvoker();
212
213 //
214 Class consumedType;
215 Class producedType;
216 if (PipeCommand.class.isAssignableFrom(invoker.getReturnType())) {
217 Type ret = invoker.getGenericReturnType();
218 consumedType = TypeResolver.resolveToClass(ret, PipeCommand.class, 0);
219 producedType = TypeResolver.resolveToClass(ret, PipeCommand.class, 1);
220 } else {
221 consumedType = Void.class;
222 producedType = Object.class;
223 Class<?>[] parameterTypes = invoker.getParameterTypes();
224 for (int i = 0;i < parameterTypes.length;i++) {
225 Class<?> parameterType = parameterTypes[i];
226 if (InvocationContext.class.isAssignableFrom(parameterType)) {
227 Type contextGenericParameterType = invoker.getGenericParameterTypes()[i];
228 producedType = TypeResolver.resolveToClass(contextGenericParameterType, InvocationContext.class, 0);
229 break;
230 }
231 }
232 }
233 final Class _consumedType = consumedType;
234 final Class _producedType = producedType;
235
236 //
237 if (consumedType == Void.class) {
238
239 return new CommandInvoker<Object, Object>() {
240
241 public Class<Object> getProducedType() {
242 return _producedType;
243 }
244
245 public Class<Object> getConsumedType() {
246 return _consumedType;
247 }
248
249 public void open(final CommandContext<Object> consumer) {
250
251 //
252 pushContext(new InvocationContextImpl<Object>(consumer));
253 CRaSHCommand.this.unmatched = match.getRest();
254 final Resolver resolver = new Resolver() {
255 public <T> T resolve(Class<T> type) {
256 if (type.equals(InvocationContext.class)) {
257 return type.cast(peekContext());
258 } else {
259 return null;
260 }
261 }
262 };
263
264 //
265 Object o;
266 try {
267 o = invoker.invoke(resolver, CRaSHCommand.this);
268 } catch (org.crsh.cli.SyntaxException e) {
269 throw new org.crsh.command.SyntaxException(e.getMessage());
270 } catch (InvocationException e) {
271 throw toScript(e.getCause());
272 }
273 if (o != null) {
274 peekContext().getWriter().print(o);
275 }
276 }
277 public void provide(Object element) throws IOException {
278 // We just drop the elements
279 }
280 public void flush() throws IOException {
281 peekContext().flush();
282 }
283 public void close() {
284 CRaSHCommand.this.unmatched = null;
285 popContext();
286 }
287 };
288 } else {
289 return new CommandInvoker<Object, Object>() {
290
291 /** . */
292 PipeCommand real;
293
294 public Class<Object> getProducedType() {
295 return _producedType;
296 }
297
298 public Class<Object> getConsumedType() {
299 return _consumedType;
300 }
301
302 public void open(final CommandContext<Object> consumer) {
303
304 //
305 final InvocationContextImpl<Object> invocationContext = new InvocationContextImpl<Object>(consumer);
306
307 //
308 pushContext(invocationContext);
309 CRaSHCommand.this.unmatched = match.getRest();
310 final Resolver resolver = new Resolver() {
311 public <T> T resolve(Class<T> type) {
312 if (type.equals(InvocationContext.class)) {
313 return type.cast(invocationContext);
314 } else {
315 return null;
316 }
317 }
318 };
319 try {
320 real = (PipeCommand)invoker.invoke(resolver, CRaSHCommand.this);
321 }
322 catch (org.crsh.cli.SyntaxException e) {
323 throw new org.crsh.command.SyntaxException(e.getMessage());
324 } catch (InvocationException e) {
325 throw toScript(e.getCause());
326 }
327
328 //
329 real.doOpen(invocationContext);
330 }
331
332 public void provide(Object element) throws IOException {
333 real.provide(element);
334 }
335
336 public void flush() throws IOException {
337 real.flush();
338 }
339
340 public void close() {
341 try {
342 real.close();
343 }
344 finally {
345 popContext();
346 }
347 }
348 };
349 }
350 }
351 }