/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.cpd;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
public class MatchAlgorithm {
private static final int MOD = 37;
private int lastHash;
private int lastMod = 1;
private List<Match> matches;
private Map<String, SourceCode> source;
private Tokens tokens;
private List<TokenEntry> code;
private CPDListener cpdListener;
private int min;
public MatchAlgorithm(Map<String, SourceCode> sourceCode, Tokens tokens, int min) {
this(sourceCode, tokens, min, new CPDNullListener());
}
public MatchAlgorithm(Map<String, SourceCode> sourceCode, Tokens tokens, int min, CPDListener listener) {
this.source = sourceCode;
this.tokens = tokens;
this.code = tokens.getTokens();
this.min = min;
this.cpdListener = listener;
for (int i = 0; i < min; i++) {
lastMod *= MOD;
}
}
public void setListener(CPDListener listener) {
this.cpdListener = listener;
}
public Iterator<Match> matches() {
return matches.iterator();
}
public TokenEntry tokenAt(int offset, TokenEntry m) {
return code.get(offset + m.getIndex());
}
public int getMinimumTileSize() {
return this.min;
}
public void findMatches() {
cpdListener.phaseUpdate(CPDListener.HASH);
Map<TokenEntry, Object> markGroups = hash();
cpdListener.phaseUpdate(CPDListener.MATCH);
MatchCollector matchCollector = new MatchCollector(this);
for (Iterator<Object> i = markGroups.values().iterator(); i.hasNext();) {
Object o = i.next();
if (o instanceof List) {
@SuppressWarnings("unchecked")
List<TokenEntry> l = (List<TokenEntry>) o;
Collections.reverse(l);
matchCollector.collect(l);
}
i.remove();
}
cpdListener.phaseUpdate(CPDListener.GROUPING);
matches = matchCollector.getMatches();
for (Match match : matches) {
for (Mark mark : match) {
TokenEntry token = mark.getToken();
int lineCount = tokens.getLineCount(token, match);
mark.setLineCount(lineCount);
SourceCode sourceCode = source.get(token.getTokenSrcID());
mark.setSourceCode(sourceCode);
}
}
cpdListener.phaseUpdate(CPDListener.DONE);
}
@SuppressWarnings("PMD.JumbledIncrementer")
private Map<TokenEntry, Object> hash() {
Map<TokenEntry, Object> markGroups = new HashMap<>(tokens.size());
for (int i = code.size() - 1; i >= 0; i--) {
TokenEntry token = code.get(i);
if (token != TokenEntry.EOF) {
int last = tokenAt(min, token).getIdentifier();
lastHash = MOD * lastHash + token.getIdentifier() - lastMod * last;
token.setHashCode(lastHash);
Object o = markGroups.get(token);
// Note that this insertion method is worthwhile since the vast
// majority
// markGroup keys will have only one value.
if (o == null) {
markGroups.put(token, token);
} else if (o instanceof TokenEntry) {
List<TokenEntry> l = new ArrayList<>();
l.add((TokenEntry) o);
l.add(token);
markGroups.put(token, l);
} else {
@SuppressWarnings("unchecked")
List<TokenEntry> l = (List<TokenEntry>) o;
l.add(token);
}
} else {
lastHash = 0;
for (int end = Math.max(0, i - min + 1); i > end; i--) {
token = code.get(i - 1);
lastHash = MOD * lastHash + token.getIdentifier();
if (token == TokenEntry.EOF) {
break;
}
}
}
}
return markGroups;
}
}