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-2015 ForgeRock AS.
016 */
017
018package org.forgerock.openig.regex;
019
020import java.io.IOException;
021import java.io.Reader;
022import java.util.AbstractMap;
023import java.util.HashMap;
024import java.util.Iterator;
025import java.util.LinkedHashMap;
026import java.util.Map;
027import java.util.Map.Entry;
028import java.util.regex.Matcher;
029import java.util.regex.Pattern;
030
031/**
032 * Extracts regular expression patterns and/or applied templates from character
033 * streams. If a pattern has a corresponding template, then the template will be
034 * applied to yield the extraction result. If no corresponding template exists,
035 * then the entire match is yielded verbatim.
036 *
037 * @see PatternTemplate
038 */
039public class StreamPatternExtractor {
040
041    private final Map<String, Pattern> patterns = new LinkedHashMap<>();
042
043    private final Map<String, PatternTemplate> templates = new HashMap<>();
044
045    /**
046     * Mapping of names to regular expression patterns to extract from the stream.
047     * @return the patterns' Map keyed with an identifier that may be reused in the templates' Map.
048     */
049    public Map<String, Pattern> getPatterns() {
050        return patterns;
051    }
052
053    /**
054     * Mapping of names to optional templates to use for yielding pattern match results.
055     * @return the templates' Map keyed with an identifier that has to match a pattern's key.
056     */
057    public Map<String, PatternTemplate> getTemplates() {
058        return templates;
059    }
060
061    /**
062     * Extracts regular expression patterns from a character streams. Returns a
063     * mapping of names to the results of pattern extraction (literal match or
064     * applied template).
065     * <p>
066     * Patterns are resolved lazily; only as much of the stream is read in order
067     * to satisfy a request for a specific key in the returned map.
068     * <p>
069     * <strong>Note:</strong> If an {@link IOException} is encountered when
070     * accessing the stream, the exception is caught and suppressed. This
071     * results in {@code null} values being returned for values not extracted
072     * before the exception occurred.
073     *
074     * @param reader the character stream .
075     * @return a mapping of names to pattern match results (literal match or
076     * applied template).
077     */
078    public Iterable<Map.Entry<String, String>> extract(final Reader reader) {
079        return new Iterable<Map.Entry<String, String>>() {
080            private final Map<String, String> values = new HashMap<>();
081            @SuppressWarnings("rawtypes")
082            private Map.Entry[] entries = patterns.entrySet().toArray(
083                    new Map.Entry[patterns.size()]);
084            private final StreamPatternMatches matches = new StreamPatternMatches(reader, patterns
085                    .values(), true);
086
087            @Override
088            public Iterator<Map.Entry<String, String>> iterator() {
089                final Iterator<String> iterator = patterns.keySet().iterator();
090
091                return new Iterator<Map.Entry<String, String>>() {
092                    @Override
093                    public boolean hasNext() {
094                        return iterator.hasNext();
095                    }
096
097                    @Override
098                    public Entry<String, String> next() {
099                        String key = iterator.next();
100                        String value = values.get(key);
101                        try {
102                            while (value == null && matches.hasNext()) {
103                                Matcher matcher = matches.next();
104                                Pattern pattern = matcher.pattern();
105                                for (int n = 0; n < entries.length; n++) {
106                                    if (entries[n] != null) {
107                                        String entryKey = (String) (entries[n].getKey());
108                                        Pattern entryPattern = (Pattern) (entries[n].getValue());
109                                        if (entryPattern == pattern) {
110                                            // identity equality for accurate (and quick) correlation
111                                            PatternTemplate t = templates.get(entryKey);
112                                            String v =
113                                                    (t != null ? t.applyTo(matcher) : matcher
114                                                            .group());
115                                            values.put(entryKey, value);
116                                            if (entryKey.equals(key)) {
117                                                // found the value we were looking for
118                                                value = v;
119                                            }
120                                            // used entry; deference for efficiency
121                                            entries[n] = null;
122                                        }
123                                    }
124                                }
125                            }
126                        } catch (IOException ioe) {
127                            // any failure to read stream yields null value in mapping
128                        }
129                        return new AbstractMap.SimpleImmutableEntry<>(key, value);
130                    }
131
132                    @Override
133                    public void remove() {
134                        throw new UnsupportedOperationException();
135                    }
136                };
137            }
138        };
139    }
140}