001/*-----------------------------------------------------------------------+
002 | org.conqat.engine.index.incubator
003 |                                                                       |
004   $Id$            
005 |                                                                       |
006 | Copyright (c)  2009-2013 CQSE GmbH                                 |
007 +-----------------------------------------------------------------------*/
008package eu.cqse.check.framework.util.tokens;
009
010import java.util.ArrayList;
011import java.util.List;
012
013import org.conqat.lib.commons.collections.CollectionUtils;
014import org.conqat.lib.commons.collections.ListMap;
015import org.conqat.lib.commons.string.StringUtils;
016
017import eu.cqse.check.framework.scanner.IToken;
018import eu.cqse.check.framework.shallowparser.TokenStreamTextUtils;
019
020/**
021 * A single match created by a {@link TokenPattern}. Keeps track of any groups
022 * that have been matched by parts of the pattern. A group is identified by an
023 * arbitrary integer. See also {@link TokenPattern#group(int)}.
024 * 
025 * Multiple segments of the tokenStream can be associated with the same group.
026 * In this case, {@link #getMatchGroup(int)} will return multiple
027 * {@link MatchGroupElement}s for the group.
028 */
029public class TokenPatternMatch {
030
031        /** The token stream against which the matches were constructed. */
032        private final List<IToken> tokenStream;
033
034        /**
035         * Maps from the group index to the ranges that are included in the group.
036         */
037        private final ListMap<Integer, Range> groups = new ListMap<>();
038
039        /** Constructor. */
040        public TokenPatternMatch(List<IToken> tokenStream) {
041                this.tokenStream = tokenStream;
042        }
043
044        /**
045         * Appends the given tokens to the group with the given index if the given
046         * range contains at least one token.
047         */
048        public void appendToGroup(Integer groupIndex, int inclusiveStartIndex, int exclusiveEndIndex) {
049                if (inclusiveStartIndex < exclusiveEndIndex) {
050                        groups.add(groupIndex, new Range(inclusiveStartIndex, exclusiveEndIndex));
051                }
052        }
053
054        /**
055         * Returns the text of the tokens in the given group. All
056         * {@link MatchGroupElement}s for the given group will be concatenated.
057         */
058        public List<String> groupTexts(int groupIndex) {
059                return TokenStreamTextUtils.getTokenTexts(groupTokens(groupIndex));
060        }
061
062        /**
063         * Returns the indices into the token stream in the given group or an empty
064         * list. All {@link MatchGroupElement}s for the given group will be
065         * concatenated.
066         */
067        public List<Integer> groupIndices(int groupIndex) {
068                List<Range> ranges = groups.getCollection(groupIndex);
069                if (ranges == null) {
070                        return CollectionUtils.emptyList();
071                }
072                List<Integer> indices = new ArrayList<>();
073                for (Range range : ranges) {
074                        for (int i = range.inclusiveStartIndex; i < range.exclusiveEndIndex; i++) {
075                                indices.add(i);
076                        }
077                }
078                return indices;
079        }
080
081        /**
082         * Returns the tokens in the given group or an empty list. All
083         * {@link MatchGroupElement}s for the given group will be concatenated.
084         */
085        public List<IToken> groupTokens(int groupIndex) {
086                List<IToken> tokens = new ArrayList<IToken>();
087                for (Integer index : groupIndices(groupIndex)) {
088                        tokens.add(tokenStream.get(index));
089                }
090                return tokens;
091        }
092
093        /**
094         * Returns the TokenStream parts matched in this group. Each part is
095         * represented by a {@link MatchGroupElement} and can contain multiple
096         * {@link IToken}s, depending on the used pattern.
097         */
098        public List<MatchGroupElement> getMatchGroup(int groupIndex) {
099                List<Range> ranges = groups.getCollection(groupIndex);
100                if (ranges == null) {
101                        return CollectionUtils.emptyList();
102                }
103                List<MatchGroupElement> groupElements = new ArrayList<>();
104                for (Range range : ranges) {
105                        groupElements.add(
106                                        new MatchGroupElement(tokenStream.subList(range.inclusiveStartIndex, range.exclusiveEndIndex)));
107                }
108                return groupElements;
109        }
110
111        /**
112         * Returns the concatenated text of the tokens in the given group. All
113         * {@link MatchGroupElement}s for the given group will be concatenated.If
114         * the group was not matched, returns the empty string.
115         */
116        public String groupString(int groupIndex) {
117                return StringUtils.concat(groupTexts(groupIndex), StringUtils.EMPTY_STRING);
118        }
119
120        /**
121         * Returns <code>true</code> if the match contains tokens in the given
122         * group.
123         */
124        public boolean hasGroup(int groupIndex) {
125                return groups.containsCollection(groupIndex);
126        }
127
128        /** Merges the group information from given match into this match. */
129        public void mergeFrom(TokenPatternMatch other) {
130                groups.addAll(other.groups);
131        }
132
133        /**
134         * Returns a list of all group strings in all matches of the group with the
135         * given index.
136         */
137        public static List<String> getAllStrings(List<TokenPatternMatch> matches, int groupIndex) {
138                List<String> strings = new ArrayList<String>();
139                for (TokenPatternMatch match : matches) {
140                        strings.add(match.groupString(groupIndex));
141                }
142                return strings;
143        }
144
145        /**
146         * Returns a list of all group tokens in all matches of the group with the
147         * given index.
148         */
149        public static List<IToken> getAllTokens(List<TokenPatternMatch> matches, int groupIndex) {
150                List<IToken> tokens = new ArrayList<IToken>();
151                for (TokenPatternMatch match : matches) {
152                        tokens.addAll(match.groupTokens(groupIndex));
153                }
154                return tokens;
155        }
156
157        /** A range of indices into a token stream. */
158        private static class Range {
159
160                /** The inclusive start index into the token stream. */
161                private final int inclusiveStartIndex;
162
163                /** The exclusive end index into the token stream. */
164                private final int exclusiveEndIndex;
165
166                /** Constructor. */
167                public Range(int inclusiveStartIndex, int exclusiveEndIndex) {
168                        this.inclusiveStartIndex = inclusiveStartIndex;
169                        this.exclusiveEndIndex = exclusiveEndIndex;
170                }
171
172        }
173}