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
020package org.crsh.cli.impl.invocation;
021
022import org.crsh.cli.descriptor.CommandDescriptor;
023import org.crsh.cli.impl.SyntaxException;
024import org.crsh.cli.impl.LiteralValue;
025import org.crsh.cli.descriptor.OptionDescriptor;
026import org.crsh.cli.impl.tokenizer.Token;
027import org.crsh.cli.impl.tokenizer.Tokenizer;
028import org.crsh.cli.impl.tokenizer.TokenizerImpl;
029import org.crsh.cli.impl.parser.Event;
030import org.crsh.cli.impl.parser.Mode;
031import org.crsh.cli.impl.parser.Parser;
032
033import java.util.ArrayList;
034import java.util.Collections;
035import java.util.Iterator;
036import java.util.List;
037import java.util.Map;
038
039/** @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a> */
040public class InvocationMatcher<T> {
041
042  /** . */
043  private final CommandDescriptor<T> descriptor;
044
045  /** . */
046  private Iterable<Token> tokens;
047
048  public InvocationMatcher(CommandDescriptor<T> descriptor) {
049    this(descriptor, Collections.<Token>emptyList());
050  }
051
052  private InvocationMatcher(CommandDescriptor<T> descriptor, Iterable<Token> tokens) {
053    this.descriptor = descriptor;
054    this.tokens = tokens;
055  }
056
057  public InvocationMatcher<T> subordinate(String name) throws SyntaxException {
058    TokenList tokens = new TokenList(this.tokens);
059    if (name != null && name.length() > 0) {
060      tokens.add(new Token.Literal.Word(tokens.last(), name));
061    }
062    return new InvocationMatcher<T>(descriptor, tokens);
063  }
064
065  public InvocationMatcher<T> option(String optionName, List<?> optionValue) throws SyntaxException {
066    return options(Collections.<String, List<?>>singletonMap(optionName, optionValue));
067  }
068
069  public InvocationMatcher<T> options(Map<String, List<?>> options) throws SyntaxException {
070    TokenList tokens = new TokenList(this.tokens);
071    for (Map.Entry<String, List<?>> option : options.entrySet()) {
072      tokens.addOption(option.getKey(), option.getValue());
073    }
074    return new InvocationMatcher<T>(descriptor, tokens);
075  }
076
077  public InvocationMatch<T> arguments(List<?> arguments) throws SyntaxException {
078    TokenList tokens = new TokenList(this.tokens);
079    for (Object argument : arguments) {
080      tokens.add(new Token.Literal.Word(tokens.last(), argument.toString()));
081    }
082    return match(tokens);
083  }
084
085  public InvocationMatch<T> parse(String s) throws SyntaxException {
086    ArrayList<Token> tokens = new ArrayList<Token>();
087    for (Token token : this.tokens) {
088      tokens.add(token);
089    }
090    for (Iterator<Token> i = new TokenizerImpl(s);i.hasNext();) {
091      tokens.add(i.next());
092    }
093    return match(tokens);
094  }
095
096  private InvocationMatch<T> match(final Iterable<Token> tokens) throws SyntaxException {
097    Tokenizer tokenizer = new Tokenizer() {
098
099      /** . */
100      Iterator<Token> i = tokens.iterator();
101
102      @Override
103      protected Token parse() {
104        return i.hasNext() ? i.next() : null;
105      }
106    };
107    return match(tokenizer);
108  }
109
110  private InvocationMatch<T> match(Tokenizer tokenizer) throws SyntaxException {
111
112    //
113    Parser<T> parser = new Parser<T>(tokenizer, descriptor, Mode.INVOKE);
114    InvocationMatch<T> current = new InvocationMatch<T>(descriptor);
115
116    //
117    while (true) {
118      Event event = parser.next();
119      if (event instanceof Event.Separator) {
120        //
121      } else if (event instanceof Event.Stop) {
122        break;
123      } else if (event instanceof Event.Option) {
124        Event.Option optionEvent = (Event.Option)event;
125        OptionDescriptor desc = optionEvent.getParameter();
126        Iterable<OptionMatch> options = current.options();
127        OptionMatch option = null;
128        for (OptionMatch om : options) {
129          if (om.getParameter().equals(desc)) {
130            List<LiteralValue> v = new ArrayList<LiteralValue>(om.getValues());
131            v.addAll(bilto(optionEvent.getValues()));
132            List<String> names = new ArrayList<String>(om.getNames());
133            names.add(optionEvent.getToken().getName());
134            option = new OptionMatch(desc, names, v);
135            break;
136          }
137        }
138        if (option == null) {
139          option = new OptionMatch(desc, optionEvent.getToken().getName(), bilto(optionEvent.getValues()));
140        }
141        current.option(option);
142      } else if (event instanceof Event.Subordinate) {
143        current = current.subordinate(((Event.Subordinate)event).getDescriptor().getName());
144      } else if (event instanceof Event.Argument) {
145        Event.Argument argumentEvent = (Event.Argument)event;
146        List<Token.Literal> values = argumentEvent.getValues();
147        ArgumentMatch match;
148        if (values.size() > 0) {
149          match = new ArgumentMatch(
150              argumentEvent.getParameter(),
151              argumentEvent.getFrom(),
152              argumentEvent.getTo(),
153              bilto(argumentEvent.getValues())
154          );
155          if (argumentEvent.getCommand() == current.getDescriptor()) {
156            current.argument(match);
157          } else {
158            throw new AssertionError();
159          }
160        }
161      }
162    }
163
164    //
165    StringBuilder rest = new StringBuilder();
166    while (tokenizer.hasNext()) {
167      Token token = tokenizer.next();
168      rest.append(token.getRaw());
169    }
170    current.setRest(rest.toString());
171
172    //
173    return current;
174  }
175
176  private List<LiteralValue> bilto(List<? extends Token.Literal> literals) {
177    List<LiteralValue> values = new ArrayList<LiteralValue>(literals.size());
178    for (Token.Literal literal : literals) {
179      values.add(new LiteralValue(literal.getRaw(), literal.getValue()));
180    }
181    return values;
182  }
183}