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 }