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