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