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.util.List;
044 import java.util.Map;
045 import java.util.logging.Level;
046 import java.util.logging.Logger;
047
048 public abstract class BaseCommand extends AbstractCommand 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 BaseCommand() 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 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 public CommandInvoker<?, ?> resolveInvoker(String name, Map<String, ?> options, List<?> args) {
151 if (options.containsKey("h") || options.containsKey("help")) {
152 throw new UnsupportedOperationException("Implement me");
153 } else {
154
155 InvocationMatcher matcher = descriptor.invoker("main");
156 InvocationMatch<BaseCommand> match = null;
157 try {
158 match = matcher.match(name, options, args);
159 }
160 catch (org.crsh.cli.SyntaxException e) {
161 throw new SyntaxException(e.getMessage());
162 }
163 return resolveInvoker(match);
164 }
165 }
166
167 public ScriptException toScript(Throwable cause) {
168 if (cause instanceof ScriptException) {
169 return (ScriptException)cause;
170 } else {
171 return new ScriptException(cause);
172 }
173 }
174
175 public CommandInvoker<?, ?> resolveInvoker(String line) {
176 InvocationMatcher analyzer = descriptor.invoker("main");
177 InvocationMatch<BaseCommand> match;
178 try {
179 match = analyzer.match(line);
180 }
181 catch (org.crsh.cli.SyntaxException e) {
182 throw new SyntaxException(e.getMessage());
183 }
184 return resolveInvoker(match);
185 }
186
187 public final void execute(String s) throws ScriptException, IOException {
188 InvocationContext<?> context = peekContext();
189 CommandInvoker invoker = context.resolve(s);
190 invoker.open(context);
191 invoker.flush();
192 invoker.close();
193 }
194
195 public final CommandInvoker<?, ?> resolveInvoker(final InvocationMatch<BaseCommand> match) {
196
197 //
198 final org.crsh.cli.impl.invocation.CommandInvoker invoker = match.getInvoker();
199
200 //
201 Class consumedType;
202 Class producedType;
203 if (PipeCommand.class.isAssignableFrom(invoker.getReturnType())) {
204 Type ret = invoker.getGenericReturnType();
205 consumedType = TypeResolver.resolveToClass(ret, PipeCommand.class, 0);
206 producedType = TypeResolver.resolveToClass(ret, PipeCommand.class, 1);
207 } else {
208 consumedType = Void.class;
209 producedType = Object.class;
210 Class<?>[] parameterTypes = invoker.getParameterTypes();
211 for (int i = 0;i < parameterTypes.length;i++) {
212 Class<?> parameterType = parameterTypes[i];
213 if (InvocationContext.class.isAssignableFrom(parameterType)) {
214 Type contextGenericParameterType = invoker.getGenericParameterTypes()[i];
215 producedType = TypeResolver.resolveToClass(contextGenericParameterType, InvocationContext.class, 0);
216 break;
217 }
218 }
219 }
220 final Class _consumedType = consumedType;
221 final Class _producedType = producedType;
222
223 //
224 return new CommandInvoker<Object, Object>() {
225
226 /** . */
227 PipeCommand real;
228
229 public Class<Object> getProducedType() {
230 return _producedType;
231 }
232
233 public Class<Object> getConsumedType() {
234 return _consumedType;
235 }
236
237 public void open(final CommandContext<Object> consumer) {
238
239 //
240 final InvocationContextImpl<Object> invocationContext = new InvocationContextImpl<Object>(consumer);
241 final Resolver resolver = new Resolver() {
242 public <T> T resolve(Class<T> type) {
243 if (type.equals(InvocationContext.class)) {
244 return type.cast(invocationContext);
245 } else {
246 return null;
247 }
248 }
249 };
250
251 // Push context
252 pushContext(invocationContext);
253
254 // Set the unmatched part
255 BaseCommand.this.unmatched = match.getRest();
256
257 //
258 Object ret;
259 try {
260 ret = invoker.invoke(resolver, BaseCommand.this);
261 }
262 catch (org.crsh.cli.SyntaxException e) {
263 throw new SyntaxException(e.getMessage());
264 } catch (InvocationException e) {
265 throw toScript(e.getCause());
266 }
267
268 // It's a pipe command
269 if (ret instanceof PipeCommand) {
270 real = (PipeCommand)ret;
271 real.doOpen(invocationContext);
272 } else {
273 if (ret != null) {
274 peekContext().getWriter().print(ret);
275 }
276 }
277 }
278 public void provide(Object element) throws IOException {
279 if (real != null) {
280 real.provide(element);
281 } else {
282 // We just drop the elements
283 }
284 }
285 public void flush() throws IOException {
286 if (real != null) {
287 real.flush();
288 } else {
289 peekContext().flush();
290 }
291 }
292 public void close() throws IOException {
293 if (real != null) {
294 try {
295 real.close();
296 }
297 finally {
298 popContext();
299 }
300 } else {
301 InvocationContext<?> context = popContext();
302 context.close();
303 }
304 BaseCommand.this.unmatched = null;
305 }
306 };
307 }
308 }