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.*;
023    import org.crsh.cmdline.matcher.tokenizer.Token;
024    import org.crsh.cmdline.matcher.tokenizer.Tokenizer;
025    
026    import java.util.*;
027    
028    abstract class Status {
029    
030      /**
031       * The input.
032       */
033      static class Request {
034    
035        /** . */
036        final Mode mode;
037        
038        /** . */
039        final String mainName;
040        
041        /** . */
042        Tokenizer tokenizer;
043    
044        /** . */
045        final CommandDescriptor<?, ?> command;
046    
047        Request(Mode mode, String mainName, Tokenizer tokenizer, CommandDescriptor<?, ?> command) {
048          this.mode = mode;
049          this.mainName = mainName;
050          this.tokenizer = tokenizer;
051          this.command = command;
052        }
053      }
054    
055      /**
056       * The output.
057       */
058      static class Response {
059    
060        /** . */
061        Status status;
062    
063        /** . */
064        LinkedList<Event> events;
065    
066        /** . */
067        CommandDescriptor<?, ?> command;
068    
069        Response(Status status) {
070          this.status = status;
071          this.events = null;
072          this.command = null;
073        }
074    
075        Response() {
076          this.status = null;
077          this.events = null;
078          this.command = null;
079        }
080    
081        void add(Event event) {
082          if (events == null) {
083            events = new LinkedList<Event>();
084          }
085          events.add(event);
086        }
087    
088        void addAll(Collection<Event> toAdd) {
089          if (events == null) {
090            events = new LinkedList<Event>();
091          }
092          events.addAll(toAdd);
093        }
094      }
095    
096      /**
097       * Process a request.
098       *
099       * @param req the request
100       * @param <T> the generic type of the command
101       * @return the response
102       */
103      abstract <T> Response process(Request req);
104    
105      static class ReadingOption extends Status {
106    
107        <T> Response process(Request req) {
108          Response response = new Response();
109          Token token = req.tokenizer.peek();
110          if (token == null) {
111            response.add(new Event.Stop.Done.Option(req.tokenizer.getIndex()));
112          } else if (token instanceof Token.Whitespace) {
113            response.add(new Event.Separator((Token.Whitespace) token));
114            req.tokenizer.next();
115          } else {
116            Token.Literal literal = (Token.Literal)token;
117            if (literal instanceof Token.Literal.Option) {
118              Token.Literal.Option optionToken = (Token.Literal.Option)literal;
119              if (optionToken.getName().length() == 0 && optionToken instanceof Token.Literal.Option.Long) {
120                req.tokenizer.next();
121                if (req.tokenizer.hasNext()) {
122                  if (req.command instanceof ClassDescriptor<?>) {
123                    ClassDescriptor<T> classCommand = (ClassDescriptor<T>)req.command;
124                    MethodDescriptor<T> m = classCommand.getMethod(req.mainName);
125                    if (m != null) {
126                      response.command = m;
127                      response.add(new Event.Method.Implicit(m, optionToken));
128                    }
129                  }
130                  response.status = new Status.WantReadArg();
131                } else {
132                  if (req.mode == Mode.INVOKE) {
133                    if (req.command instanceof ClassDescriptor<?>) {
134                      ClassDescriptor<T> classCommand = (ClassDescriptor<T>)req.command;
135                      MethodDescriptor<T> m = classCommand.getMethod(req.mainName);
136                      if (m != null) {
137                        response.command = m;
138                        response.add(new Event.Method.Implicit(m, optionToken));
139                      }
140                    }
141                    response.status = new Status.Done();
142                    response.add(new Event.Stop.Done.Arg(req.tokenizer.getIndex()));
143                  } else {
144                    if (req.command instanceof ClassDescriptor<?>) {
145                      ClassDescriptor<T> classCommand = (ClassDescriptor<T>)req.command;
146                      MethodDescriptor<T> m = classCommand.getMethod(req.mainName);
147                      if (m != null) {
148                        response.command = m;
149                        response.add(new Event.Method.Implicit(m, optionToken));
150                        response.add(new Event.Stop.Unresolved.NoSuchOption.Method(optionToken));
151                      } else  {
152                        response.add(new Event.Stop.Unresolved.NoSuchOption.Class(optionToken));
153                      }
154                    } else {
155                      response.add(new Event.Stop.Unresolved.NoSuchOption.Method(optionToken));
156                    }
157                  }
158                }
159              } else {
160                OptionDescriptor<?> desc = req.command.findOption(literal.getValue());
161                if (desc != null) {
162                  req.tokenizer.next();
163                  int arity = desc.getArity();
164                  LinkedList<Token.Literal.Word> values = new LinkedList<Token.Literal.Word>();
165                  while (arity > 0) {
166                    if (req.tokenizer.hasNext()) {
167                      Token a = req.tokenizer.peek();
168                      if (a instanceof Token.Whitespace) {
169                        req.tokenizer.next();
170                        if (req.tokenizer.hasNext() && req.tokenizer.peek() instanceof Token.Literal.Word) {
171                          // ok
172                        } else {
173                          req.tokenizer.pushBack();
174                          break;
175                        }
176                      } else {
177                        Token.Literal b = (Token.Literal)a;
178                        if (b instanceof Token.Literal.Word) {
179                          values.addLast((Token.Literal.Word)b);
180                          req.tokenizer.next();
181                          arity--;
182                        } else {
183                          req.tokenizer.pushBack();
184                          break;
185                        }
186                      }
187                    } else {
188                      break;
189                    }
190                  }
191                  response.add(new Event.Option(desc, optionToken, values));
192                } else {
193                  // We are reading an unknown option
194                  // it could match an option of an implicit command
195                  if (req.command instanceof ClassDescriptor<?>) {
196                    MethodDescriptor<T> m = ((ClassDescriptor<T>)req.command).getMethod(req.mainName);
197                    if (m != null) {
198                      desc = m.findOption(literal.getValue());
199                      if (desc != null) {
200                        response.command = m;
201                        response.add(new Event.Method.Implicit(m, literal));
202                      } else {
203                        if (req.command.getOptionNames().size() == 0) {
204                          response.command = m;
205                          response.add(new Event.Method.Implicit(m, literal));
206                        } else {
207                          response.add(new Event.Stop.Unresolved.NoSuchOption.Method(optionToken));
208                        }
209                      }
210                    } else {
211                      response.add(new Event.Stop.Unresolved.NoSuchOption.Class(optionToken));
212                    }
213                  } else {
214                    response.add(new Event.Stop.Unresolved.NoSuchOption.Method(optionToken));
215                  }
216                }
217              }
218            } else {
219              Token.Literal.Word wordLiteral = (Token.Literal.Word)literal;
220              if (req.command instanceof ClassDescriptor<?>) {
221                ClassDescriptor<T> classCommand = (ClassDescriptor<T>)req.command;
222                MethodDescriptor<T> m = classCommand.getMethod(wordLiteral.getValue());
223                if (m != null && !m.getName().equals(req.mainName)) {
224                  response.command = m;
225                  req.tokenizer.next();
226                  response.add(new Event.Method.Explicit(m, wordLiteral));
227                } else {
228                  m = classCommand.getMethod(req.mainName);
229                  if (m != null) {
230                    response.add(new Event.Method.Implicit(m, wordLiteral));
231                    response.status = new Status.WantReadArg();
232                    response.command = m;
233                  } else {
234                    response.status = new Status.WantReadArg();
235                  }
236                }
237              } else {
238                response.status = new Status.WantReadArg();
239              }
240            }
241          }
242          return response;
243        }
244    
245      }
246    
247      static class WantReadArg extends Status {
248        @Override
249        <T> Response process(Request req) {
250          switch (req.mode) {
251            case INVOKE:
252              return new Response(new Status.ComputeArg());
253            case COMPLETE:
254              return new Response(new Status.ReadingArg());
255            default:
256              throw new AssertionError();
257          }
258        }
259      }
260    
261      static class ComputeArg extends Status {
262    
263        @Override
264        <T> Response process(Request req) {
265          Token token = req.tokenizer.peek();
266          Response response = new Response();
267          if (token == null) {
268            response.add(new Event.Stop.Done.Arg(req.tokenizer.getIndex()));
269          } else if (token instanceof Token.Whitespace) {
270            response.add(new Event.Separator((Token.Whitespace) token));
271            req.tokenizer.next();
272          } else {
273    
274            //
275            List<? extends ArgumentDescriptor<?>> arguments = req.command.getArguments();
276    
277            // Count the number ok remaining non whitespace;
278            int tokenCount = 0;
279            int wordCount = 0;
280            do {
281              Token t = req.tokenizer.next();
282              if (t instanceof Token.Literal) {
283                wordCount++;
284              }
285              tokenCount++;
286            }
287            while (req.tokenizer.hasNext());
288            req.tokenizer.pushBack(tokenCount);
289    
290            //
291            int oneCount = 0;
292            int zeroOrOneCount = 0;
293            int index = 0;
294            for (ArgumentDescriptor<?> argument : arguments) {
295              Multiplicity multiplicity = argument.getMultiplicity();
296              if (multiplicity == Multiplicity.SINGLE) {
297                if (argument.isRequired()) {
298                  if (oneCount + 1 > wordCount) {
299                    break;
300                  }
301                  oneCount++;
302                } else {
303                  zeroOrOneCount++;
304                }
305              }
306              index++;
307            }
308    
309            // This the number of arguments we can satisfy
310            arguments = arguments.subList(0, index);
311    
312            // How many words we can consume for zeroOrOne and zeroOrMore
313            int toConsume = wordCount - oneCount;
314    
315            // Correct the zeroOrOneCount and adjust toConsume
316            zeroOrOneCount = Math.min(zeroOrOneCount, toConsume);
317            toConsume -= zeroOrOneCount;
318    
319            // The remaining
320            LinkedList<Event> events = new LinkedList<Event>();
321            for (ArgumentDescriptor<?> argument : arguments) {
322              int size;
323              switch (argument.getMultiplicity()) {
324                case SINGLE:
325                  if (argument.isRequired()) {
326                    size = 1;
327                  } else {
328                    if (zeroOrOneCount > 0) {
329                      zeroOrOneCount--;
330                      size = 1;
331                    } else {
332                      size = 0;
333                    }
334                  }
335                  break;
336                case MULTI:
337                  // We consume the remaining
338                  size = toConsume;
339                  toConsume = 0;
340                  break;
341                default:
342                  throw new AssertionError();
343              }
344    
345              // Now take care of the argument
346              if (size > 0) {
347                List<Token.Literal> values = new ArrayList<Token.Literal>(size);
348                while (size > 0) {
349                  Token t = req.tokenizer.next();
350                  if (t instanceof Token.Literal) {
351                    values.add(((Token.Literal)t));
352                    size--;
353                  }
354                }
355                events.addLast(new Event.Argument(argument, values));
356    
357                // Add the whitespace if needed
358                if (req.tokenizer.hasNext() && req.tokenizer.peek() instanceof Token.Whitespace) {
359                  events.addLast(new Event.Separator((Token.Whitespace) req.tokenizer.next()));
360                }
361              }
362            }
363    
364            //
365            events.addLast(new Event.Stop.Done.Arg(req.tokenizer.getIndex()));
366    
367            //
368            response.status = new Status.Done();
369            response.addAll(events);
370          }
371          return response;
372        }
373      }
374    
375      static class Done extends Status {
376        @Override
377        <T> Response process(Request req) {
378          throw new IllegalStateException();
379        }
380      }
381    
382      static class ReadingArg extends Status {
383    
384        /** . */
385        private final int index;
386    
387        ReadingArg() {
388          this(0);
389        }
390    
391        private ReadingArg(int index) {
392          this.index = index;
393        }
394    
395        ReadingArg next() {
396          return new ReadingArg(index + 1);
397        }
398    
399        @Override
400        <T> Response process(Request req) {
401          Token token = req.tokenizer.peek();
402          Response response = new Response();
403          if (token == null) {
404            response.add(new Event.Stop.Done.Arg(req.tokenizer.getIndex()));
405          } else if (token instanceof Token.Whitespace) {
406            response.add(new Event.Separator((Token.Whitespace) token));
407            req.tokenizer.next();
408          } else {
409            final Token.Literal literal = (Token.Literal)token;
410            List<? extends ArgumentDescriptor<?>> arguments = req.command.getArguments();
411            if (index < arguments.size()) {
412              ArgumentDescriptor<?> argument = arguments.get(index);
413              switch (argument.getMultiplicity()) {
414                case SINGLE:
415                  req.tokenizer.next();
416                  response.add(new Event.Argument(argument, Arrays.asList(literal)));
417                  response.status = next();
418                  break;
419                case MULTI:
420                  req.tokenizer.next();
421                  List<Token.Literal> values = new ArrayList<Token.Literal>();
422                  values.add(literal);
423                  while (req.tokenizer.hasNext()) {
424                    Token capture = req.tokenizer.next();
425                    if (capture instanceof Token.Literal) {
426                      values.add(((Token.Literal)capture));
427                    } else {
428                      if (req.tokenizer.hasNext()) {
429                        // Ok
430                      } else {
431                        req.tokenizer.pushBack();
432                        break;
433                      }
434                    }
435                  }
436                  response.add(new Event.Argument(argument, values));
437              }
438            } else {
439              response.add(new Event.Stop.Unresolved.TooManyArguments(literal));
440            }
441          }
442          return response;
443        }
444      }
445    }