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}