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.cmdline.matcher.impl;
021    
022    import org.crsh.cmdline.ArgumentDescriptor;
023    import org.crsh.cmdline.ClassDescriptor;
024    import org.crsh.cmdline.CommandCompletion;
025    import org.crsh.cmdline.CommandDescriptor;
026    import org.crsh.cmdline.Delimiter;
027    import org.crsh.cmdline.MethodDescriptor;
028    import org.crsh.cmdline.OptionDescriptor;
029    import org.crsh.cmdline.binding.ClassFieldBinding;
030    import org.crsh.cmdline.binding.MethodArgumentBinding;
031    import org.crsh.cmdline.matcher.ArgumentMatch;
032    import org.crsh.cmdline.matcher.ClassMatch;
033    import org.crsh.cmdline.matcher.CmdCompletionException;
034    import org.crsh.cmdline.matcher.CommandMatch;
035    import org.crsh.cmdline.matcher.LiteralValue;
036    import org.crsh.cmdline.matcher.Matcher;
037    import org.crsh.cmdline.matcher.MethodMatch;
038    import org.crsh.cmdline.matcher.OptionMatch;
039    import org.crsh.cmdline.matcher.tokenizer.Token;
040    import org.crsh.cmdline.matcher.tokenizer.Tokenizer;
041    import org.crsh.cmdline.matcher.tokenizer.TokenizerImpl;
042    import org.crsh.cmdline.spi.Completer;
043    
044    import java.util.ArrayList;
045    import java.util.Collections;
046    import java.util.Iterator;
047    import java.util.List;
048    import java.util.ListIterator;
049    import java.util.Map;
050    
051    public class MatcherImpl<T> extends Matcher<T> {
052    
053      /** . */
054      private final ClassDescriptor<T> descriptor;
055    
056      /** . */
057      private final String mainName;
058    
059      public MatcherImpl(ClassDescriptor<T> descriptor) {
060        this(null, descriptor);
061      }
062    
063      public MatcherImpl(String mainName, ClassDescriptor<T> descriptor) {
064        this.mainName = mainName;
065        this.descriptor = descriptor;
066      }
067    
068      private List<LiteralValue> bilto(List<? extends Token.Literal> literals) {
069        List<LiteralValue> values = new ArrayList<LiteralValue>(literals.size());
070        for (Token.Literal literal : literals) {
071          values.add(new LiteralValue(literal.getRaw(), literal.getValue()));
072        }
073        return values;
074      }
075    
076      public CommandMatch<T, ?, ?> match(final String name, Map<String, ?> options, List<?> arguments) {
077    
078        class TokenizerImpl extends ArrayList<Token> {
079          int last() {
080            return size() > 0 ? get(size() - 1).getTo() : 0;
081          }
082          @Override
083          public boolean add(Token token) {
084            if (size() > 0) {
085              super.add(new Token.Whitespace(last(), " "));
086            }
087            return super.add(token);
088          }
089    
090          public void addOption(String name) {
091            if (name.length() == 1) {
092              add(new Token.Literal.Option.Short(last(), "-" + name));
093            } else {
094              add(new Token.Literal.Option.Long(last(), "--" + name));
095            }
096          }
097        }
098        final TokenizerImpl t = new TokenizerImpl();
099    
100        // Add name
101        if (name != null && name.length() > 0) {
102          t.add(new Token.Literal.Word(t.last(), name));
103        }
104    
105        // Add options
106        for (Map.Entry<String, ?> option : options.entrySet()) {
107          if (option.getValue() instanceof Boolean) {
108            if ((Boolean)option.getValue()) {
109              t.addOption(option.getKey());
110            }
111          } else {
112            t.addOption(option.getKey());
113            t.add(new Token.Literal.Word(t.last(), option.getValue().toString()));
114          }
115        }
116    
117        //
118        for (Object argument : arguments) {
119          t.add(new Token.Literal.Word(t.last(), argument.toString()));
120        }
121    
122        //
123        Tokenizer tokenizer = new Tokenizer() {
124    
125          Iterator<Token> i = t.iterator();
126    
127          @Override
128          protected Token parse() {
129            return i.hasNext() ? i.next() : null;
130          }
131    
132          @Override
133          public Delimiter getDelimiter() {
134            return Delimiter.EMPTY;
135          }
136        };
137    
138        //
139        return match(tokenizer);
140      }
141    
142      @Override
143      public CommandMatch<T, ?, ?> match(String s) {
144        return match(new TokenizerImpl(s));
145      }
146    
147      private CommandMatch<T, ?, ?> match(Tokenizer tokenizer) {
148    
149        //
150        List<OptionMatch<ClassFieldBinding>> classOptions = new ArrayList<OptionMatch<ClassFieldBinding>>();
151        List<ArgumentMatch<ClassFieldBinding>> classArguments = new ArrayList<ArgumentMatch<ClassFieldBinding>>();
152        List<OptionMatch<MethodArgumentBinding>> methodOptions = new ArrayList<OptionMatch<MethodArgumentBinding>>();
153        List<ArgumentMatch<MethodArgumentBinding>> methodArguments = new ArrayList<ArgumentMatch<MethodArgumentBinding>>();
154        MethodDescriptor<T> method = null;
155    
156        Parser<T> parser = new Parser<T>(tokenizer, descriptor, mainName, Mode.INVOKE);
157    
158    
159        //
160        while (true) {
161          Event event = parser.next();
162          if (event instanceof Event.Separator) {
163            //
164          } else if (event instanceof Event.Stop) {
165            // We are done
166            // Check error status and react to it maybe
167            // We try to match the main if none was found
168            if (method == null) {
169              if (mainName != null) {
170                method = descriptor.getMethod(mainName);
171              }
172            }
173            break;
174          } else if (event instanceof Event.Option) {
175            Event.Option optionEvent = (Event.Option)event;
176            OptionDescriptor<?> desc = optionEvent.getDescriptor();
177            List options;
178            if (desc.getOwner() instanceof ClassDescriptor<?>) {
179              options = classOptions;
180            } else {
181              options = methodOptions;
182            }
183            boolean done = false;
184            for (ListIterator<OptionMatch> i = options.listIterator();i.hasNext();) {
185              OptionMatch om = i.next();
186              if (om.getParameter().equals(desc)) {
187                List<LiteralValue> v = new ArrayList<LiteralValue>(om.getValues());
188                v.addAll(bilto(optionEvent.getValues()));
189                List<String> names = new ArrayList<String>(om.getNames());
190                names.add(optionEvent.getToken().getName());
191                i.set(new OptionMatch(desc, names, v));
192                done = true;
193                break;
194              }
195            }
196            if (!done) {
197              options.add(new OptionMatch(desc, optionEvent.getToken().getName(), bilto(optionEvent.getValues())));
198            }
199          } else if (event instanceof Event.Method) {
200            method = (MethodDescriptor<T>)((Event.Method)event).getDescriptor();
201          } else if (event instanceof Event.Argument) {
202            Event.Argument argumentEvent = (Event.Argument)event;
203            List<Token.Literal> values = argumentEvent.getValues();
204            ArgumentMatch match;
205            if (values.size() > 0) {
206              match = new ArgumentMatch(
207                argumentEvent.getDescriptor(),
208                argumentEvent.getFrom(),
209                argumentEvent.getTo(),
210                bilto(argumentEvent.getValues())
211              );
212              if (argumentEvent.getDescriptor().getOwner() instanceof ClassDescriptor<?>) {
213                classArguments.add(match);
214              } else {
215                methodArguments.add(match);
216              }
217            }
218          }
219        }
220    
221        //
222        StringBuilder rest = new StringBuilder();
223        while (tokenizer.hasNext()) {
224          Token token = tokenizer.next();
225          rest.append(token.getRaw());
226        }
227    
228        //
229        ClassMatch classMatch = new ClassMatch(descriptor, classOptions, classArguments, rest.toString());
230        if (method != null) {
231          return new MethodMatch(classMatch, method, false, methodOptions, methodArguments, rest.toString());
232        } else {
233          return classMatch;
234        }
235      }
236    
237      private Completion argument(MethodDescriptor<?> method, Completer completer) {
238        List<? extends ArgumentDescriptor<?>> arguments = method.getArguments();
239        if (arguments.isEmpty()) {
240          return new EmptyCompletion();
241        } else {
242          ArgumentDescriptor<?> argument = arguments.get(0);
243          return new ParameterCompletion("", Delimiter.EMPTY, argument, completer);
244        }
245      }
246    
247      @Override
248      public CommandCompletion complete(Completer completer, String s) throws CmdCompletionException {
249        return getCompletion(completer, s).complete();
250      }
251    
252      private Completion getCompletion(Completer completer, String s) throws CmdCompletionException {
253    
254        Tokenizer tokenizer = new TokenizerImpl(s);
255        Parser<T> parser = new Parser<T>(tokenizer, descriptor, mainName, Mode.COMPLETE);
256    
257        // Last non separator event
258        Event last = null;
259        Event.Separator separator = null;
260        MethodDescriptor<?> method = null;
261        Event.Stop stop;
262    
263        //
264        while (true) {
265          Event event = parser.next();
266          if (event instanceof Event.Separator) {
267            separator = (Event.Separator)event;
268          } else if (event instanceof Event.Stop) {
269            stop = (Event.Stop)event;
270            break;
271          } else if (event instanceof Event.Option) {
272            last = event;
273            separator = null;
274          } else if (event instanceof Event.Method) {
275            method = ((Event.Method)event).getDescriptor();
276            last = event;
277            separator = null;
278          } else if (event instanceof Event.Argument) {
279            last = event;
280            separator = null;
281          }/* else if (event instanceof Event.DoubleDash) {
282            last = event;
283            separator = null;
284          }*/
285        }
286    
287        //
288        if (stop instanceof Event.Stop.Unresolved.NoSuchOption) {
289          Event.Stop.Unresolved.NoSuchOption nso = (Event.Stop.Unresolved.NoSuchOption)stop;
290          return new OptionCompletion<T>(method != null ? (CommandDescriptor<T, ?>)method : descriptor, nso.getToken());
291        } else if (stop instanceof Event.Stop.Unresolved) {
292          if (stop instanceof Event.Stop.Unresolved.TooManyArguments) {
293            if (method == null) {
294              Event.Stop.Unresolved.TooManyArguments tma = (Event.Stop.Unresolved.TooManyArguments)stop;
295              return new MethodCompletion<T>(descriptor, mainName, s.substring(stop.getIndex()), parser.getDelimiter());
296            } else {
297              return new EmptyCompletion();
298            }
299          } else {
300            return new EmptyCompletion();
301          }
302        } else if (stop instanceof Event.Stop.Done.Option) {
303          // to use ?
304        } else if (stop instanceof Event.Stop.Done.Arg) {
305          // to use ?
306        }
307    
308        //
309        if (last == null) {
310          if (method == null) {
311            if (descriptor.getSubordinates().keySet().equals(Collections.singleton(mainName))) {
312              method = descriptor.getMethod(mainName);
313              List<ArgumentDescriptor<MethodArgumentBinding>> args = method.getArguments();
314              if (args.size() > 0) {
315                return new ParameterCompletion("", Delimiter.EMPTY, args.get(0), completer);
316              } else {
317                return new EmptyCompletion();
318              }
319            } else {
320              return new MethodCompletion<T>(descriptor, mainName, s.substring(stop.getIndex()), Delimiter.EMPTY);
321            }
322          } else {
323            return new EmptyCompletion();
324          }
325        }
326    
327        //
328        /*if (last instanceof Event.DoubleDash) {
329          Event.DoubleDash dd = (Event.DoubleDash)last;
330          return new OptionCompletion<T>(method != null ? (CommandDescriptor<T, ?>)method : descriptor, dd.token);
331        } else*/
332        if (last instanceof Event.Option) {
333          Event.Option optionEvent = (Event.Option)last;
334          List<Token.Literal.Word> values = optionEvent.getValues();
335          OptionDescriptor<?> option = optionEvent.getDescriptor();
336          if (separator == null) {
337            if (values.size() == 0) {
338              return new SpaceCompletion();
339            } else if (values.size() <= option.getArity()) {
340              Token.Literal.Word word = optionEvent.peekLast();
341              return new ParameterCompletion(word.getValue(), parser.getDelimiter(), option, completer);
342            } else {
343              return new EmptyCompletion();
344            }
345          } else {
346            if (values.size() < option.getArity()) {
347              return new ParameterCompletion("", Delimiter.EMPTY, option, completer);
348            } else {
349              if (method == null) {
350                return new MethodCompletion<T>(descriptor, mainName, s.substring(stop.getIndex()), Delimiter.EMPTY);
351              } else {
352                return argument(method, completer);
353              }
354            }
355          }
356        } else if (last instanceof Event.Argument) {
357          Event.Argument eventArgument = (Event.Argument)last;
358          ArgumentDescriptor<?> argument = eventArgument.getDescriptor();
359          if (separator != null) {
360            switch (argument.getMultiplicity()) {
361              case SINGLE:
362                List<? extends ArgumentDescriptor<?>> arguments = argument.getOwner().getArguments();
363                int index = arguments.indexOf(argument) + 1;
364                if (index < arguments.size()) {
365                  ArgumentDescriptor<?> nextArg = arguments.get(index);
366                  return new ParameterCompletion("", Delimiter.EMPTY, nextArg, completer);
367                } else {
368                  return new EmptyCompletion();
369                }
370              case MULTI:
371                return new ParameterCompletion("", Delimiter.EMPTY, argument, completer);
372              default:
373                throw new AssertionError();
374            }
375          } else {
376            Token.Literal value = eventArgument.peekLast();
377            return new ParameterCompletion(value.getValue(), parser.getDelimiter(), argument, completer);
378          }
379        } else if (last instanceof Event.Method) {
380          if (separator != null) {
381            return argument(method, completer);
382          } else {
383            return new SpaceCompletion();
384          }
385        } else {
386          throw new AssertionError();
387        }
388      }
389    }