blob: e67958a21fafd26df403ac003cb48697188bc3e5 [file] [log] [blame]
/*
* Copyright 2000-2014 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.usageView;
import com.intellij.lang.injection.InjectedLanguageManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.ProperTextRange;
import com.intellij.openapi.util.Segment;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class UsageInfo {
public static final UsageInfo[] EMPTY_ARRAY = new UsageInfo[0];
private static final Logger LOG = Logger.getInstance("#com.intellij.usageView.UsageInfo");
private final SmartPsiElementPointer<?> mySmartPointer;
private final SmartPsiFileRange myPsiFileRange;
public final boolean isNonCodeUsage;
protected boolean myDynamicUsage = false;
public UsageInfo(@NotNull PsiElement element, int startOffset, int endOffset, boolean isNonCodeUsage) {
element = element.getNavigationElement();
PsiFile file = element.getContainingFile();
PsiElement topElement = file == null ? element : file;
LOG.assertTrue(topElement.isValid(), element);
TextRange elementRange = element.getTextRange();
if (elementRange == null) {
throw new IllegalArgumentException("text range null for " + element + "; " + element.getClass());
}
if (startOffset == -1 && endOffset == -1) {
// calculate natural element range
startOffset = element.getTextOffset() - elementRange.getStartOffset();
endOffset = elementRange.getEndOffset() - elementRange.getStartOffset();
}
if (startOffset < 0) {
throw new IllegalArgumentException("element " + element + "; startOffset " +startOffset);
}
if (startOffset > endOffset) {
throw new IllegalArgumentException("element " + element + "; diff " + (endOffset-startOffset));
}
Project project = topElement.getProject();
SmartPointerManager smartPointerManager = SmartPointerManager.getInstance(project);
mySmartPointer = smartPointerManager.createSmartPsiElementPointer(element, file);
if (startOffset != element.getTextOffset() - elementRange.getStartOffset() || endOffset != elementRange.getLength()) {
TextRange rangeToStore;
if (file != null && InjectedLanguageManager.getInstance(project).isInjectedFragment(file)) {
rangeToStore = elementRange;
}
else {
rangeToStore = TextRange.create(startOffset, endOffset).shiftRight(elementRange.getStartOffset());
}
myPsiFileRange = smartPointerManager.createSmartPsiFileRangePointer(file, rangeToStore);
}
else {
myPsiFileRange = null;
}
this.isNonCodeUsage = isNonCodeUsage;
}
public UsageInfo(@NotNull SmartPsiElementPointer<?> smartPointer,
SmartPsiFileRange psiFileRange,
boolean dynamicUsage,
boolean nonCodeUsage) {
myDynamicUsage = dynamicUsage;
isNonCodeUsage = nonCodeUsage;
myPsiFileRange = psiFileRange;
mySmartPointer = smartPointer;
}
@NotNull
public SmartPsiElementPointer<?> getSmartPointer() {
return mySmartPointer;
}
public SmartPsiFileRange getPsiFileRange() {
return myPsiFileRange;
}
public boolean isNonCodeUsage() {
return isNonCodeUsage;
}
public void setDynamicUsage(boolean dynamicUsage) {
myDynamicUsage = dynamicUsage;
}
public UsageInfo(@NotNull PsiElement element, boolean isNonCodeUsage) {
this(element, -1, -1, isNonCodeUsage);
}
public UsageInfo(@NotNull PsiElement element, int startOffset, int endOffset) {
this(element, startOffset, endOffset, false);
}
public UsageInfo(@NotNull PsiReference reference) {
this(reference.getElement(), reference.getRangeInElement().getStartOffset(), reference.getRangeInElement().getEndOffset());
myDynamicUsage = reference.resolve() == null;
}
public UsageInfo(@NotNull PsiQualifiedReferenceElement reference) {
this((PsiElement)reference);
}
public UsageInfo(@NotNull PsiElement element) {
this(element, false);
}
@Nullable
public PsiElement getElement() { // SmartPointer is used to fix SCR #4572, hotya eto krivo i nado vse perepisat'
return mySmartPointer.getElement();
}
@Nullable
public PsiReference getReference() {
PsiElement element = getElement();
return element == null ? null : element.getReference();
}
/**
* @deprecated for the range in element use {@link #getRangeInElement} instead,
* for the whole text range in the file covered by this usage info, use {@link #getSegment()}
*/
public TextRange getRange() {
return getRangeInElement();
}
/**
* @return range in element
*/
@Nullable("null means range is invalid")
public ProperTextRange getRangeInElement() {
PsiElement element = getElement();
if (element == null) return null;
TextRange elementRange = element.getTextRange();
ProperTextRange result;
if (myPsiFileRange == null) {
int startOffset = element.getTextOffset();
result = ProperTextRange.create(startOffset, elementRange.getEndOffset());
}
else {
Segment rangeInFile = myPsiFileRange.getRange();
if (rangeInFile == null) return null;
result = ProperTextRange.create(rangeInFile);
}
int delta = elementRange.getStartOffset();
return result.getStartOffset() < delta ? null : result.shiftRight(-delta);
}
/**
* Override this method if you want a tooltip to be displayed for this usage
*/
public String getTooltipText() {
return null;
}
public int getNavigationOffset() {
if (myPsiFileRange != null) {
final Segment range = myPsiFileRange.getRange();
if (range != null) {
return range.getStartOffset();
}
}
PsiElement element = getElement();
if (element == null) return -1;
TextRange range = element.getTextRange();
TextRange rangeInElement = getRangeInElement();
if (rangeInElement == null) return -1;
return range.getStartOffset() + rangeInElement.getStartOffset();
}
public boolean isValid() {
return getSegment() != null;
}
@Nullable
public Segment getSegment() {
PsiElement element = getElement();
if (element == null) return null;
TextRange range = element.getTextRange();
TextRange.assertProperRange(range, element);
if (element instanceof PsiFile) {
// hack: it's actually a range inside file, use document for range checking since during the "find|replace all" operation, file range might have been changed
Document document = PsiDocumentManager.getInstance(getProject()).getDocument((PsiFile)element);
if (document != null) {
range = new ProperTextRange(0, document.getTextLength());
}
}
ProperTextRange rangeInElement = getRangeInElement();
if (rangeInElement == null) return null;
return new ProperTextRange(Math.min(range.getEndOffset(), range.getStartOffset() + rangeInElement.getStartOffset()),
Math.min(range.getEndOffset(), range.getStartOffset() + rangeInElement.getEndOffset()));
}
@NotNull
public Project getProject() {
return mySmartPointer.getProject();
}
public final boolean isWritable() {
PsiElement element = getElement();
return element == null || element.isWritable();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!getClass().equals(o.getClass())) return false;
final UsageInfo usageInfo = (UsageInfo)o;
if (isNonCodeUsage != usageInfo.isNonCodeUsage) return false;
SmartPointerManager smartPointerManager = SmartPointerManager.getInstance(getProject());
return smartPointerManager.pointToTheSameElement(mySmartPointer, usageInfo.mySmartPointer)
&& (myPsiFileRange == null || usageInfo.myPsiFileRange != null && smartPointerManager.pointToTheSameElement(myPsiFileRange, usageInfo.myPsiFileRange));
}
@Override
public int hashCode() {
int result = mySmartPointer != null ? mySmartPointer.hashCode() : 0;
result = 29 * result + (myPsiFileRange == null ? 0 : myPsiFileRange.hashCode());
result = 29 * result + (isNonCodeUsage ? 1 : 0);
return result;
}
@Override
public String toString() {
PsiReference reference = getReference();
if (reference == null) {
return super.toString();
}
return reference.getCanonicalText() + " (" + reference.getClass() + ")";
}
@Nullable
public PsiFile getFile() {
return mySmartPointer.getContainingFile();
}
@Nullable
public VirtualFile getVirtualFile() {
return mySmartPointer.getVirtualFile();
}
public boolean isDynamicUsage() {
return myDynamicUsage;
}
// creates new smart pointers
@Nullable("null means could not copy because info is no longer valid")
public UsageInfo copy() {
PsiElement element = mySmartPointer.getElement();
SmartPointerManager smartPointerManager = SmartPointerManager.getInstance(getProject());
PsiFile containingFile = myPsiFileRange == null ? null : myPsiFileRange.getContainingFile();
Segment segment = containingFile == null ? null : myPsiFileRange.getRange();
TextRange range = segment == null ? null : TextRange.create(segment);
SmartPsiFileRange psiFileRange = range == null ? null : smartPointerManager.createSmartPsiFileRangePointer(containingFile, range);
SmartPsiElementPointer<PsiElement> pointer = element == null || !isValid() ? null : smartPointerManager.createSmartPsiElementPointer(element);
return pointer == null ? null : new UsageInfo(pointer, psiFileRange, isDynamicUsage(), isNonCodeUsage());
}
}