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                response.add(new Event.DoubleDash((Token.Literal.Option.Long) optionToken));
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                OptionDescriptor<?> desc = req.command.findOption(literal.getValue());
117                if (desc != null) {
118                  req.tokenizer.next();
119                  int arity = desc.getArity();
120                  LinkedList<Token.Literal.Word> values = new LinkedList<Token.Literal.Word>();
121                  while (arity > 0) {
122                    if (req.tokenizer.hasNext()) {
123                      Token a = req.tokenizer.peek();
124                      if (a instanceof Token.Whitespace) {
125                        req.tokenizer.next();
126                        if (req.tokenizer.hasNext() && req.tokenizer.peek() instanceof Token.Literal.Word) {
127                          // ok
128                        } else {
129                          req.tokenizer.pushBack();
130                          break;
131                        }
132                      } else {
133                        Token.Literal b = (Token.Literal)a;
134                        if (b instanceof Token.Literal.Word) {
135                          values.addLast((Token.Literal.Word)b);
136                          req.tokenizer.next();
137                          arity--;
138                        } else {
139                          req.tokenizer.pushBack();
140                          break;
141                        }
142                      }
143                    } else {
144                      break;
145                    }
146                  }
147                  response.add(new Event.Option(desc, optionToken, values));
148                } else {
149                  // We are reading an unknown option
150                  // it could match an option of an implicit command
151                  if (req.command instanceof ClassDescriptor<?>) {
152                    MethodDescriptor<T> m = ((ClassDescriptor<T>)req.command).getMethod(req.mainName);
153                    if (m != null) {
154                      desc = m.findOption(literal.getValue());
155                      if (desc != null) {
156                        response.command = m;
157                        response.add(new Event.Method.Implicit(m, literal));
158                      } else {
159                        response.add(new Event.Stop.Unresolved.NoSuchOption.Method(optionToken));
160                      }
161                    } else {
162                      response.add(new Event.Stop.Unresolved.NoSuchOption.Class(optionToken));
163                    }
164                  } else {
165                    response.add(new Event.Stop.Unresolved.NoSuchOption.Method(optionToken));
166                  }
167                }
168              }
169            } else {
170              Token.Literal.Word wordLiteral = (Token.Literal.Word)literal;
171              if (req.command instanceof ClassDescriptor<?>) {
172                ClassDescriptor<T> classCommand = (ClassDescriptor<T>)req.command;
173                MethodDescriptor<T> m = classCommand.getMethod(wordLiteral.getValue());
174                if (m != null && !m.getName().equals(req.mainName)) {
175                  response.command = m;
176                  req.tokenizer.next();
177                  response.add(new Event.Method.Explicit(m, wordLiteral));
178                } else {
179                  m = classCommand.getMethod(req.mainName);
180                  if (m != null) {
181                    response.add(new Event.Method.Implicit(m, wordLiteral));
182                    response.status = new Status.WantReadArg();
183                    response.command = m;
184                  } else {
185                    response.status = new Status.WantReadArg();
186                  }
187                }
188              } else {
189                response.status = new Status.WantReadArg();
190              }
191            }
192          }
193          return response;
194        }
195    
196      }
197    
198      static class WantReadArg extends Status {
199        @Override
200        <T> Response process(Request req) {
201          switch (req.mode) {
202            case INVOKE:
203              return new Response(new Status.ComputeArg());
204            case COMPLETE:
205              return new Response(new Status.ReadingArg());
206            default:
207              throw new AssertionError();
208          }
209        }
210      }
211    
212      static class ComputeArg extends Status {
213    
214        @Override
215        <T> Response process(Request req) {
216          Token token = req.tokenizer.peek();
217          Response response = new Response();
218          if (token == null) {
219            response.add(new Event.Stop.Done.Arg(req.tokenizer.getIndex()));
220          } else if (token instanceof Token.Whitespace) {
221            response.add(new Event.Separator((Token.Whitespace) token));
222            req.tokenizer.next();
223          } else {
224    
225            //
226            List<? extends ArgumentDescriptor<?>> arguments = req.command.getArguments();
227    
228            // Count the number ok remaining non whitespace;
229            int tokenCount = 0;
230            int wordCount = 0;
231            do {
232              Token t = req.tokenizer.next();
233              if (t instanceof Token.Literal) {
234                wordCount++;
235              }
236              tokenCount++;
237            }
238            while (req.tokenizer.hasNext());
239            req.tokenizer.pushBack(tokenCount);
240    
241            //
242            int oneCount = 0;
243            int zeroOrOneCount = 0;
244            int index = 0;
245            for (ArgumentDescriptor<?> argument : arguments) {
246              Multiplicity multiplicity = argument.getMultiplicity();
247              if (multiplicity == Multiplicity.SINGLE) {
248                if (argument.isRequired()) {
249                  if (oneCount + 1 > wordCount) {
250                    break;
251                  }
252                  oneCount++;
253                } else {
254                  zeroOrOneCount++;
255                }
256              }
257              index++;
258            }
259    
260            // This the number of arguments we can satisfy
261            arguments = arguments.subList(0, index);
262    
263            // How many words we can consume for zeroOrOne and zeroOrMore
264            int toConsume = wordCount - oneCount;
265    
266            // Correct the zeroOrOneCount and adjust toConsume
267            zeroOrOneCount = Math.min(zeroOrOneCount, toConsume);
268            toConsume -= zeroOrOneCount;
269    
270            // The remaining
271            LinkedList<Event> events = new LinkedList<Event>();
272            for (ArgumentDescriptor<?> argument : arguments) {
273              int size;
274              switch (argument.getMultiplicity()) {
275                case SINGLE:
276                  if (argument.isRequired()) {
277                    size = 1;
278                  } else {
279                    if (zeroOrOneCount > 0) {
280                      zeroOrOneCount--;
281                      size = 1;
282                    } else {
283                      size = 0;
284                    }
285                  }
286                  break;
287                case MULTI:
288                  // We consume the remaining
289                  size = toConsume;
290                  toConsume = 0;
291                  break;
292                default:
293                  throw new AssertionError();
294              }
295    
296              // Now take care of the argument
297              if (size > 0) {
298                List<Token.Literal> values = new ArrayList<Token.Literal>(size);
299                while (size > 0) {
300                  Token t = req.tokenizer.next();
301                  if (t instanceof Token.Literal) {
302                    values.add(((Token.Literal)t));
303                    size--;
304                  }
305                }
306                events.addLast(new Event.Argument(argument, values));
307    
308                // Add the whitespace if needed
309                if (req.tokenizer.hasNext() && req.tokenizer.peek() instanceof Token.Whitespace) {
310                  events.addLast(new Event.Separator((Token.Whitespace) req.tokenizer.next()));
311                }
312              }
313            }
314    
315            //
316            events.addLast(new Event.Stop.Done.Arg(req.tokenizer.getIndex()));
317    
318            //
319            response.status = new Status.Done();
320            response.addAll(events);
321          }
322          return response;
323        }
324      }
325    
326      static class Done extends Status {
327        @Override
328        <T> Response process(Request req) {
329          throw new IllegalStateException();
330        }
331      }
332    
333      static class ReadingArg extends Status {
334    
335        /** . */
336        private final int index;
337    
338        ReadingArg() {
339          this(0);
340        }
341    
342        private ReadingArg(int index) {
343          this.index = index;
344        }
345    
346        ReadingArg next() {
347          return new ReadingArg(index + 1);
348        }
349    
350        @Override
351        <T> Response process(Request req) {
352          Token token = req.tokenizer.peek();
353          Response response = new Response();
354          if (token == null) {
355            response.add(new Event.Stop.Done.Arg(req.tokenizer.getIndex()));
356          } else if (token instanceof Token.Whitespace) {
357            response.add(new Event.Separator((Token.Whitespace) token));
358            req.tokenizer.next();
359          } else {
360            final Token.Literal literal = (Token.Literal)token;
361            List<? extends ArgumentDescriptor<?>> arguments = req.command.getArguments();
362            if (index < arguments.size()) {
363              ArgumentDescriptor<?> argument = arguments.get(index);
364              switch (argument.getMultiplicity()) {
365                case SINGLE:
366                  req.tokenizer.next();
367                  response.add(new Event.Argument(argument, Arrays.asList(literal)));
368                  response.status = next();
369                  break;
370                case MULTI:
371                  req.tokenizer.next();
372                  List<Token.Literal> values = new ArrayList<Token.Literal>();
373                  values.add(literal);
374                  while (req.tokenizer.hasNext()) {
375                    Token capture = req.tokenizer.next();
376                    if (capture instanceof Token.Literal) {
377                      values.add(((Token.Literal)capture));
378                    } else {
379                      if (req.tokenizer.hasNext()) {
380                        // Ok
381                      } else {
382                        req.tokenizer.pushBack();
383                        break;
384                      }
385                    }
386                  }
387                  response.add(new Event.Argument(argument, values));
388              }
389            } else {
390              response.add(new Event.Stop.Unresolved.TooManyArguments(literal));
391            }
392          }
393          return response;
394        }
395      }
396    }