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