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.shell.InteractionContext; 038 import org.crsh.util.TypeResolver; 039 040 import java.io.IOException; 041 import java.io.PrintWriter; 042 import java.io.StringWriter; 043 import java.lang.reflect.Type; 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 CRaSHCommand extends GroovyCommand 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 CRaSHCommand() 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(CommandContext 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 org.crsh.command.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 static ScriptException toScript(Throwable cause) { 152 if (cause instanceof ScriptException) { 153 return (ScriptException)cause; 154 } if (cause instanceof groovy.util.ScriptException) { 155 // Special handling for groovy.util.ScriptException 156 // which may be thrown by scripts because it is imported by default 157 // by groovy imports 158 String msg = cause.getMessage(); 159 ScriptException translated; 160 if (msg != null) { 161 translated = new ScriptException(msg); 162 } else { 163 translated = new ScriptException(); 164 } 165 translated.setStackTrace(cause.getStackTrace()); 166 return translated; 167 } else { 168 return new ScriptException(cause); 169 } 170 } 171 172 public CommandInvoker<?, ?> resolveInvoker(String name, Map<String, ?> options, List<?> args) { 173 if (options.containsKey("h") || options.containsKey("help")) { 174 throw new UnsupportedOperationException("Implement me"); 175 } else { 176 177 InvocationMatcher matcher = descriptor.invoker("main"); 178 InvocationMatch<CRaSHCommand> match = null; 179 try { 180 match = matcher.match(name, options, args); 181 } 182 catch (org.crsh.cli.SyntaxException e) { 183 throw new org.crsh.command.SyntaxException(e.getMessage()); 184 } 185 return resolveInvoker(match); 186 } 187 } 188 189 public CommandInvoker<?, ?> resolveInvoker(String line) { 190 InvocationMatcher analyzer = descriptor.invoker("main"); 191 InvocationMatch<CRaSHCommand> match; 192 try { 193 match = analyzer.match(line); 194 } 195 catch (org.crsh.cli.SyntaxException e) { 196 throw new org.crsh.command.SyntaxException(e.getMessage()); 197 } 198 return resolveInvoker(match); 199 } 200 201 public final void execute(String s) throws ScriptException, IOException { 202 InvocationContext<?> context = peekContext(); 203 CommandInvoker invoker = context.resolve(s); 204 invoker.open(context); 205 invoker.flush(); 206 invoker.close(); 207 } 208 209 public final CommandInvoker<?, ?> resolveInvoker(final InvocationMatch<CRaSHCommand> match) { 210 211 // 212 final org.crsh.cli.impl.invocation.CommandInvoker invoker = match.getInvoker(); 213 214 // 215 Class consumedType; 216 Class producedType; 217 if (PipeCommand.class.isAssignableFrom(invoker.getReturnType())) { 218 Type ret = invoker.getGenericReturnType(); 219 consumedType = TypeResolver.resolveToClass(ret, PipeCommand.class, 0); 220 producedType = TypeResolver.resolveToClass(ret, PipeCommand.class, 1); 221 } else { 222 consumedType = Void.class; 223 producedType = Object.class; 224 Class<?>[] parameterTypes = invoker.getParameterTypes(); 225 for (int i = 0;i < parameterTypes.length;i++) { 226 Class<?> parameterType = parameterTypes[i]; 227 if (InvocationContext.class.isAssignableFrom(parameterType)) { 228 Type contextGenericParameterType = invoker.getGenericParameterTypes()[i]; 229 producedType = TypeResolver.resolveToClass(contextGenericParameterType, InvocationContext.class, 0); 230 break; 231 } 232 } 233 } 234 final Class _consumedType = consumedType; 235 final Class _producedType = producedType; 236 237 // 238 if (consumedType == Void.class) { 239 240 return new CommandInvoker<Object, Object>() { 241 242 /** . */ 243 private CommandContext session; 244 245 public void setSession(CommandContext session) { 246 this.session = session; 247 } 248 249 public Class<Object> getProducedType() { 250 return _producedType; 251 } 252 253 public Class<Object> getConsumedType() { 254 return _consumedType; 255 } 256 257 public void open(final InteractionContext<Object> consumer) { 258 259 // 260 pushContext(new InvocationContextImpl<Object>(consumer, session)); 261 CRaSHCommand.this.unmatched = match.getRest(); 262 final Resolver resolver = new Resolver() { 263 public <T> T resolve(Class<T> type) { 264 if (type.equals(InvocationContext.class)) { 265 return type.cast(peekContext()); 266 } else { 267 return null; 268 } 269 } 270 }; 271 272 // 273 Object o; 274 try { 275 o = invoker.invoke(resolver, CRaSHCommand.this); 276 } catch (org.crsh.cli.SyntaxException e) { 277 throw new org.crsh.command.SyntaxException(e.getMessage()); 278 } catch (InvocationException e) { 279 throw toScript(e.getCause()); 280 } 281 if (o != null) { 282 peekContext().getWriter().print(o); 283 } 284 } 285 public void setPiped(boolean piped) { 286 } 287 public void provide(Object element) throws IOException { 288 // We just drop the elements 289 } 290 public void flush() throws IOException { 291 peekContext().flush(); 292 } 293 public void close() { 294 CRaSHCommand.this.unmatched = null; 295 popContext(); 296 } 297 }; 298 } else { 299 return new CommandInvoker<Object, Object>() { 300 301 /** . */ 302 PipeCommand real; 303 304 /** . */ 305 boolean piped; 306 307 /** . */ 308 private CommandContext session; 309 310 public Class<Object> getProducedType() { 311 return _producedType; 312 } 313 314 public Class<Object> getConsumedType() { 315 return _consumedType; 316 } 317 318 public void setSession(CommandContext session) { 319 this.session = session; 320 } 321 322 public void setPiped(boolean piped) { 323 this.piped = piped; 324 } 325 326 public void open(final InteractionContext<Object> consumer) { 327 328 // 329 final InvocationContextImpl<Object> invocationContext = new InvocationContextImpl<Object>(consumer, session); 330 331 // 332 pushContext(invocationContext); 333 CRaSHCommand.this.unmatched = match.getRest(); 334 final Resolver resolver = new Resolver() { 335 public <T> T resolve(Class<T> type) { 336 if (type.equals(InvocationContext.class)) { 337 return type.cast(invocationContext); 338 } else { 339 return null; 340 } 341 } 342 }; 343 try { 344 real = (PipeCommand)invoker.invoke(resolver, CRaSHCommand.this); 345 } 346 catch (org.crsh.cli.SyntaxException e) { 347 throw new org.crsh.command.SyntaxException(e.getMessage()); 348 } catch (InvocationException e) { 349 throw toScript(e.getCause()); 350 } 351 352 // 353 real.setPiped(piped); 354 real.doOpen(invocationContext); 355 } 356 357 public void provide(Object element) throws IOException { 358 real.provide(element); 359 } 360 361 public void flush() throws IOException { 362 real.flush(); 363 } 364 365 public void close() { 366 try { 367 real.close(); 368 } 369 finally { 370 popContext(); 371 } 372 } 373 }; 374 } 375 } 376 }