blob: 23c4d1fdb3e2322428d1de644e3dce930e4e9223 [file] [log] [blame]
/*
* Copyright 2000-2009 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.openapi.paths;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiReference;
import com.intellij.util.SmartList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* @author Dmitry Avdeev
*/
public class PathReferenceManagerImpl extends PathReferenceManager {
private final StaticPathReferenceProvider myStaticProvider = new StaticPathReferenceProvider(null);
private final PathReferenceProvider myGlobalPathsProvider = new GlobalPathReferenceProvider();
private static final Comparator<PsiReference> START_OFFSET_COMPARATOR = new Comparator<PsiReference>() {
@Override
public int compare(final PsiReference o1, final PsiReference o2) {
return o1.getRangeInElement().getStartOffset() - o2.getRangeInElement().getStartOffset();
}
};
@Override
@Nullable
public PathReference getPathReference(@NotNull String path,
@NotNull PsiElement element,
PathReferenceProvider... additionalProviders) {
PathReference pathReference;
for (PathReferenceProvider provider : getProviders()) {
pathReference = provider.getPathReference(path, element);
if (pathReference != null) {
return pathReference;
}
}
for (PathReferenceProvider provider : additionalProviders) {
pathReference = provider.getPathReference(path, element);
if (pathReference != null) {
return pathReference;
}
}
pathReference = myStaticProvider.getPathReference(path, element);
if (pathReference != null) {
return pathReference;
}
return null;
}
@Override
@Nullable
public PathReference getCustomPathReference(@NotNull String path, @NotNull Module module, @NotNull PsiElement element, PathReferenceProvider... providers) {
for (PathReferenceProvider provider : providers) {
PathReference reference = provider.getPathReference(path, element);
if (reference != null) {
return reference;
}
}
return null;
}
@Override
@NotNull
public PathReferenceProvider getGlobalWebPathReferenceProvider() {
return myGlobalPathsProvider;
}
@Override
@NotNull
public PathReferenceProvider createStaticPathReferenceProvider(final boolean relativePathsAllowed) {
final StaticPathReferenceProvider provider = new StaticPathReferenceProvider(null);
provider.setRelativePathsAllowed(relativePathsAllowed);
return provider;
}
@Override
@NotNull
public PsiReference[] createReferences(@NotNull final PsiElement psiElement,
final boolean soft,
boolean endingSlashNotAllowed,
final boolean relativePathsAllowed, PathReferenceProvider... additionalProviders) {
return createReferences(psiElement, soft, endingSlashNotAllowed, relativePathsAllowed, null, additionalProviders);
}
@Override
@NotNull
public PsiReference[] createReferences(@NotNull final PsiElement psiElement,
final boolean soft,
boolean endingSlashNotAllowed,
final boolean relativePathsAllowed, FileType[] suitableFileTypes, PathReferenceProvider... additionalProviders) {
List<PsiReference> mergedReferences = new ArrayList<PsiReference>();
processProvider(psiElement, myGlobalPathsProvider, mergedReferences, soft);
StaticPathReferenceProvider staticProvider = new StaticPathReferenceProvider(suitableFileTypes);
staticProvider.setEndingSlashNotAllowed(endingSlashNotAllowed);
staticProvider.setRelativePathsAllowed(relativePathsAllowed);
processProvider(psiElement, staticProvider, mergedReferences, soft);
for (PathReferenceProvider provider : getProviders()) {
processProvider(psiElement, provider, mergedReferences, soft);
}
for (PathReferenceProvider provider : additionalProviders) {
processProvider(psiElement, provider, mergedReferences, soft);
}
for (PathReferenceProvider provider : Extensions.getExtensions(ANCHOR_REFERENCE_PROVIDER_EP)) {
processProvider(psiElement, provider, mergedReferences, soft);
}
return mergeReferences(psiElement, mergedReferences);
}
@Override
@NotNull
public PsiReference[] createCustomReferences(@NotNull PsiElement psiElement, boolean soft, PathReferenceProvider... providers) {
List<PsiReference> references = new ArrayList<PsiReference>();
for (PathReferenceProvider provider : providers) {
boolean processed = processProvider(psiElement, provider, references, soft);
if (processed) {
break;
}
}
return mergeReferences(psiElement, references);
}
@Override
@NotNull
public PsiReference[] createReferences(@NotNull PsiElement psiElement, final boolean soft, PathReferenceProvider... additionalProviders) {
return createReferences(psiElement, soft, false, true, null, additionalProviders);
}
private static PsiReference[] mergeReferences(PsiElement element, List<PsiReference> references) {
if (references.size() <= 1) {
return references.toArray(new PsiReference[references.size()]);
}
Collections.sort(references, START_OFFSET_COMPARATOR);
final List<PsiReference> intersecting = new ArrayList<PsiReference>();
final List<PsiReference> notIntersecting = new ArrayList<PsiReference>();
TextRange intersectingRange = references.get(0).getRangeInElement();
boolean intersected = false;
for (int i = 1; i < references.size(); i++) {
final PsiReference reference = references.get(i);
final TextRange range = reference.getRangeInElement();
final int offset = range.getStartOffset();
if (intersectingRange.getStartOffset() <= offset && intersectingRange.getEndOffset() >= offset) {
intersected = true;
intersecting.add(references.get(i - 1));
if (i == references.size() - 1) {
intersecting.add(reference);
}
intersectingRange = intersectingRange.union(range);
} else {
if (intersected) {
intersecting.add(references.get(i - 1));
intersected = false;
} else {
notIntersecting.add(references.get(i - 1));
}
intersectingRange = range;
if (i == references.size() - 1) {
notIntersecting.add(reference);
}
}
}
List<PsiReference> result = doMerge(element, intersecting);
result.addAll(notIntersecting);
return result.toArray(new PsiReference[result.size()]);
}
private static List<PsiReference> doMerge(final PsiElement element, final List<PsiReference> references) {
List<PsiReference> resolvingRefs = new ArrayList<PsiReference>();
List<PsiReference> nonResolvingRefs = new ArrayList<PsiReference>();
//noinspection ForLoopReplaceableByForEach
for (int i = 0; i < references.size(); i++) {
PsiReference reference = references.get(i);
assert element.equals(reference.getElement());
if (reference.resolve() != null) {
resolvingRefs.add(reference);
} else {
nonResolvingRefs.add(reference);
}
}
List<PsiReference> result = new ArrayList<PsiReference>(5);
while (!resolvingRefs.isEmpty()) {
final List<PsiReference> list = new ArrayList<PsiReference>(5);
final TextRange range = getFirstIntersectingReferences(resolvingRefs, list);
final TextRange textRange = addIntersectingReferences(nonResolvingRefs, list, range);
addToResult(element, result, list, textRange);
}
while (!nonResolvingRefs.isEmpty()) {
final SmartList<PsiReference> list = new SmartList<PsiReference>();
final TextRange range = getFirstIntersectingReferences(nonResolvingRefs, list);
int endOffset = range.getEndOffset();
for (final PsiReference reference : list) {
endOffset = Math.min(endOffset, reference.getRangeInElement().getEndOffset());
}
addToResult(element, result, list, new TextRange(range.getStartOffset(), endOffset));
}
return result;
}
private static void addToResult(final PsiElement element,
final List<PsiReference> result,
final List<PsiReference> list,
final TextRange range) {
if (list.size() == 1) {
result.add(list.get(0));
} else {
final PsiDynaReference psiDynaReference = new PsiDynaReference<PsiElement>(element);
psiDynaReference.addReferences(list);
psiDynaReference.setRangeInElement(range);
result.add(psiDynaReference);
}
}
private static TextRange addIntersectingReferences(List<PsiReference> set, List<PsiReference> toAdd, TextRange range) {
int startOffset = range.getStartOffset();
int endOffset = range.getStartOffset();
for (Iterator<PsiReference> iterator = set.iterator(); iterator.hasNext();) {
PsiReference reference = iterator.next();
final TextRange rangeInElement = reference.getRangeInElement();
if (intersect(range, rangeInElement)) {
toAdd.add(reference);
iterator.remove();
startOffset = Math.min(startOffset, rangeInElement.getStartOffset());
endOffset = Math.max(endOffset, rangeInElement.getEndOffset());
}
}
return new TextRange(startOffset, endOffset);
}
private static boolean intersect(final TextRange range1, final TextRange range2) {
return range2.intersectsStrict(range1) || range2.intersects(range1) && (range1.isEmpty() || range2.isEmpty());
}
private static TextRange getFirstIntersectingReferences(List<PsiReference> set, List<PsiReference> toAdd) {
int startOffset = Integer.MAX_VALUE;
int endOffset = -1;
for (Iterator<PsiReference> it = set.iterator(); it.hasNext();) {
PsiReference reference = it.next();
final TextRange range = reference.getRangeInElement();
if (endOffset == -1 || range.getStartOffset() <= endOffset) {
startOffset = Math.min(startOffset, range.getStartOffset());
endOffset = Math.max(range.getEndOffset(), endOffset);
toAdd.add(reference);
it.remove();
}
else {
break;
}
}
return new TextRange(startOffset, endOffset);
}
private static boolean processProvider(PsiElement psiElement, PathReferenceProvider provider, List<PsiReference> mergedReferences, boolean soft) {
return provider.createReferences(psiElement, mergedReferences, soft);
}
private static PathReferenceProvider[] getProviders() {
return Extensions.getExtensions(PATH_REFERENCE_PROVIDER_EP);
}
}