blob: bf0ef80ab793637c7b54777a19861976e330367d [file] [log] [blame]
package com.intellij.dupLocator.util;
import com.intellij.dupLocator.*;
import com.intellij.dupLocator.equivalence.EquivalenceDescriptor;
import com.intellij.dupLocator.equivalence.EquivalenceDescriptorProvider;
import com.intellij.dupLocator.equivalence.MultiChildDescriptor;
import com.intellij.dupLocator.equivalence.SingleChildDescriptor;
import com.intellij.dupLocator.iterators.FilteringNodeIterator;
import com.intellij.dupLocator.iterators.SiblingNodeIterator;
import com.intellij.lang.Language;
import com.intellij.openapi.util.Comparing;
import com.intellij.psi.PsiComment;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiErrorElement;
import com.intellij.psi.PsiWhiteSpace;
import com.intellij.psi.impl.source.tree.LeafElement;
import com.intellij.psi.tree.IElementType;
import com.intellij.util.text.CharArrayUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Set;
/**
* @author Eugene.Kudelevsky
*/
public class DuplocatorUtil {
private DuplocatorUtil() {
}
public static boolean isIgnoredNode(PsiElement element) {
// ex. "var i = 0" in AS: empty JSAttributeList should be skipped
/*if (element.getText().length() == 0) {
return true;
}*/
if (element instanceof PsiWhiteSpace || element instanceof PsiErrorElement || element instanceof PsiComment) {
return true;
}
if (!(element instanceof LeafElement)) {
return false;
}
if (CharArrayUtil.containsOnlyWhiteSpaces(element.getText())) {
return true;
}
EquivalenceDescriptorProvider descriptorProvider = EquivalenceDescriptorProvider.getInstance(element);
if (descriptorProvider == null) {
return false;
}
final IElementType elementType = ((LeafElement)element).getElementType();
return descriptorProvider.getIgnoredTokens().contains(elementType);
}
public static PsiElement getOnlyChild(PsiElement element, @NotNull NodeFilter filter) {
FilteringNodeIterator it = new FilteringNodeIterator(new SiblingNodeIterator(element.getFirstChild()), filter);
PsiElement child = it.current();
if (child != null) {
it.advance();
if (!it.hasNext()) {
return child;
}
}
return element;
}
public static boolean shouldSkip(PsiElement element, PsiElement elementToMatchWith) {
if (element == null || elementToMatchWith == null) {
return false;
}
if (element.getClass() == elementToMatchWith.getClass()) {
return false;
}
if (element.getFirstChild() == null && element.getTextLength() == 0 && !(element instanceof LeafElement)) {
return true;
}
return false;
}
@Nullable
public static PsiElement skipNodeIfNeccessary(PsiElement element, EquivalenceDescriptor descriptor, NodeFilter filter) {
if (element == null) {
return null;
}
/*if (!canSkip(element) && getOnlyNonWhitespaceChild(element) == null) {
return element;
}*/
// todo optimize! (this method is often invokated for the same node)
if (descriptor == null) {
final EquivalenceDescriptorProvider provider = EquivalenceDescriptorProvider.getInstance(element);
if (provider != null) {
descriptor = provider.buildDescriptor(element);
}
}
if (descriptor != null) {
final PsiElement onlyChild = getOnlyChildFromDescriptor(descriptor, filter);
return onlyChild != null ? onlyChild : element;
}
return getOnlyChild(element, filter);
}
@Nullable
private static PsiElement getOnlyChildFromDescriptor(EquivalenceDescriptor equivalenceDescriptor, NodeFilter filter) {
if (!equivalenceDescriptor.getConstants().isEmpty()) {
return null;
}
final List<SingleChildDescriptor> singleChildren = equivalenceDescriptor.getSingleChildDescriptors();
final List<MultiChildDescriptor> multiChildren = equivalenceDescriptor.getMultiChildDescriptors();
final List<PsiElement[]> codeBlocks = equivalenceDescriptor.getCodeBlocks();
if (singleChildren.size() + multiChildren.size() + codeBlocks.size() != 1) {
return null;
}
if (!singleChildren.isEmpty()) {
final SingleChildDescriptor descriptor = singleChildren.get(0);
final PsiElement child = descriptor.getElement();
if (child != null) {
final SingleChildDescriptor.MyType type = descriptor.getType();
if (type == SingleChildDescriptor.MyType.DEFAULT) {
return child;
}
else if (type == SingleChildDescriptor.MyType.CHILDREN ||
type == SingleChildDescriptor.MyType.CHILDREN_IN_ANY_ORDER) {
return getOnlyChild(child, filter);
}
}
}
else if (!multiChildren.isEmpty()) {
final MultiChildDescriptor descriptor = multiChildren.get(0);
final PsiElement[] children = descriptor.getElements();
if (children != null && children.length == 1 && descriptor.getType() != MultiChildDescriptor.MyType.OPTIONALLY) {
return children[0];
}
}
else if (!codeBlocks.isEmpty()) {
final PsiElement[] codeBlock = codeBlocks.get(0);
if (codeBlock != null && codeBlock.length == 1) {
return codeBlock[0];
}
}
return null;
}
public static boolean match(@NotNull EquivalenceDescriptor descriptor1,
@NotNull EquivalenceDescriptor descriptor2,
@NotNull AbstractMatchingVisitor g,
@NotNull Set<PsiElementRole> skippedRoles,
@Nullable DuplicatesProfile profile) {
if (descriptor1.getSingleChildDescriptors().size() != descriptor2.getSingleChildDescriptors().size()) {
return false;
}
if (descriptor1.getMultiChildDescriptors().size() != descriptor2.getMultiChildDescriptors().size()) {
return false;
}
if (descriptor1.getCodeBlocks().size() != descriptor2.getCodeBlocks().size()) {
return false;
}
if (descriptor1.getConstants().size() != descriptor2.getConstants().size()) {
return false;
}
for (int i = 0, n = descriptor1.getConstants().size(); i < n; i++) {
Object childDescriptor1 = descriptor1.getConstants().get(i);
Object childDescriptor2 = descriptor2.getConstants().get(i);
if (!Comparing.equal(childDescriptor1, childDescriptor2)) {
return false;
}
}
for (int i = 0, n = descriptor1.getSingleChildDescriptors().size(); i < n; i++) {
SingleChildDescriptor childDescriptor1 = descriptor1.getSingleChildDescriptors().get(i);
SingleChildDescriptor childDescriptor2 = descriptor2.getSingleChildDescriptors().get(i);
if (!match(childDescriptor1, childDescriptor2, g, skippedRoles, profile)) {
return false;
}
}
for (int i = 0, n = descriptor1.getMultiChildDescriptors().size(); i < n; i++) {
MultiChildDescriptor childDescriptor1 = descriptor1.getMultiChildDescriptors().get(i);
MultiChildDescriptor childDescriptor2 = descriptor2.getMultiChildDescriptors().get(i);
if (!match(childDescriptor1, childDescriptor2, g)) {
return false;
}
}
for (int i = 0, n = descriptor1.getCodeBlocks().size(); i < n; i++) {
final PsiElement[] codeBlock1 = descriptor1.getCodeBlocks().get(i);
final PsiElement[] codeBlock2 = descriptor2.getCodeBlocks().get(i);
if (!g.matchSequentially(codeBlock1, codeBlock2)) {
return false;
}
}
return true;
}
private static boolean match(@NotNull SingleChildDescriptor childDescriptor1,
@NotNull SingleChildDescriptor childDescriptor2,
@NotNull AbstractMatchingVisitor g,
@NotNull Set<PsiElementRole> skippedRoles,
@Nullable DuplicatesProfile duplicatesProfile) {
if (childDescriptor1.getType() != childDescriptor2.getType()) {
return false;
}
final PsiElement element1 = childDescriptor1.getElement();
final PsiElement element2 = childDescriptor2.getElement();
if (duplicatesProfile != null) {
final PsiElementRole role1 = element1 != null ? duplicatesProfile.getRole(element1) : null;
final PsiElementRole role2 = element2 != null ? duplicatesProfile.getRole(element2) : null;
if (role1 == role2 && skippedRoles.contains(role1)) {
return true;
}
}
switch (childDescriptor1.getType()) {
case DEFAULT:
return g.match(element1, element2);
case OPTIONALLY_IN_PATTERN:
case OPTIONALLY:
return g.matchOptionally(element1, element2);
case CHILDREN:
return g.matchSons(element1, element2);
case CHILDREN_OPTIONALLY_IN_PATTERN:
case CHILDREN_OPTIONALLY:
return g.matchSonsOptionally(element1, element2);
case CHILDREN_IN_ANY_ORDER:
return g.matchSonsInAnyOrder(element1, element2);
default:
return false;
}
}
private static boolean match(@NotNull MultiChildDescriptor childDescriptor1,
@NotNull MultiChildDescriptor childDescriptor2,
@NotNull AbstractMatchingVisitor g) {
if (childDescriptor1.getType() != childDescriptor2.getType()) {
return false;
}
final PsiElement[] elements1 = childDescriptor1.getElements();
final PsiElement[] elements2 = childDescriptor2.getElements();
switch (childDescriptor1.getType()) {
case DEFAULT:
return g.matchSequentially(elements1, elements2);
case OPTIONALLY_IN_PATTERN:
case OPTIONALLY:
return g.matchOptionally(elements1, elements2);
case IN_ANY_ORDER:
return g.matchInAnyOrder(elements1, elements2);
default:
return false;
}
}
@Nullable
public static DuplocatorState getDuplocatorState(PsiFragment frag) {
final Language language = frag.getLanguage();
if (language == null) {
return null;
}
final DuplicatesProfile profile = DuplicatesProfile.findProfileForLanguage(language);
return profile != null
? profile.getDuplocatorState(language)
: null;
}
@NotNull
public static ExternalizableDuplocatorState registerAndGetState(Language language) {
final MultilanguageDuplocatorSettings settings = MultilanguageDuplocatorSettings.getInstance();
ExternalizableDuplocatorState state = settings.getState(language);
if (state == null) {
state = new DefaultDuplocatorState();
settings.registerState(language, state);
}
return state;
}
}