/cmdline/src/main/java/org/crsh/cmdline/matcher/impl/MatcherImpl.java

http://crsh.googlecode.com/ · Java · 476 lines · 374 code · 53 blank · 49 comment · 111 complexity · 5ff442ad03861ad68e6962aaec0fd34d MD5 · raw file

  1. /*
  2. * Copyright (C) 2010 eXo Platform SAS.
  3. *
  4. * This is free software; you can redistribute it and/or modify it
  5. * under the terms of the GNU Lesser General Public License as
  6. * published by the Free Software Foundation; either version 2.1 of
  7. * the License, or (at your option) any later version.
  8. *
  9. * This software is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12. * Lesser General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU Lesser General Public
  15. * License along with this software; if not, write to the Free
  16. * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
  17. * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
  18. */
  19. package org.crsh.cmdline.matcher.impl;
  20. import org.crsh.cmdline.ArgumentDescriptor;
  21. import org.crsh.cmdline.ClassDescriptor;
  22. import org.crsh.cmdline.CommandDescriptor;
  23. import org.crsh.cmdline.EmptyCompleter;
  24. import org.crsh.cmdline.MethodDescriptor;
  25. import org.crsh.cmdline.OptionDescriptor;
  26. import org.crsh.cmdline.ParameterDescriptor;
  27. import org.crsh.cmdline.binding.ClassFieldBinding;
  28. import org.crsh.cmdline.binding.MethodArgumentBinding;
  29. import org.crsh.cmdline.matcher.ArgumentMatch;
  30. import org.crsh.cmdline.matcher.ClassMatch;
  31. import org.crsh.cmdline.matcher.CmdCompletionException;
  32. import org.crsh.cmdline.matcher.CommandMatch;
  33. import org.crsh.cmdline.matcher.LiteralValue;
  34. import org.crsh.cmdline.matcher.Matcher;
  35. import org.crsh.cmdline.matcher.MethodMatch;
  36. import org.crsh.cmdline.matcher.OptionMatch;
  37. import org.crsh.cmdline.spi.Completer;
  38. import java.util.ArrayList;
  39. import java.util.Collections;
  40. import java.util.HashMap;
  41. import java.util.List;
  42. import java.util.ListIterator;
  43. import java.util.Map;
  44. import java.util.Set;
  45. /**
  46. * @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a>
  47. * @version $Revision$
  48. */
  49. public class MatcherImpl<T> extends Matcher<T> {
  50. /** . */
  51. private final ClassDescriptor<T> descriptor;
  52. /** . */
  53. private final String mainName;
  54. public MatcherImpl(ClassDescriptor<T> descriptor) {
  55. this(null, descriptor);
  56. }
  57. public MatcherImpl(String mainName, ClassDescriptor<T> descriptor) {
  58. this.mainName = mainName;
  59. this.descriptor = descriptor;
  60. }
  61. private List<LiteralValue> bilto(List<? extends Token.Literal> literals) {
  62. List<LiteralValue> values = new ArrayList<LiteralValue>(literals.size());
  63. for (Token.Literal literal : literals) {
  64. values.add(new LiteralValue(literal.raw, literal.value));
  65. }
  66. return values;
  67. }
  68. @Override
  69. public CommandMatch<T, ?, ?> match(String s) {
  70. Tokenizer tokenizer = new Tokenizer(s);
  71. Parser<T> parser = new Parser<T>(tokenizer, descriptor, mainName, Parser.Mode.INVOKE);
  72. //
  73. List<OptionMatch<ClassFieldBinding>> classOptions = new ArrayList<OptionMatch<ClassFieldBinding>>();
  74. List<ArgumentMatch<ClassFieldBinding>> classArguments = new ArrayList<ArgumentMatch<ClassFieldBinding>>();
  75. List<OptionMatch<MethodArgumentBinding>> methodOptions = new ArrayList<OptionMatch<MethodArgumentBinding>>();
  76. List<ArgumentMatch<MethodArgumentBinding>> methodArguments = new ArrayList<ArgumentMatch<MethodArgumentBinding>>();
  77. MethodDescriptor<T> method = null;
  78. //
  79. Integer methodEnd = null;
  80. Integer classEnd;
  81. Event previous = null;
  82. while (true) {
  83. Event event = parser.next();
  84. if (event instanceof Event.Separator) {
  85. //
  86. } else if (event instanceof Event.Stop) {
  87. // We are done
  88. // Check error status and react to it maybe
  89. Event.Stop end = (Event.Stop)event;
  90. int endIndex;
  91. if (previous instanceof Event.Separator) {
  92. endIndex = ((Event.Separator)previous).getToken().getFrom();
  93. } else {
  94. endIndex = end.getIndex();
  95. }
  96. // We try to match the main if none was found
  97. if (method == null) {
  98. classEnd = endIndex;
  99. if (mainName != null) {
  100. method = descriptor.getMethod(mainName);
  101. }
  102. if (method != null) {
  103. methodEnd = classEnd;
  104. }
  105. } else {
  106. methodEnd = classEnd = endIndex;
  107. }
  108. break;
  109. } else if (event instanceof Event.Option) {
  110. Event.Option optionEvent = (Event.Option)event;
  111. OptionDescriptor<?> desc = optionEvent.getDescriptor();
  112. List options;
  113. if (desc.getOwner() instanceof ClassDescriptor<?>) {
  114. options = classOptions;
  115. } else {
  116. options = methodOptions;
  117. }
  118. boolean done = false;
  119. for (ListIterator<OptionMatch> i = options.listIterator();i.hasNext();) {
  120. OptionMatch om = i.next();
  121. if (om.getParameter().equals(desc)) {
  122. List<LiteralValue> v = new ArrayList<LiteralValue>(om.getValues());
  123. v.addAll(bilto(optionEvent.getValues()));
  124. List<String> names = new ArrayList<String>(om.getNames());
  125. names.add(optionEvent.getToken().getName());
  126. i.set(new OptionMatch(desc, names, v));
  127. done = true;
  128. break;
  129. }
  130. }
  131. if (!done) {
  132. OptionMatch match = new OptionMatch(desc, optionEvent.getToken().getName(), bilto(optionEvent.getValues()));
  133. options.add(match);
  134. }
  135. } else if (event instanceof Event.Method) {
  136. if (event instanceof Event.Method.Implicit) {
  137. Event.Method.Implicit implicit = (Event.Method.Implicit)event;
  138. classEnd = implicit.getTrigger().getFrom();
  139. method = (MethodDescriptor<T>)implicit.getDescriptor();
  140. } else {
  141. Event.Method.Explicit explicit = (Event.Method.Explicit)event;
  142. classEnd = explicit.getToken().getFrom();
  143. method = (MethodDescriptor<T>)explicit.getDescriptor();
  144. }
  145. } else if (event instanceof Event.Argument) {
  146. Event.Argument argumentEvent = (Event.Argument)event;
  147. List<Token.Literal> values = argumentEvent.getValues();
  148. ArgumentMatch match;
  149. if (values.size() > 0) {
  150. match = new ArgumentMatch(
  151. argumentEvent.getDescriptor(),
  152. argumentEvent.getFrom(),
  153. argumentEvent.getTo(),
  154. bilto(argumentEvent.getValues())
  155. );
  156. if (argumentEvent.getDescriptor().getOwner() instanceof ClassDescriptor<?>) {
  157. classArguments.add(match);
  158. } else {
  159. methodArguments.add(match);
  160. }
  161. }
  162. }
  163. previous = event;
  164. }
  165. //
  166. ClassMatch classMatch = new ClassMatch(descriptor, classOptions, classArguments, s.substring(classEnd));
  167. if (method != null) {
  168. return new MethodMatch(classMatch, method, false, methodOptions, methodArguments, s.substring(methodEnd));
  169. } else {
  170. return classMatch;
  171. }
  172. }
  173. private abstract class Completion {
  174. protected abstract Map<String, String> complete() throws CmdCompletionException;
  175. }
  176. private class EmptyCompletion extends Completion {
  177. @Override
  178. protected Map<String, String> complete() throws CmdCompletionException {
  179. return Collections.emptyMap();
  180. }
  181. }
  182. private class SpaceCompletion extends Completion {
  183. @Override
  184. protected Map<String, String> complete() throws CmdCompletionException {
  185. return Collections.singletonMap("", " ");
  186. }
  187. }
  188. private class MethodCompletion extends Completion {
  189. /** . */
  190. private final String mainName;
  191. /** . */
  192. private final String prefix;
  193. /** . */
  194. private final Termination termination;
  195. private MethodCompletion(String mainName, String prefix, Termination termination) {
  196. this.mainName = mainName;
  197. this.prefix = prefix;
  198. this.termination = termination;
  199. }
  200. @Override
  201. protected Map<String, String> complete() throws CmdCompletionException {
  202. Map<String, String> completions = new HashMap<String, String>();
  203. for (MethodDescriptor<?> m : descriptor.getMethods()) {
  204. String name = m.getName();
  205. if (name.startsWith(prefix)) {
  206. if (!name.equals(mainName)) {
  207. completions.put(name.substring(prefix.length()), termination.getEnd());
  208. }
  209. }
  210. }
  211. return completions;
  212. }
  213. }
  214. private class OptionCompletion extends Completion {
  215. /** . */
  216. private final CommandDescriptor<T, ?> descriptor;
  217. /** . */
  218. private final Token.Literal.Option prefix;
  219. private OptionCompletion(CommandDescriptor<T, ?> descriptor, Token.Literal.Option prefix) {
  220. this.descriptor = descriptor;
  221. this.prefix = prefix;
  222. }
  223. @Override
  224. protected Map<String, String> complete() throws CmdCompletionException {
  225. Map<String, String> completions = new HashMap<String, String>();
  226. Set<String> optionNames = prefix instanceof Token.Literal.Option.Short ? descriptor.getShortOptionNames() : descriptor.getLongOptionNames();
  227. for (String optionName : optionNames) {
  228. if (optionName.startsWith(prefix.value)) {
  229. completions.put(optionName.substring(prefix.value.length()), " ");
  230. }
  231. }
  232. return completions;
  233. }
  234. }
  235. private Completion argument(MethodDescriptor<?> method, Completer completer) {
  236. List<? extends ArgumentDescriptor<?>> arguments = method.getArguments();
  237. if (arguments.isEmpty()) {
  238. return new EmptyCompletion();
  239. } else {
  240. ArgumentDescriptor<?> argument = arguments.get(0);
  241. return new ParameterCompletion("", Termination.DETERMINED, argument, completer);
  242. }
  243. }
  244. private class ParameterCompletion extends Completion {
  245. /** . */
  246. private final String prefix;
  247. /** . */
  248. private final Termination termination;
  249. /** . */
  250. private final ParameterDescriptor<?> parameter;
  251. /** . */
  252. private final Completer completer;
  253. private ParameterCompletion(String prefix, Termination termination, ParameterDescriptor<?> parameter, Completer completer) {
  254. this.prefix = prefix;
  255. this.termination = termination;
  256. this.parameter = parameter;
  257. this.completer = completer;
  258. }
  259. protected Map<String, String> complete() throws CmdCompletionException {
  260. Class<? extends Completer> completerType = parameter.getCompleterType();
  261. Completer completer = this.completer;
  262. // Use the most adapted completer
  263. if (completerType != EmptyCompleter.class) {
  264. try {
  265. completer = completerType.newInstance();
  266. }
  267. catch (Exception e) {
  268. throw new CmdCompletionException(e);
  269. }
  270. }
  271. //
  272. if (completer != null) {
  273. try {
  274. Map<String, Boolean> res = completer.complete(parameter, prefix);
  275. Map<String, String> delimiter = new HashMap<String, String>();
  276. for (Map.Entry<String, Boolean> entry : res.entrySet()) {
  277. delimiter.put(entry.getKey(), entry.getValue() ? termination.getEnd() : "");
  278. }
  279. return delimiter;
  280. }
  281. catch (Exception e) {
  282. throw new CmdCompletionException(e);
  283. }
  284. } else {
  285. return Collections.emptyMap();
  286. }
  287. }
  288. }
  289. @Override
  290. public Map<String, String> complete(Completer completer, String s) throws CmdCompletionException {
  291. return _complete(completer, s).complete();
  292. }
  293. private Completion _complete(Completer completer, String s) throws CmdCompletionException {
  294. Tokenizer tokenizer = new Tokenizer(s);
  295. Parser<T> parser = new Parser<T>(tokenizer, descriptor, mainName, Parser.Mode.COMPLETE);
  296. // Last non separator event
  297. Event last = null;
  298. Event.Separator separator = null;
  299. MethodDescriptor<?> method = null;
  300. Event.Stop stop;
  301. //
  302. while (true) {
  303. Event event = parser.next();
  304. if (event instanceof Event.Separator) {
  305. separator = (Event.Separator)event;
  306. } else if (event instanceof Event.Stop) {
  307. stop = (Event.Stop)event;
  308. break;
  309. } else if (event instanceof Event.Option) {
  310. last = event;
  311. separator = null;
  312. } else if (event instanceof Event.Method) {
  313. method = ((Event.Method)event).getDescriptor();
  314. last = event;
  315. separator = null;
  316. } else if (event instanceof Event.Argument) {
  317. last = event;
  318. separator = null;
  319. } else if (event instanceof Event.DoubleDash) {
  320. last = event;
  321. separator = null;
  322. }
  323. }
  324. //
  325. if (stop instanceof Event.Stop.Unresolved.NoSuchOption) {
  326. Event.Stop.Unresolved.NoSuchOption nso = (Event.Stop.Unresolved.NoSuchOption)stop;
  327. return new OptionCompletion(method != null ? (CommandDescriptor<T, ?>)method : descriptor, nso.getToken());
  328. } else if (stop instanceof Event.Stop.Unresolved) {
  329. if (stop instanceof Event.Stop.Unresolved.TooManyArguments) {
  330. if (method == null) {
  331. Event.Stop.Unresolved.TooManyArguments tma = (Event.Stop.Unresolved.TooManyArguments)stop;
  332. return new MethodCompletion(mainName, s.substring(stop.getIndex()), tma.getToken().termination);
  333. } else {
  334. return new EmptyCompletion();
  335. }
  336. } else {
  337. return new EmptyCompletion();
  338. }
  339. } else if (stop instanceof Event.Stop.Done.Option) {
  340. // to use ?
  341. } else if (stop instanceof Event.Stop.Done.Arg) {
  342. // to use ?
  343. }
  344. //
  345. if (last == null) {
  346. if (method == null) {
  347. if (descriptor.getSubordinates().keySet().equals(Collections.singleton(mainName))) {
  348. method = descriptor.getMethod(mainName);
  349. List<ArgumentDescriptor<MethodArgumentBinding>> args = method.getArguments();
  350. if (args.size() > 0) {
  351. return new ParameterCompletion("", Termination.DETERMINED, args.get(0), completer);
  352. } else {
  353. return new EmptyCompletion();
  354. }
  355. } else {
  356. return new MethodCompletion(mainName, s.substring(stop.getIndex()), Termination.DETERMINED);
  357. }
  358. } else {
  359. return new EmptyCompletion();
  360. }
  361. }
  362. //
  363. if (last instanceof Event.DoubleDash) {
  364. Event.DoubleDash dd = (Event.DoubleDash)last;
  365. return new OptionCompletion(method != null ? (CommandDescriptor<T, ?>)method : descriptor, dd.token);
  366. } else if (last instanceof Event.Option) {
  367. Event.Option optionEvent = (Event.Option)last;
  368. List<Token.Literal.Word> values = optionEvent.getValues();
  369. OptionDescriptor<?> option = optionEvent.getDescriptor();
  370. if (separator == null) {
  371. if (values.size() == 0) {
  372. return new SpaceCompletion();
  373. } else if (values.size() <= option.getArity()) {
  374. Token.Literal.Word word = optionEvent.peekLast();
  375. return new ParameterCompletion(word.value, word.termination, option, completer);
  376. } else {
  377. return new EmptyCompletion();
  378. }
  379. } else {
  380. if (values.size() < option.getArity()) {
  381. return new ParameterCompletion("", Termination.DETERMINED, option, completer);
  382. } else {
  383. if (method == null) {
  384. return new MethodCompletion(mainName, s.substring(stop.getIndex()), Termination.DETERMINED);
  385. } else {
  386. return argument(method, completer);
  387. }
  388. }
  389. }
  390. } else if (last instanceof Event.Argument) {
  391. Event.Argument eventArgument = (Event.Argument)last;
  392. ArgumentDescriptor<?> argument = eventArgument.getDescriptor();
  393. if (separator != null) {
  394. switch (argument.getMultiplicity()) {
  395. case ZERO_OR_ONE:
  396. case ONE:
  397. List<? extends ArgumentDescriptor<?>> arguments = argument.getOwner().getArguments();
  398. int index = arguments.indexOf(argument) + 1;
  399. if (index < arguments.size()) {
  400. throw new UnsupportedOperationException("Need to find next argument and use it for completion");
  401. } else {
  402. return new EmptyCompletion();
  403. }
  404. case ZERO_OR_MORE:
  405. return new ParameterCompletion("", Termination.DETERMINED, argument, completer);
  406. default:
  407. throw new AssertionError();
  408. }
  409. } else {
  410. Token.Literal value = eventArgument.peekLast();
  411. return new ParameterCompletion(value.value, value.termination, argument, completer);
  412. }
  413. } else if (last instanceof Event.Method) {
  414. if (separator != null) {
  415. return argument(method, completer);
  416. } else {
  417. return new SpaceCompletion();
  418. }
  419. } else {
  420. throw new AssertionError();
  421. }
  422. }
  423. }