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