001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2010–2011 ApexIdentity Inc. 015 * Portions Copyright 2011-2014 ForgeRock AS. 016 */ 017 018package org.forgerock.openig.regex; 019 020import java.io.BufferedReader; 021import java.io.Closeable; 022import java.io.IOException; 023import java.io.Reader; 024import java.util.Arrays; 025import java.util.Collection; 026import java.util.NoSuchElementException; 027import java.util.regex.Matcher; 028import java.util.regex.Pattern; 029 030/** 031 * Iterates through multiple regular expression matches within a character stream. 032 */ 033public class StreamPatternMatches implements Closeable { 034 035 /** The patterns to match. */ 036 private final Pattern[] patterns; 037 038 /** The character stream to search. */ 039 private BufferedReader input; 040 041 /** Should patterns be discarded after they yield a match. */ 042 private boolean discard; 043 044 /** The string pattern match iterator to search each line of input. */ 045 private StringPatternMatches matches = null; 046 047 /** 048 * Constructs a new stream pattern match iterator. If {@code discard} is {@code true}, 049 * then a pattern is discarded after it is first matched. 050 * 051 * @param input the character stream to match regular expression patterns against. 052 * @param patterns a collection of regular expression patterns to match. 053 * @param discard indicates patterns be discarded after they yield a match. 054 */ 055 public StreamPatternMatches(Reader input, Collection<Pattern> patterns, boolean discard) { 056 this.input = (BufferedReader) (input instanceof BufferedReader ? input : new BufferedReader(input)); 057 this.patterns = patterns.toArray(new Pattern[patterns.size()]); 058 this.discard = discard; 059 } 060 061 /** 062 * Returns the next match from the character stream. Matches are returned in the order 063 * they are encountered in the character stream, then by the order they are expressed in 064 * the supplied patterns collection. 065 * 066 * @return the next {@link java.util.regex.Matcher} from the character stream. 067 * @throws IOException if an I/O exception occurs. 068 * @throws NoSuchElementException if the reader has no more matches. 069 */ 070 public Matcher next() throws IOException { 071 readahead(); 072 if (matches == null) { 073 throw new NoSuchElementException(); 074 } 075 Matcher matcher = matches.next(); 076 if (discard) { 077 Pattern pattern = matcher.pattern(); 078 for (int n = 0; n < patterns.length; n++) { 079 if (patterns[n] == pattern) { 080 patterns[n] = null; 081 break; 082 } 083 } 084 } 085 return matcher; 086 } 087 088 /** 089 * Returns {@code true} if the character stream has more matches. 090 * 091 * @return {@code true} if the character stream has more matches. 092 * @throws IOException if an I/O exception occurs. 093 */ 094 public boolean hasNext() throws IOException { 095 readahead(); 096 return (matches != null && matches.hasNext()); 097 } 098 099 /** 100 * Closes this character stream, as well as the the reader it its iterating over. 101 * 102 * @throws IOException if an I/O exception occurs. 103 */ 104 public void close() throws IOException { 105 if (input != null) { 106 input.close(); 107 input = null; 108 } 109 } 110 111 /** 112 * Throws an {@link IOException} if the stream is closed. 113 */ 114 private void notClosed() throws IOException { 115 if (input == null) { 116 throw new IOException("stream is closed"); 117 } 118 } 119 120 private void readahead() throws IOException { 121 notClosed(); 122 while (matches == null || !matches.hasNext()) { 123 String line = input.readLine(); 124 if (line == null) { 125 break; 126 } 127 matches = new StringPatternMatches(line, Arrays.asList(patterns), discard); 128 } 129 } 130}