PageRenderTime 63ms CodeModel.GetById 2ms app.highlight 57ms RepoModel.GetById 1ms app.codeStats 0ms

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