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