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.cli.impl.invocation;
021
022 import org.crsh.cli.impl.descriptor.CommandDescriptorImpl;
023 import org.crsh.cli.SyntaxException;
024 import org.crsh.cli.impl.Delimiter;
025 import org.crsh.cli.impl.LiteralValue;
026 import org.crsh.cli.descriptor.OptionDescriptor;
027 import org.crsh.cli.impl.tokenizer.Token;
028 import org.crsh.cli.impl.tokenizer.Tokenizer;
029 import org.crsh.cli.impl.tokenizer.TokenizerImpl;
030 import org.crsh.cli.impl.parser.Event;
031 import org.crsh.cli.impl.parser.Mode;
032 import org.crsh.cli.impl.parser.Parser;
033
034 import java.util.ArrayList;
035 import java.util.Iterator;
036 import java.util.List;
037 import java.util.Map;
038
039 /** @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a> */
040 public 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 }