blob: d020651f055ddd0ac06a6335b8556e72c5ec1ef1 [file] [log] [blame]
package com.intellij.dupLocator.treeHash;
import com.intellij.dupLocator.NodeSpecificHasher;
import com.intellij.dupLocator.TreeHasher;
import com.intellij.dupLocator.util.PsiFragment;
import com.intellij.lang.Language;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.psi.PsiAnchor;
import com.intellij.psi.PsiElement;
import com.intellij.psi.impl.source.tree.LeafElement;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* Created by IntelliJ IDEA.
* User: db, oleg
* Date: Mar 26, 2004
* Time: 4:44:13 PM
* To change this template use File | Settings | File Templates.
*/
public abstract class AbstractTreeHasher implements TreeHasher {
protected final boolean myForIndexing;
protected final FragmentsCollector myCallBack;
public AbstractTreeHasher(FragmentsCollector cb, boolean forIndexing) {
myCallBack = cb;
myForIndexing = forIndexing;
}
public final void hash(@NotNull final PsiElement root, @NotNull final NodeSpecificHasher hasher) {
hash(root, null, hasher);
}
protected abstract TreeHashResult hash(@NotNull PsiElement root, PsiFragment upper, @NotNull NodeSpecificHasher hasher);
/**
* Computes element hash using children hashes.
* Creates only single PsiFragment.
*/
protected TreeHashResult computeElementHash(@NotNull final PsiElement root, final PsiFragment upper, final NodeSpecificHasher hasher) {
if (myForIndexing) {
return TreeHashingUtils.computeElementHashForIndexing(this, myCallBack, root, upper, hasher);
}
ProgressManager.checkCanceled();
final List<PsiElement> children = hasher.getNodeChildren(root);
final int size = children.size();
final int[] childHashes = new int[size];
final int[] childCosts = new int[size];
final PsiFragment fragment = buildFragment(hasher, root, getCost(root));
if (upper != null) {
fragment.setParent(upper);
}
if (size == 0 && !(root instanceof LeafElement)) {
return new TreeHashResult(hasher.getNodeHash(root), hasher.getNodeCost(root), fragment);
}
for (int i = 0; i < size; i++) {
final TreeHashResult res = hash(children.get(i), fragment, hasher);
childHashes[i] = res.getHash();
childCosts[i] = res.getCost();
}
final int c = hasher.getNodeCost(root) + vector(childCosts);
final int h1 = hasher.getNodeHash(root);
final int discardCost = getDiscardCost(root);
for (int i = 0; i < size; i++) {
if (childCosts[i] <= discardCost && ignoreChildHash(children.get(i))) {
childHashes[i] = 0;
}
}
final int h = h1 + vector(childHashes);
if (myCallBack != null) {
myCallBack.add(h, c, fragment);
}
return new TreeHashResult(h, c, fragment);
}
protected TreePsiFragment buildFragment(NodeSpecificHasher hasher, PsiElement root,int cost) {
if (myForIndexing) {
return new TreePsiFragment(hasher, root, cost) {
@Override
protected PsiAnchor createAnchor(PsiElement element) {
return new PsiAnchor.HardReference(element);
}
@Override
protected Language calcLanguage(PsiElement element) {
return null; // for performance
}
};
}
return new TreePsiFragment(hasher, root, cost);
}
protected TreePsiFragment buildFragment(NodeSpecificHasher hasher, List<? extends PsiElement> elements, int from, int to) {
if (myForIndexing) {
return new TreePsiFragment(hasher, elements, from, to) {
@Override
protected PsiAnchor createAnchor(PsiElement element) {
return new PsiAnchor.HardReference(element);
}
@Override
protected Language calcLanguage(PsiElement element) {
return null; // for performance
}
};
}
return new TreePsiFragment(hasher, elements, from, to);
}
protected abstract int getDiscardCost(PsiElement root);
protected boolean ignoreChildHash(PsiElement element) {
return false;
}
protected TreeHashResult hashCodeBlock(final List<? extends PsiElement> statements,
final PsiFragment upper,
final NodeSpecificHasher hasher) {
return hashCodeBlock(statements, upper, hasher, false);
}
/**
* Creates PsiFragments using given statements with their hashes
*/
protected TreeHashResult hashCodeBlock(final List<? extends PsiElement> statements,
final PsiFragment upper,
final NodeSpecificHasher hasher,
boolean forceHash) {
final int statementsSize = statements.size();
if (statementsSize == 1) {
return hash(statements.get(0), upper, hasher);
}
if (statementsSize > 0) {
// Here we compute all the possible code fragments using statements
if (statementsSize < 20 || forceHash) { //todo should be configurable
final PsiFragment[] frags = new PsiFragment[statementsSize];
final PsiFragment fragment = buildFragment(hasher, statements, 0, statementsSize - 1);
fragment.setParent(upper);
// Fill all the statements costs and hashes
final int[] hashes = new int[statementsSize];
final int[] costs = new int[statementsSize];
for (int i = 0; i < statementsSize; i++) {
final TreeHashResult res = hash(statements.get(i), null, hasher);
hashes[i] = res.getHash();
costs[i] = res.getCost();
frags[i] = res.getFragment();
}
if (myCallBack != null) {
final PsiFragment[] parents = new PsiFragment[statementsSize]; //parent(end) = [beg, end]
for (int beg = 0; beg < statementsSize; beg++) {
int hash = 0;
int cost = 0;
for (int end = beg; end < statementsSize && end - beg < 20; end++) {
hash = 31 * hash + hashes[end];
cost += costs[end];
final PsiFragment curr =
beg == end
? frags[beg]
: beg == 0 && end == statementsSize - 1 ? fragment : buildFragment(hasher, statements, beg, end);
if (beg > 0) {
curr.setParent(parents[end]); //[beg, end].setParent([beg - 1, end])
}
parents[end] = curr;
if (end > beg) {
parents[end - 1].setParent(curr);//[beg, end - 1].setParent([beg, end])
}
myCallBack.add(hash, cost, curr);
}
}
}
return new TreeHashResult(vector(hashes, 31), vector(costs), fragment);
}
}
return new TreeHashResult(1, 0, buildFragment(hasher, statements, 0, statementsSize - 1));
}
protected int getCost(final PsiElement root){
return 0;
}
public static int vector(int[] args) {
return vector(args, 1);
}
public static int vector(int[] args, int mult) {
int sum = 0;
for (int arg : args) {
sum = mult * sum + arg;
}
return sum;
}
public boolean shouldAnonymize(PsiElement root, NodeSpecificHasher hasher) {
return false;
}
}