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 return new CommandInvoker<Object, Object>() { 238 239 /** . */ 240 PipeCommand real; 241 242 public Class<Object> getProducedType() { 243 return _producedType; 244 } 245 246 public Class<Object> getConsumedType() { 247 return _consumedType; 248 } 249 250 public void open(final CommandContext<Object> consumer) { 251 252 // 253 final InvocationContextImpl<Object> invocationContext = new InvocationContextImpl<Object>(consumer); 254 final Resolver resolver = new Resolver() { 255 public <T> T resolve(Class<T> type) { 256 if (type.equals(InvocationContext.class)) { 257 return type.cast(invocationContext); 258 } else { 259 return null; 260 } 261 } 262 }; 263 264 // Push context 265 pushContext(invocationContext); 266 267 // Set the unmatched part 268 CRaSHCommand.this.unmatched = match.getRest(); 269 270 // 271 Object ret; 272 try { 273 ret = invoker.invoke(resolver, CRaSHCommand.this); 274 } 275 catch (org.crsh.cli.SyntaxException e) { 276 throw new org.crsh.command.SyntaxException(e.getMessage()); 277 } catch (InvocationException e) { 278 throw toScript(e.getCause()); 279 } 280 281 // It's a pipe command 282 if (ret instanceof PipeCommand) { 283 real = (PipeCommand)ret; 284 real.doOpen(invocationContext); 285 } else { 286 if (ret != null) { 287 peekContext().getWriter().print(ret); 288 } 289 } 290 } 291 public void provide(Object element) throws IOException { 292 if (real != null) { 293 real.provide(element); 294 } else { 295 // We just drop the elements 296 } 297 } 298 public void flush() throws IOException { 299 if (real != null) { 300 real.flush(); 301 } else { 302 peekContext().flush(); 303 } 304 } 305 public void close() throws IOException { 306 if (real != null) { 307 try { 308 real.close(); 309 } 310 finally { 311 popContext(); 312 } 313 } else { 314 InvocationContext<?> context = popContext(); 315 context.close(); 316 } 317 CRaSHCommand.this.unmatched = null; 318 } 319 }; 320 } 321 }