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    }