001 /* 002 * Copyright (C) 2010 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.cmdline.ClassDescriptor; 023 import org.crsh.cmdline.CommandCompletion; 024 import org.crsh.cmdline.CommandFactory; 025 import org.crsh.cmdline.Delimiter; 026 import org.crsh.cmdline.IntrospectionException; 027 import org.crsh.cmdline.annotations.Man; 028 import org.crsh.cmdline.annotations.Option; 029 import org.crsh.cmdline.OptionDescriptor; 030 import org.crsh.cmdline.ParameterDescriptor; 031 import org.crsh.cmdline.annotations.Usage; 032 import org.crsh.cmdline.matcher.*; 033 import org.crsh.cmdline.spi.Completer; 034 import org.crsh.cmdline.spi.ValueCompletion; 035 import org.crsh.util.TypeResolver; 036 import org.slf4j.Logger; 037 import org.slf4j.LoggerFactory; 038 039 import java.io.IOException; 040 import java.io.PrintWriter; 041 import java.io.StringWriter; 042 import java.lang.reflect.Method; 043 import java.lang.reflect.Type; 044 045 /** 046 * A real CRaSH command, the most powerful kind of command. 047 * 048 * @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a> 049 * @version $Revision$ 050 */ 051 public abstract class CRaSHCommand extends GroovyCommand implements ShellCommand { 052 053 /** . */ 054 private final Logger log = LoggerFactory.getLogger(getClass()); 055 056 /** . */ 057 private CommandContext context; 058 059 /** . */ 060 private boolean unquoteArguments; 061 062 /** . */ 063 private final ClassDescriptor<?> descriptor; 064 065 /** The unmatched text, only valid during an invocation. */ 066 private String unmatched; 067 068 /** . */ 069 @Option(names = {"h","help"}) 070 @Usage("command usage") 071 @Man("Provides command usage") 072 private boolean help; 073 074 protected CRaSHCommand() throws IntrospectionException { 075 this.context = null; 076 this.unquoteArguments = true; 077 this.descriptor = CommandFactory.create(getClass()); 078 this.help = false; 079 this.unmatched = null; 080 } 081 082 /** 083 * Returns the command descriptor. 084 * 085 * @return the command descriptor 086 */ 087 public ClassDescriptor<?> getDescriptor() { 088 return descriptor; 089 } 090 091 /** 092 * Returns true if the command wants its arguments to be unquoted. 093 * 094 * @return true if arguments must be unquoted 095 */ 096 public final boolean getUnquoteArguments() { 097 return unquoteArguments; 098 } 099 100 public final void setUnquoteArguments(boolean unquoteArguments) { 101 this.unquoteArguments = unquoteArguments; 102 } 103 104 protected final String readLine(String msg) { 105 return readLine(msg, true); 106 } 107 108 protected final String readLine(String msg, boolean echo) { 109 if (context instanceof InvocationContext) { 110 return ((InvocationContext)context).readLine(msg, echo); 111 } else { 112 throw new IllegalStateException("No current context of interaction with the term"); 113 } 114 } 115 116 public final String getUnmatched() { 117 return unmatched; 118 } 119 120 @Override 121 protected final CommandContext getContext() { 122 return context; 123 } 124 125 public final CommandCompletion complete(CommandContext context, String line) { 126 127 // WTF 128 Matcher analyzer = Matcher.createMatcher("main", descriptor); 129 130 // 131 Completer completer = this instanceof Completer ? (Completer)this : null; 132 133 // 134 try { 135 this.context = context; 136 137 // 138 return analyzer.complete(completer, line); 139 } 140 catch (CmdCompletionException e) { 141 log.error("Error during completion of line " + line, e); 142 return new CommandCompletion(Delimiter.EMPTY, ValueCompletion.create()); 143 } finally { 144 this.context = null; 145 } 146 } 147 148 public final String describe(String line, DescriptionFormat mode) { 149 150 // WTF 151 Matcher analyzer = Matcher.createMatcher("main", descriptor); 152 153 // 154 CommandMatch match = analyzer.match(line); 155 156 // 157 try { 158 switch (mode) { 159 case DESCRIBE: 160 return match.getDescriptor().getUsage(); 161 case MAN: 162 StringWriter sw = new StringWriter(); 163 PrintWriter pw = new PrintWriter(sw); 164 match.printMan(pw); 165 return sw.toString(); 166 case USAGE: 167 StringWriter sw2 = new StringWriter(); 168 PrintWriter pw2 = new PrintWriter(sw2); 169 match.printUsage(pw2); 170 return sw2.toString(); 171 } 172 } 173 catch (IOException e) { 174 throw new AssertionError(e); 175 } 176 177 // 178 return null; 179 } 180 181 static ScriptException toScript(Throwable cause) { 182 if (cause instanceof ScriptException) { 183 return (ScriptException)cause; 184 } if (cause instanceof groovy.util.ScriptException) { 185 // Special handling for groovy.util.ScriptException 186 // which may be thrown by scripts because it is imported by default 187 // by groovy imports 188 String msg = cause.getMessage(); 189 ScriptException translated; 190 if (msg != null) { 191 translated = new ScriptException(msg); 192 } else { 193 translated = new ScriptException(); 194 } 195 translated.setStackTrace(cause.getStackTrace()); 196 return translated; 197 } else { 198 return new ScriptException(cause); 199 } 200 } 201 202 public final CommandInvoker<?, ?> createInvoker(final String line) { 203 204 // Remove surrounding quotes if there are 205 if (unquoteArguments) { 206 // todo ? 207 } 208 209 // WTF 210 Matcher analyzer = Matcher.createMatcher("main", descriptor); 211 212 // 213 final CommandMatch<CRaSHCommand, ?, ?> match = analyzer.match(line); 214 215 // 216 if (match instanceof MethodMatch) { 217 218 // 219 final MethodMatch<CRaSHCommand> methodMatch = (MethodMatch<CRaSHCommand>)match; 220 221 // 222 boolean help = false; 223 for (OptionMatch optionMatch : methodMatch.getOwner().getOptionMatches()) { 224 ParameterDescriptor<?> parameterDesc = optionMatch.getParameter(); 225 if (parameterDesc instanceof OptionDescriptor<?>) { 226 OptionDescriptor<?> optionDesc = (OptionDescriptor<?>)parameterDesc; 227 if (optionDesc.getNames().contains("h")) { 228 help = true; 229 } 230 } 231 } 232 final boolean doHelp = help; 233 234 // 235 return new CommandInvoker() { 236 237 Class consumedType = Void.class; 238 Class producedType = Void.class; 239 240 { 241 // Try to find a command context argument 242 Method m = methodMatch.getDescriptor().getMethod(); 243 244 // 245 Class<?>[] parameterTypes = m.getParameterTypes(); 246 for (int i = 0;i < parameterTypes.length;i++) { 247 Class<?> parameterType = parameterTypes[i]; 248 if (InvocationContext.class.isAssignableFrom(parameterType)) { 249 Type contextGenericParameterType = m.getGenericParameterTypes()[i]; 250 consumedType = TypeResolver.resolveToClass(contextGenericParameterType, InvocationContext.class, 0); 251 producedType = TypeResolver.resolveToClass(contextGenericParameterType, InvocationContext.class, 1); 252 } 253 } 254 } 255 256 public void invoke(InvocationContext context) throws ScriptException { 257 258 if (doHelp) { 259 try { 260 match.printUsage(context.getWriter()); 261 } 262 catch (IOException e) { 263 throw new AssertionError(e); 264 } 265 } else { 266 CRaSHCommand.this.context = context; 267 CRaSHCommand.this.unmatched = methodMatch.getRest(); 268 try { 269 org.crsh.cmdline.matcher.InvocationContext invocationContext = new org.crsh.cmdline.matcher.InvocationContext(); 270 invocationContext.setAttribute(InvocationContext.class, context); 271 Object o = methodMatch.invoke(invocationContext, CRaSHCommand.this); 272 if (o != null) { 273 context.getWriter().print(o); 274 } 275 } catch (CmdSyntaxException e) { 276 throw new SyntaxException(e.getMessage()); 277 } catch (CmdInvocationException e) { 278 throw toScript(e.getCause()); 279 } finally { 280 CRaSHCommand.this.context = null; 281 CRaSHCommand.this.unmatched = null; 282 } 283 } 284 } 285 286 public Class getProducedType() { 287 return producedType; 288 } 289 290 public Class getConsumedType() { 291 return consumedType; 292 } 293 }; 294 } else if (match instanceof ClassMatch) { 295 296 // 297 final ClassMatch<?> classMatch = (ClassMatch)match; 298 299 // 300 boolean help = false; 301 for (OptionMatch optionMatch : classMatch.getOptionMatches()) { 302 ParameterDescriptor<?> parameterDesc = optionMatch.getParameter(); 303 if (parameterDesc instanceof OptionDescriptor<?>) { 304 OptionDescriptor<?> optionDesc = (OptionDescriptor<?>)parameterDesc; 305 if (optionDesc.getNames().contains("h")) { 306 help = true; 307 } 308 } 309 } 310 final boolean doHelp = help; 311 312 // 313 return new CommandInvoker<Void, Void>() { 314 public void invoke(InvocationContext<Void, Void> context) throws ScriptException { 315 try { 316 if (doHelp) { 317 match.printUsage(context.getWriter()); 318 } else { 319 classMatch.printUsage(context.getWriter()); 320 } 321 } 322 catch (IOException e) { 323 throw new AssertionError(e); 324 } 325 } 326 327 public Class<Void> getProducedType() { 328 return Void.class; 329 } 330 331 public Class<Void> getConsumedType() { 332 return Void.class; 333 } 334 }; 335 336 } else { 337 return null; 338 } 339 } 340 }