/*
* Copyright 2013 Guidewire Software, Inc.
*/
package gw.plugin.ij.refactor;
import com.intellij.codeInsight.CodeInsightUtilBase;
import com.intellij.codeInsight.PsiEquivalenceUtil;
import com.intellij.codeInsight.completion.JavaCompletionUtil;
import com.intellij.lang.Language;
import com.intellij.lang.StdLanguages;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.FileViewProvider;
import com.intellij.psi.JavaPsiFacade;
import com.intellij.psi.JavaTokenType;
import com.intellij.psi.JspPsiUtil;
import com.intellij.psi.PsiBlockStatement;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiClassType;
import com.intellij.psi.PsiCodeBlock;
import com.intellij.psi.PsiCodeFragment;
import com.intellij.psi.PsiComment;
import com.intellij.psi.PsiDiamondType;
import com.intellij.psi.PsiDiamondTypeImpl;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementFactory;
import com.intellij.psi.PsiExpression;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiJavaToken;
import com.intellij.psi.PsiManager;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiMethodCallExpression;
import com.intellij.psi.PsiParameter;
import com.intellij.psi.PsiReference;
import com.intellij.psi.PsiReferenceExpression;
import com.intellij.psi.PsiResolveHelper;
import com.intellij.psi.PsiSubstitutor;
import com.intellij.psi.PsiType;
import com.intellij.psi.PsiTypeElement;
import com.intellij.psi.PsiTypeParameter;
import com.intellij.psi.PsiWhiteSpace;
import com.intellij.psi.PsiWildcardType;
import com.intellij.psi.impl.source.PsiDiamondTypeElementImpl;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.searches.ClassInheritorsSearch;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.PsiUtilBase;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.psi.util.proximity.PsiProximityComparator;
import com.intellij.refactoring.util.RefactoringUtil;
import com.intellij.util.Consumer;
import com.intellij.util.FilteredQuery;
import com.intellij.util.Processor;
import com.intellij.util.Query;
import gw.plugin.ij.filetypes.GosuCodeFileType;
import gw.plugin.ij.lang.GosuLanguage;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
//Gosu copy of Intellij CodeInsightUtil
public class GosuCodeInsightUtil {
@Nullable
public static PsiExpression findExpressionInRange(PsiFile file, int startOffset, int endOffset) {
if (!file.getViewProvider().getLanguages().contains(GosuCodeFileType.INSTANCE.getLanguage())) {
return null;
}
PsiExpression expression = findElementInRange(file, startOffset, endOffset, PsiExpression.class);
if (expression == null && findStatementsInRange(file, startOffset, endOffset).length == 0) {
PsiElement element2 = file.getViewProvider().findElementAt(endOffset - 1, GosuCodeFileType.INSTANCE.getLanguage());
if (element2 instanceof PsiJavaToken) {
final PsiJavaToken token = (PsiJavaToken) element2;
final IElementType tokenType = token.getTokenType();
if (tokenType.equals(JavaTokenType.SEMICOLON)) {
expression = findElementInRange(file, startOffset, element2.getTextRange().getStartOffset(), PsiExpression.class);
}
}
}
if (expression == null && findStatementsInRange(file, startOffset, endOffset).length == 0) {
PsiElement element = PsiTreeUtil.skipSiblingsBackward(file.findElementAt(endOffset), PsiWhiteSpace.class);
if (element != null) {
element = PsiTreeUtil.skipSiblingsBackward(element.getLastChild(), PsiWhiteSpace.class, PsiComment.class);
if (element != null) {
final int newEndOffset = element.getTextRange().getEndOffset();
if (newEndOffset < endOffset) {
expression = findExpressionInRange(file, startOffset, newEndOffset);
}
}
}
}
if (expression instanceof PsiReferenceExpression && expression.getParent() instanceof PsiMethodCallExpression) {
return null;
}
return expression;
}
public static <T extends PsiElement> T findElementInRange(PsiFile file, int startOffset, int endOffset, Class<T> klass) {
return CodeInsightUtilBase.findElementInRange(file, startOffset, endOffset, klass, GosuLanguage.instance());
}
@NotNull
public static PsiElement[] findStatementsInRange(@NotNull PsiFile file, int startOffset, int endOffset) {
Language language = findJavaOrGosuLikeLanguage(file);
if (language == null) {
return PsiElement.EMPTY_ARRAY;
}
FileViewProvider viewProvider = file.getViewProvider();
PsiElement element1 = viewProvider.findElementAt(startOffset, language);
PsiElement element2 = viewProvider.findElementAt(endOffset - 1, language);
if (element1 instanceof PsiWhiteSpace) {
startOffset = element1.getTextRange().getEndOffset();
element1 = file.findElementAt(startOffset);
}
if (element2 instanceof PsiWhiteSpace) {
endOffset = element2.getTextRange().getStartOffset();
element2 = file.findElementAt(endOffset - 1);
}
if (element1 == null || element2 == null) {
return PsiElement.EMPTY_ARRAY;
}
PsiElement parent = PsiTreeUtil.findCommonParent(element1, element2);
if (parent == null) {
return PsiElement.EMPTY_ARRAY;
}
while (true) {
if (GosuRefactoringUtil.isStatement(parent)) {
parent = parent.getParent();
break;
}
if (parent instanceof PsiCodeBlock) {
break;
}
if (JspPsiUtil.isInJspFile(parent) && parent instanceof PsiFile) {
break;
}
if (parent instanceof PsiCodeFragment) {
break;
}
if (parent == null || parent instanceof PsiFile) {
return PsiElement.EMPTY_ARRAY;
}
parent = parent.getParent();
}
if (!parent.equals(element1)) {
while (!parent.equals(element1.getParent())) {
element1 = element1.getParent();
}
}
if (startOffset != element1.getTextRange().getStartOffset()) {
return PsiElement.EMPTY_ARRAY;
}
if (!parent.equals(element2)) {
while (!parent.equals(element2.getParent())) {
element2 = element2.getParent();
}
}
if (endOffset != element2.getTextRange().getEndOffset()) {
return PsiElement.EMPTY_ARRAY;
}
if (parent instanceof PsiCodeBlock && parent.getParent() instanceof PsiBlockStatement &&
element1 == ((PsiCodeBlock) parent).getLBrace() && element2 == ((PsiCodeBlock) parent).getRBrace()) {
return new PsiElement[]{parent.getParent()};
}
/*
if(parent instanceof PsiCodeBlock && parent.getParent() instanceof PsiBlockStatement) {
return new PsiElement[]{parent.getParent()};
}
*/
PsiElement[] children = parent.getChildren();
ArrayList<PsiElement> array = new ArrayList<>();
boolean flag = false;
for (PsiElement child : children) {
if (child.equals(element1)) {
flag = true;
}
if (flag && !(child instanceof PsiWhiteSpace)) {
array.add(child);
}
if (child.equals(element2)) {
break;
}
}
for (PsiElement element : array) {
if (!(GosuRefactoringUtil.isStatement(element) || element instanceof PsiWhiteSpace || element instanceof PsiComment)) {
return PsiElement.EMPTY_ARRAY;
}
}
return PsiUtilBase.toPsiElementArray(array);
}
@Nullable
public static Language findJavaOrGosuLikeLanguage(@NotNull final PsiFile file) {
final Set<Language> languages = file.getViewProvider().getLanguages();
for (final Language language : languages) {
if (language == StdLanguages.JAVA || language == GosuCodeFileType.INSTANCE.getLanguage()) {
return language;
}
}
for (final Language language : languages) {
if (language.isKindOf(StdLanguages.JAVA) || language.isKindOf(GosuCodeFileType.INSTANCE.getLanguage())) {
return language;
}
}
return null;
}
public static void sortIdenticalShortNameClasses(PsiClass[] classes, @NotNull PsiReference context) {
if (classes.length <= 1) {
return;
}
PsiElement leaf = context.getElement().getFirstChild(); // the same proximity weighers are used in completion, where the leafness is critical
Arrays.sort(classes, new PsiProximityComparator(leaf));
}
public static PsiExpression[] findExpressionOccurrences(PsiElement scope, PsiExpression expr) {
List<PsiExpression> array = new ArrayList<>();
addExpressionOccurrences(RefactoringUtil.unparenthesizeExpression(expr), array, scope);
if (expr.isPhysical()) {
boolean found = false;
for (PsiExpression psiExpression : array) {
if (PsiTreeUtil.isAncestor(expr, psiExpression, false) || PsiTreeUtil.isAncestor(psiExpression, expr, false)) {
found = true;
break;
}
}
if (!found) {
array.add(expr);
}
}
return array.toArray(new PsiExpression[array.size()]);
}
private static void addExpressionOccurrences(PsiExpression expr, List<PsiExpression> array, PsiElement scope) {
PsiElement[] children = scope.getChildren();
for (PsiElement child : children) {
if (child instanceof PsiExpression) {
if (areExpressionsEquivalent(RefactoringUtil.unparenthesizeExpression((PsiExpression) child), expr)) {
array.add((PsiExpression) child);
continue;
}
}
addExpressionOccurrences(expr, array, child);
}
}
public static PsiExpression[] findReferenceExpressions(PsiElement scope, PsiElement referee) {
ArrayList<PsiElement> array = new ArrayList<>();
if (scope != null) {
addReferenceExpressions(array, scope, referee);
}
return array.toArray(new PsiExpression[array.size()]);
}
private static void addReferenceExpressions(ArrayList<PsiElement> array, PsiElement scope, PsiElement referee) {
PsiElement[] children = scope.getChildren();
for (PsiElement child : children) {
if (GosuRefactoringUtil.isPsiReferenceExpression(child)) {
PsiElement ref = ((PsiReference) child).resolve();
if (ref != null && PsiEquivalenceUtil.areElementsEquivalent(ref, referee)) {
array.add(child);
}
}
addReferenceExpressions(array, child, referee);
}
}
public static boolean areExpressionsEquivalent(PsiExpression expr1, PsiExpression expr2) {
return PsiEquivalenceUtil.areElementsEquivalent(expr1, expr2, new Comparator<PsiElement>() {
public int compare(PsiElement o1, PsiElement o2) {
if (o1 instanceof PsiParameter && o2 instanceof PsiParameter && ((PsiParameter) o1).getDeclarationScope() instanceof PsiMethod) {
return ((PsiParameter) o1).getName().compareTo(((PsiParameter) o2).getName());
}
return 1;
}
}, new Comparator<PsiElement>() {
@Override
public int compare(PsiElement o1, PsiElement o2) {
if (!o1.textMatches(o2)) {
return 1;
}
if (o1 instanceof PsiDiamondTypeElementImpl && o2 instanceof PsiDiamondTypeElementImpl) {
final PsiDiamondType.DiamondInferenceResult thisInferenceResult = new PsiDiamondTypeImpl(o1.getManager(), (PsiTypeElement) o1).resolveInferredTypes();
final PsiDiamondType.DiamondInferenceResult otherInferenceResult = new PsiDiamondTypeImpl(o2.getManager(), (PsiTypeElement) o2).resolveInferredTypes();
return thisInferenceResult.equals(otherInferenceResult) ? 0 : 1;
}
return 0;
}
}, null, false
);
}
public static Editor positionCursor(final Project project, PsiFile targetFile, PsiElement element) {
TextRange range = element.getTextRange();
int textOffset = range.getStartOffset();
OpenFileDescriptor descriptor = new OpenFileDescriptor(project, targetFile.getVirtualFile(), textOffset);
return FileEditorManager.getInstance(project).openTextEditor(descriptor, true);
}
public static boolean preparePsiElementsForWrite(@NotNull PsiElement... elements) {
return CodeInsightUtilBase.preparePsiElementsForWrite(Arrays.asList(elements));
}
public static void processSubTypes(PsiType psiType,
final PsiElement context,
boolean getRawSubtypes,
@NotNull Condition<String> shortNameCondition,
Consumer<PsiType> consumer) {
int arrayDim = psiType.getArrayDimensions();
psiType = psiType.getDeepComponentType();
if (!(psiType instanceof PsiClassType)) {
return;
}
final PsiClassType baseType = (PsiClassType) psiType;
final PsiClassType.ClassResolveResult baseResult =
ApplicationManager.getApplication().runReadAction(new Computable<PsiClassType.ClassResolveResult>() {
public PsiClassType.ClassResolveResult compute() {
return JavaCompletionUtil.originalize(baseType).resolveGenerics();
}
});
final PsiClass baseClass = baseResult.getElement();
final PsiSubstitutor baseSubstitutor = baseResult.getSubstitutor();
if (baseClass == null) {
return;
}
final GlobalSearchScope scope = ApplicationManager.getApplication().runReadAction(new Computable<GlobalSearchScope>() {
public GlobalSearchScope compute() {
return context.getResolveScope();
}
});
final Query<PsiClass> baseQuery = ClassInheritorsSearch.search(
new ClassInheritorsSearch.SearchParameters(baseClass, scope, true, false, false, shortNameCondition));
final Query<PsiClass> query = new FilteredQuery<>(baseQuery, new Condition<PsiClass>() {
public boolean value(final PsiClass psiClass) {
return !(psiClass instanceof PsiTypeParameter);
}
});
query.forEach(createInheritorsProcessor(context, baseType, arrayDim, getRawSubtypes, consumer, baseClass, baseSubstitutor));
}
public static Processor<PsiClass> createInheritorsProcessor(final PsiElement context, final PsiClassType baseType,
final int arrayDim,
final boolean getRawSubtypes,
final Consumer<PsiType> result, @NotNull final PsiClass baseClass, final PsiSubstitutor baseSubstitutor) {
final PsiManager manager = context.getManager();
final JavaPsiFacade facade = JavaPsiFacade.getInstance(manager.getProject());
final PsiResolveHelper resolveHelper = facade.getResolveHelper();
return new Processor<PsiClass>() {
public boolean process(final PsiClass inheritor) {
ProgressManager.checkCanceled();
return ApplicationManager.getApplication().runReadAction(new Computable<Boolean>() {
public Boolean compute() {
if (!context.isValid() || !inheritor.isValid() || !facade.getResolveHelper().isAccessible(inheritor, context, null)) {
return true;
}
if (inheritor.getQualifiedName() == null &&
!manager.areElementsEquivalent(inheritor.getContainingFile(), context.getContainingFile().getOriginalFile())) {
return true;
}
if (JavaCompletionUtil.isInExcludedPackage(inheritor, false)) {
return true;
}
PsiSubstitutor superSubstitutor = TypeConversionUtil.getClassSubstitutor(baseClass, inheritor, PsiSubstitutor.EMPTY);
if (superSubstitutor == null) {
return true;
}
if (getRawSubtypes) {
result.consume(createType(inheritor, facade.getElementFactory().createRawSubstitutor(inheritor), arrayDim));
return true;
}
PsiSubstitutor inheritorSubstitutor = PsiSubstitutor.EMPTY;
for (PsiTypeParameter inheritorParameter : PsiUtil.typeParametersIterable(inheritor)) {
for (PsiTypeParameter baseParameter : PsiUtil.typeParametersIterable(baseClass)) {
final PsiType substituted = superSubstitutor.substitute(baseParameter);
PsiType arg = baseSubstitutor.substitute(baseParameter);
if (arg instanceof PsiWildcardType) {
PsiType bound = ((PsiWildcardType) arg).getBound();
arg = bound != null ? bound : ((PsiWildcardType) arg).getExtendsBound();
}
PsiType substitution = resolveHelper.getSubstitutionForTypeParameter(inheritorParameter,
substituted,
arg,
true,
PsiUtil.getLanguageLevel(context));
if (PsiType.NULL.equals(substitution) || substitution instanceof PsiWildcardType) {
continue;
}
if (substitution == null) {
result.consume(createType(inheritor, facade.getElementFactory().createRawSubstitutor(inheritor), arrayDim));
return true;
}
inheritorSubstitutor = inheritorSubstitutor.put(inheritorParameter, substitution);
break;
}
}
PsiType toAdd = createType(inheritor, inheritorSubstitutor, arrayDim);
if (baseType.isAssignableFrom(toAdd)) {
result.consume(toAdd);
}
return true;
}
}).booleanValue();
}
};
}
private static PsiType createType(PsiClass cls,
PsiSubstitutor currentSubstitutor,
int arrayDim) {
final PsiElementFactory elementFactory = JavaPsiFacade.getInstance(cls.getProject()).getElementFactory();
PsiType newType = elementFactory.createType(cls, currentSubstitutor);
for (int i = 0; i < arrayDim; i++) {
newType = newType.createArrayType();
}
return newType;
}
}