001    /* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002     *
003     * Copyright (c) 2013 Edugility LLC.
004     *
005     * Permission is hereby granted, free of charge, to any person
006     * obtaining a copy of this software and associated documentation
007     * files (the "Software"), to deal in the Software without
008     * restriction, including without limitation the rights to use, copy,
009     * modify, merge, publish, distribute, sublicense and/or sell copies
010     * of the Software, and to permit persons to whom the Software is
011     * furnished to do so, subject to the following conditions:
012     *
013     * The above copyright notice and this permission notice shall be
014     * included in all copies or substantial portions of the Software.
015     *
016     * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
017     * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
018     * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
019     * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
020     * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
021     * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
022     * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
023     * DEALINGS IN THE SOFTWARE.
024     *
025     * The original copy of this license is available at
026     * http://www.opensource.org/license/mit-license.html.
027     */
028    package com.edugility.objexj;
029    
030    import java.util.Collections;
031    import java.util.List;
032    import java.util.Map;
033    
034    import com.edugility.objexj.engine.Engine;
035    import com.edugility.objexj.engine.MatchResult;
036    import com.edugility.objexj.engine.Program;
037    
038    /**
039     * An object that matches a {@link Pattern} against a {@link List} of
040     * items (the <em>input</em>).
041     *
042     * <p>{@link Matcher}s are produced by the {@link
043     * Pattern#matcher(List)} method.  Once a {@link Matcher} has been
044     * produced it stores results about any match that might have
045     * occurred.</p>
046     *
047     * <p>{@link Matcher}s are not safe for use by multiple Java
048     * {@linkplain java.lang.Thread threads}.</p>
049     *
050     * @param <T> the kind of {@link Object} any input consists of
051     *
052     * @author <a href="http://about.me/lairdnelson"
053     * target="_parent">Laird Nelson</a>
054     *
055     * @see Pattern
056     *
057     * @see Pattern#matcher(List)
058     *
059     * @see #matches()
060     *
061     * @see #lookingAt()
062     *
063     * @see <a href="../../../../syntax.html" target="_parent">Syntax
064     * Guide</a>
065     */
066    public class Matcher<T> {
067    
068      /**
069       * The {@link Pattern} that this {@link Matcher} is going to apply.
070       * This field is never {@code null}.
071       *
072       * @see Pattern
073       *
074       * @see #getPattern()
075       *
076       * @see #Matcher(Pattern, List)
077       */
078      private final Pattern<T> pattern;
079    
080      /**
081       * The {@link List} of items against which a match will be attempted
082       * (the <em>input</em>).  This field may be {@code null}.
083       *
084       * @see #getInput()
085       */
086      private List<? extends T> input;
087    
088      /**
089       * A {@link MatchResult} that contains the state of the last match
090       * attempt.  This field may be {@code null}.
091       */
092      private transient MatchResult<? extends T> matchResult;
093    
094      /**
095       * Creates a {@link Matcher} with the supplied {@link Pattern} and
096       * input.
097       *
098       * @param pattern the {@link Pattern} to apply; must not be {@code
099       * null}
100       *
101       * @param input a possibly {@code null} {@link List} of items to
102       * match the supplied {@link Pattern} against
103       * 
104       * @exception IllegalArgumentException if {@code pattern} is {@code
105       * null}
106       */
107      Matcher(final Pattern<T> pattern, final List<? extends T> input) {
108        super();
109        if (pattern == null) {
110          throw new IllegalArgumentException("pattern", new NullPointerException("pattern"));
111        }
112        this.pattern = pattern;
113        this.input = input;
114      }
115    
116      /**
117       * Returns {@code true} if this {@link Matcher} matches the <em>entire</em>
118       * input against its {@linkplain #getPattern() affiliated
119       * <tt>Pattern</tt>}.
120       *
121       * <p>In many cases the {@link #lookingAt()} method is more
122       * suitable.</p>
123       *
124       * @return {@code true} if this {@link Matcher} matches the
125       * <em>entire</em> input against its {@linkplain #getPattern()
126       * affiliated <tt>Pattern</tt>}; {@code false} otherwise
127       *
128       * @see #lookingAt()
129       */
130      public final boolean matches() {
131        final MatchResult<?> matchResult = this.getMatchResult();
132        return matchResult != null && matchResult.matches();
133      }
134    
135      /**
136       * Returns {@code true} if a <em>prefix</em> of this {@link
137       * Matcher}'s {@linkplain #getInput() input} matches this {@link
138       * Matcher}'s {@linkplain #getPattern() affiliated
139       * <tt>Pattern</tt>}.
140       *
141       * <p>Sometimes the {@link #matches()} method is more suitable.</p>
142       *
143       * @return {@code true} if a <em>prefix</em> of this {@link
144       * Matcher}'s {@linkplain #getInput() input} matches this {@link
145       * Matcher}'s {@linkplain #getPattern() affiliated
146       * <tt>Pattern</tt>}; {@code false} otherwise
147       *
148       * @see #matches()
149       */
150      public final boolean lookingAt() {
151        final MatchResult<?> matchResult = this.getMatchResult();
152        return matchResult != null && matchResult.lookingAt();
153      }
154    
155      /**
156       * Returns the total number of <em>capture groups</em> matched by
157       * this {@link Matcher}.  Any successful match will cause this
158       * method to return an {@code int} greater than or equal to {@code
159       * 1}.  Group indices are numbered starting with {@code 0}.
160       *
161       * @return the total number of capture groups matched by this {@link
162       * Matcher}; never less than {@code 0}
163       *
164       * @see <a href="../../../../syntax.html" target="_parent">Syntax
165       * Guide</a>
166       */
167      public final int groupCount() {
168        final MatchResult<?> matchResult = this.getMatchResult();
169        final int result;
170        if (matchResult == null) {
171          result = 0;
172        } else {
173          result = matchResult.getGroupCount();
174        }
175        return result;
176      }
177    
178      /**
179       * Returns the <em>capture group</em> matched by the last match
180       * indexed under the supplied zero-based index, or {@code null} if
181       * no such capture group was matched.
182       *
183       * @param index a zero-based number identifying a capture group; may
184       * be any number
185       *
186       * @return a {@link List} of items (a subset of the {@linkplain
187       * #getInput() input}), or {@code null} if no such capture group was
188       * ever identified
189       *
190       * @see <a href="../../../../syntax.html" target="_parent">Syntax
191       * Guide</a>
192       */
193      public final List<? extends T> group(final int index) {
194        final MatchResult<? extends T> matchResult = this.getMatchResult();
195        final List<? extends T> result;
196        if (matchResult == null) {
197          result = null;
198        } else {
199          result = matchResult.getGroup(Integer.valueOf(index));
200        }
201        return result;
202      }
203    
204      /**
205       * Returns a non-{@code null}, {@linkplain
206       * Collections#unmodifiableMap(Map) unmodifiable} {@link Map}
207       * representing the variables set by this {@link Matcher} during its
208       * execution.  Variables may be set from within a {@link Pattern}'s
209       * parsed expression.
210       *
211       * <p>This method never returns {@code null}.</p>
212       *
213       * @return a non-{@code null} {@linkplain
214       * Collections#unmodifiableMap(Map) unmodifiable} {@link Map} of
215       * variables
216       *
217       * @see <a href="../../../../syntax.html" target="_parent">Syntax
218       * Guide</a>
219       */
220      public final Map<?, ?> getVariables() {
221        Map<?, ?> result = null;
222        final MatchResult<?> matchResult = this.getMatchResult();
223        if (matchResult == null) {
224          result = Collections.emptyMap();
225        } else {
226          result = matchResult.getVariables();
227        }
228        if (result == null || result.isEmpty()) {
229          result = Collections.emptyMap();
230        } else {
231          result = Collections.unmodifiableMap(result);
232        }
233        return result;
234      }
235    
236      /**
237       * Returns the value of the <em>variable</em> indexed under the
238       * supplied key, or {@code null} if no such value was ever
239       * established.  Variables may be set from within a {@link
240       * Pattern}'s parsed expression.
241       *
242       * @param key the name of the variable; may be {@code null} but if
243       * so then {@code null} will be returned
244       *
245       * @return the value of the variable, or {@code null}
246       *
247       * @see <a href="../../../../syntax.html" target="_parent">Syntax
248       * Guide</a>
249       */
250      public final Object get(final Object key) {
251        final Map<?, ?> variables = this.getVariables();
252        final Object result;
253        if (variables == null) {
254          result = null;
255        } else {
256          result = variables.get(key);
257        }
258        return result;
259      }
260    
261      /**
262       * Returns the {@link Pattern} with which this {@link Matcher} is
263       * currently affiliated.  This method never returns {@code null}.
264       *
265       * @return a non-{@code null} {@link Pattern}
266       */
267      public final Pattern<T> getPattern() {
268        return this.pattern;
269      }
270    
271      /**
272       * Returns the input with which this {@link Matcher} is currently
273       * affiliated.  This method may return {@code null}.
274       *
275       * @return the input, or {@code null}
276       */
277      public final List<? extends T> getInput() {
278        return this.input;
279      }
280    
281      /**
282       * Lazily initializes this {@link Matcher}'s associated {@link
283       * MatchResult}, if necessary, and returns it.
284       *
285       * @return a {@link MatchResult}, or {@code null} if there was no
286       * match
287       */
288      private final MatchResult<? extends T> getMatchResult() {
289        if (this.matchResult == null) {
290          final Pattern<T> pattern = this.getPattern();
291          assert pattern != null;
292          final Program<T> program = pattern.getProgram();
293          assert program != null;
294          final Engine<T> engine = pattern.getEngine();
295          assert engine != null;
296          this.matchResult = engine.run(program, this.input);
297        }
298        return this.matchResult;
299      }
300    
301    }