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.cmdline.matcher.impl;
021
022 import org.crsh.cmdline.ArgumentDescriptor;
023 import org.crsh.cmdline.ClassDescriptor;
024 import org.crsh.cmdline.CommandCompletion;
025 import org.crsh.cmdline.CommandDescriptor;
026 import org.crsh.cmdline.Delimiter;
027 import org.crsh.cmdline.MethodDescriptor;
028 import org.crsh.cmdline.OptionDescriptor;
029 import org.crsh.cmdline.binding.ClassFieldBinding;
030 import org.crsh.cmdline.binding.MethodArgumentBinding;
031 import org.crsh.cmdline.matcher.ArgumentMatch;
032 import org.crsh.cmdline.matcher.ClassMatch;
033 import org.crsh.cmdline.matcher.CmdCompletionException;
034 import org.crsh.cmdline.matcher.CommandMatch;
035 import org.crsh.cmdline.matcher.LiteralValue;
036 import org.crsh.cmdline.matcher.Matcher;
037 import org.crsh.cmdline.matcher.MethodMatch;
038 import org.crsh.cmdline.matcher.OptionMatch;
039 import org.crsh.cmdline.matcher.tokenizer.Token;
040 import org.crsh.cmdline.matcher.tokenizer.Tokenizer;
041 import org.crsh.cmdline.matcher.tokenizer.TokenizerImpl;
042 import org.crsh.cmdline.spi.Completer;
043
044 import java.util.ArrayList;
045 import java.util.Collections;
046 import java.util.Iterator;
047 import java.util.List;
048 import java.util.ListIterator;
049 import java.util.Map;
050
051 public class MatcherImpl<T> extends Matcher<T> {
052
053 /** . */
054 private final ClassDescriptor<T> descriptor;
055
056 /** . */
057 private final String mainName;
058
059 public MatcherImpl(ClassDescriptor<T> descriptor) {
060 this(null, descriptor);
061 }
062
063 public MatcherImpl(String mainName, ClassDescriptor<T> descriptor) {
064 this.mainName = mainName;
065 this.descriptor = descriptor;
066 }
067
068 private List<LiteralValue> bilto(List<? extends Token.Literal> literals) {
069 List<LiteralValue> values = new ArrayList<LiteralValue>(literals.size());
070 for (Token.Literal literal : literals) {
071 values.add(new LiteralValue(literal.getRaw(), literal.getValue()));
072 }
073 return values;
074 }
075
076 public CommandMatch<T, ?, ?> match(final String name, Map<String, ?> options, List<?> arguments) {
077
078 class TokenizerImpl extends ArrayList<Token> {
079 int last() {
080 return size() > 0 ? get(size() - 1).getTo() : 0;
081 }
082 @Override
083 public boolean add(Token token) {
084 if (size() > 0) {
085 super.add(new Token.Whitespace(last(), " "));
086 }
087 return super.add(token);
088 }
089
090 public void addOption(String name) {
091 if (name.length() == 1) {
092 add(new Token.Literal.Option.Short(last(), "-" + name));
093 } else {
094 add(new Token.Literal.Option.Long(last(), "--" + name));
095 }
096 }
097 }
098 final TokenizerImpl t = new TokenizerImpl();
099
100 // Add name
101 if (name != null && name.length() > 0) {
102 t.add(new Token.Literal.Word(t.last(), name));
103 }
104
105 // Add options
106 for (Map.Entry<String, ?> option : options.entrySet()) {
107 if (option.getValue() instanceof Boolean) {
108 if ((Boolean)option.getValue()) {
109 t.addOption(option.getKey());
110 }
111 } else {
112 t.addOption(option.getKey());
113 t.add(new Token.Literal.Word(t.last(), option.getValue().toString()));
114 }
115 }
116
117 //
118 for (Object argument : arguments) {
119 t.add(new Token.Literal.Word(t.last(), argument.toString()));
120 }
121
122 //
123 Tokenizer tokenizer = new Tokenizer() {
124
125 Iterator<Token> i = t.iterator();
126
127 @Override
128 protected Token parse() {
129 return i.hasNext() ? i.next() : null;
130 }
131
132 @Override
133 public Delimiter getDelimiter() {
134 return Delimiter.EMPTY;
135 }
136 };
137
138 //
139 return match(tokenizer);
140 }
141
142 @Override
143 public CommandMatch<T, ?, ?> match(String s) {
144 return match(new TokenizerImpl(s));
145 }
146
147 private CommandMatch<T, ?, ?> match(Tokenizer tokenizer) {
148
149 //
150 List<OptionMatch<ClassFieldBinding>> classOptions = new ArrayList<OptionMatch<ClassFieldBinding>>();
151 List<ArgumentMatch<ClassFieldBinding>> classArguments = new ArrayList<ArgumentMatch<ClassFieldBinding>>();
152 List<OptionMatch<MethodArgumentBinding>> methodOptions = new ArrayList<OptionMatch<MethodArgumentBinding>>();
153 List<ArgumentMatch<MethodArgumentBinding>> methodArguments = new ArrayList<ArgumentMatch<MethodArgumentBinding>>();
154 MethodDescriptor<T> method = null;
155
156 Parser<T> parser = new Parser<T>(tokenizer, descriptor, mainName, Mode.INVOKE);
157
158
159 //
160 while (true) {
161 Event event = parser.next();
162 if (event instanceof Event.Separator) {
163 //
164 } else if (event instanceof Event.Stop) {
165 // We are done
166 // Check error status and react to it maybe
167 // We try to match the main if none was found
168 if (method == null) {
169 if (mainName != null) {
170 method = descriptor.getMethod(mainName);
171 }
172 }
173 break;
174 } else if (event instanceof Event.Option) {
175 Event.Option optionEvent = (Event.Option)event;
176 OptionDescriptor<?> desc = optionEvent.getDescriptor();
177 List options;
178 if (desc.getOwner() instanceof ClassDescriptor<?>) {
179 options = classOptions;
180 } else {
181 options = methodOptions;
182 }
183 boolean done = false;
184 for (ListIterator<OptionMatch> i = options.listIterator();i.hasNext();) {
185 OptionMatch om = i.next();
186 if (om.getParameter().equals(desc)) {
187 List<LiteralValue> v = new ArrayList<LiteralValue>(om.getValues());
188 v.addAll(bilto(optionEvent.getValues()));
189 List<String> names = new ArrayList<String>(om.getNames());
190 names.add(optionEvent.getToken().getName());
191 i.set(new OptionMatch(desc, names, v));
192 done = true;
193 break;
194 }
195 }
196 if (!done) {
197 options.add(new OptionMatch(desc, optionEvent.getToken().getName(), bilto(optionEvent.getValues())));
198 }
199 } else if (event instanceof Event.Method) {
200 method = (MethodDescriptor<T>)((Event.Method)event).getDescriptor();
201 } else if (event instanceof Event.Argument) {
202 Event.Argument argumentEvent = (Event.Argument)event;
203 List<Token.Literal> values = argumentEvent.getValues();
204 ArgumentMatch match;
205 if (values.size() > 0) {
206 match = new ArgumentMatch(
207 argumentEvent.getDescriptor(),
208 argumentEvent.getFrom(),
209 argumentEvent.getTo(),
210 bilto(argumentEvent.getValues())
211 );
212 if (argumentEvent.getDescriptor().getOwner() instanceof ClassDescriptor<?>) {
213 classArguments.add(match);
214 } else {
215 methodArguments.add(match);
216 }
217 }
218 }
219 }
220
221 //
222 StringBuilder rest = new StringBuilder();
223 while (tokenizer.hasNext()) {
224 Token token = tokenizer.next();
225 rest.append(token.getRaw());
226 }
227
228 //
229 ClassMatch classMatch = new ClassMatch(descriptor, classOptions, classArguments, rest.toString());
230 if (method != null) {
231 return new MethodMatch(classMatch, method, false, methodOptions, methodArguments, rest.toString());
232 } else {
233 return classMatch;
234 }
235 }
236
237 private Completion argument(MethodDescriptor<?> method, Completer completer) {
238 List<? extends ArgumentDescriptor<?>> arguments = method.getArguments();
239 if (arguments.isEmpty()) {
240 return new EmptyCompletion();
241 } else {
242 ArgumentDescriptor<?> argument = arguments.get(0);
243 return new ParameterCompletion("", Delimiter.EMPTY, argument, completer);
244 }
245 }
246
247 @Override
248 public CommandCompletion complete(Completer completer, String s) throws CmdCompletionException {
249 return getCompletion(completer, s).complete();
250 }
251
252 private Completion getCompletion(Completer completer, String s) throws CmdCompletionException {
253
254 Tokenizer tokenizer = new TokenizerImpl(s);
255 Parser<T> parser = new Parser<T>(tokenizer, descriptor, mainName, Mode.COMPLETE);
256
257 // Last non separator event
258 Event last = null;
259 Event.Separator separator = null;
260 MethodDescriptor<?> method = null;
261 Event.Stop stop;
262
263 //
264 while (true) {
265 Event event = parser.next();
266 if (event instanceof Event.Separator) {
267 separator = (Event.Separator)event;
268 } else if (event instanceof Event.Stop) {
269 stop = (Event.Stop)event;
270 break;
271 } else if (event instanceof Event.Option) {
272 last = event;
273 separator = null;
274 } else if (event instanceof Event.Method) {
275 method = ((Event.Method)event).getDescriptor();
276 last = event;
277 separator = null;
278 } else if (event instanceof Event.Argument) {
279 last = event;
280 separator = null;
281 }/* else if (event instanceof Event.DoubleDash) {
282 last = event;
283 separator = null;
284 }*/
285 }
286
287 //
288 if (stop instanceof Event.Stop.Unresolved.NoSuchOption) {
289 Event.Stop.Unresolved.NoSuchOption nso = (Event.Stop.Unresolved.NoSuchOption)stop;
290 return new OptionCompletion<T>(method != null ? (CommandDescriptor<T, ?>)method : descriptor, nso.getToken());
291 } else if (stop instanceof Event.Stop.Unresolved) {
292 if (stop instanceof Event.Stop.Unresolved.TooManyArguments) {
293 if (method == null) {
294 Event.Stop.Unresolved.TooManyArguments tma = (Event.Stop.Unresolved.TooManyArguments)stop;
295 return new MethodCompletion<T>(descriptor, mainName, s.substring(stop.getIndex()), parser.getDelimiter());
296 } else {
297 return new EmptyCompletion();
298 }
299 } else {
300 return new EmptyCompletion();
301 }
302 } else if (stop instanceof Event.Stop.Done.Option) {
303 // to use ?
304 } else if (stop instanceof Event.Stop.Done.Arg) {
305 // to use ?
306 }
307
308 //
309 if (last == null) {
310 if (method == null) {
311 if (descriptor.getSubordinates().keySet().equals(Collections.singleton(mainName))) {
312 method = descriptor.getMethod(mainName);
313 List<ArgumentDescriptor<MethodArgumentBinding>> args = method.getArguments();
314 if (args.size() > 0) {
315 return new ParameterCompletion("", Delimiter.EMPTY, args.get(0), completer);
316 } else {
317 return new EmptyCompletion();
318 }
319 } else {
320 return new MethodCompletion<T>(descriptor, mainName, s.substring(stop.getIndex()), Delimiter.EMPTY);
321 }
322 } else {
323 return new EmptyCompletion();
324 }
325 }
326
327 //
328 /*if (last instanceof Event.DoubleDash) {
329 Event.DoubleDash dd = (Event.DoubleDash)last;
330 return new OptionCompletion<T>(method != null ? (CommandDescriptor<T, ?>)method : descriptor, dd.token);
331 } else*/
332 if (last instanceof Event.Option) {
333 Event.Option optionEvent = (Event.Option)last;
334 List<Token.Literal.Word> values = optionEvent.getValues();
335 OptionDescriptor<?> option = optionEvent.getDescriptor();
336 if (separator == null) {
337 if (values.size() == 0) {
338 return new SpaceCompletion();
339 } else if (values.size() <= option.getArity()) {
340 Token.Literal.Word word = optionEvent.peekLast();
341 return new ParameterCompletion(word.getValue(), parser.getDelimiter(), option, completer);
342 } else {
343 return new EmptyCompletion();
344 }
345 } else {
346 if (values.size() < option.getArity()) {
347 return new ParameterCompletion("", Delimiter.EMPTY, option, completer);
348 } else {
349 if (method == null) {
350 return new MethodCompletion<T>(descriptor, mainName, s.substring(stop.getIndex()), Delimiter.EMPTY);
351 } else {
352 return argument(method, completer);
353 }
354 }
355 }
356 } else if (last instanceof Event.Argument) {
357 Event.Argument eventArgument = (Event.Argument)last;
358 ArgumentDescriptor<?> argument = eventArgument.getDescriptor();
359 if (separator != null) {
360 switch (argument.getMultiplicity()) {
361 case SINGLE:
362 List<? extends ArgumentDescriptor<?>> arguments = argument.getOwner().getArguments();
363 int index = arguments.indexOf(argument) + 1;
364 if (index < arguments.size()) {
365 ArgumentDescriptor<?> nextArg = arguments.get(index);
366 return new ParameterCompletion("", Delimiter.EMPTY, nextArg, completer);
367 } else {
368 return new EmptyCompletion();
369 }
370 case MULTI:
371 return new ParameterCompletion("", Delimiter.EMPTY, argument, completer);
372 default:
373 throw new AssertionError();
374 }
375 } else {
376 Token.Literal value = eventArgument.peekLast();
377 return new ParameterCompletion(value.getValue(), parser.getDelimiter(), argument, completer);
378 }
379 } else if (last instanceof Event.Method) {
380 if (separator != null) {
381 return argument(method, completer);
382 } else {
383 return new SpaceCompletion();
384 }
385 } else {
386 throw new AssertionError();
387 }
388 }
389 }