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