blob: a6d734b92d8e14a9c4ccbc0c1dc6589594d27128 [file] [log] [blame]
/*
* Copyright 2000-2013 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.codeInsight.lookup;
import com.intellij.codeInsight.completion.*;
import com.intellij.diagnostic.LogMessageEx;
import com.intellij.diagnostic.AttachmentFactory;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.ScrollType;
import com.intellij.openapi.util.ClassConditionKey;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.impl.DebugUtil;
import com.intellij.psi.impl.source.PostprocessReformattingAspect;
import com.intellij.psi.impl.source.PsiClassReferenceType;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.ArrayUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashSet;
import java.util.Set;
/**
* @author peter
*/
public class PsiTypeLookupItem extends LookupItem {
private static final InsertHandler<PsiTypeLookupItem> DEFAULT_IMPORT_FIXER = new InsertHandler<PsiTypeLookupItem>() {
@Override
public void handleInsert(InsertionContext context, PsiTypeLookupItem item) {
if (item.getObject() instanceof PsiClass) {
addImportForItem(context, (PsiClass)item.getObject());
}
}
};
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.lookup.PsiTypeLookupItem");
public static final ClassConditionKey<PsiTypeLookupItem> CLASS_CONDITION_KEY = ClassConditionKey.create(PsiTypeLookupItem.class);
private final boolean myDiamond;
private final int myBracketsCount;
private boolean myIndicateAnonymous;
private final InsertHandler<PsiTypeLookupItem> myImportFixer;
private boolean myAddArrayInitializer;
private PsiTypeLookupItem(Object o, @NotNull @NonNls String lookupString, boolean diamond, int bracketsCount, InsertHandler<PsiTypeLookupItem> fixer) {
super(o, lookupString);
myDiamond = diamond;
myBracketsCount = bracketsCount;
myImportFixer = fixer;
}
@NotNull
public PsiType getPsiType() {
Object object = getObject();
PsiType type = object instanceof PsiType ? (PsiType)object : JavaPsiFacade.getElementFactory(((PsiClass) object).getProject()).createType((PsiClass)object);
for (int i = 0; i < getBracketsCount(); i++) {
type = new PsiArrayType(type);
}
return type;
}
public void setIndicateAnonymous(boolean indicateAnonymous) {
myIndicateAnonymous = indicateAnonymous;
}
public boolean isIndicateAnonymous() {
return myIndicateAnonymous;
}
@Override
public boolean equals(final Object o) {
return super.equals(o) && o instanceof PsiTypeLookupItem &&
getBracketsCount() == ((PsiTypeLookupItem) o).getBracketsCount() &&
myAddArrayInitializer == ((PsiTypeLookupItem) o).myAddArrayInitializer;
}
public boolean isAddArrayInitializer() {
return myAddArrayInitializer;
}
public void setAddArrayInitializer() {
myAddArrayInitializer = true;
}
@Override
public void handleInsert(InsertionContext context) {
myImportFixer.handleInsert(context, this);
PsiElement position = context.getFile().findElementAt(context.getStartOffset());
if (position != null) {
int genericsStart = context.getTailOffset();
context.getDocument().insertString(genericsStart, JavaCompletionUtil.escapeXmlIfNeeded(context, calcGenerics(position, context)));
JavaCompletionUtil.shortenReference(context.getFile(), genericsStart - 1);
}
int tail = context.getTailOffset();
String braces = StringUtil.repeat("[]", getBracketsCount());
Editor editor = context.getEditor();
if (!braces.isEmpty()) {
if (myAddArrayInitializer) {
context.getDocument().insertString(tail, braces + "{}");
editor.getCaretModel().moveToOffset(tail + braces.length() + 1);
} else {
context.getDocument().insertString(tail, braces);
editor.getCaretModel().moveToOffset(tail + 1);
if (context.getCompletionChar() == '[') {
context.setAddCompletionChar(false);
}
}
}
else {
editor.getCaretModel().moveToOffset(tail);
}
editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
InsertHandler handler = getInsertHandler();
if (handler != null && !(handler instanceof DefaultInsertHandler)) {
//noinspection unchecked
handler.handleInsert(context, this);
}
}
public String calcGenerics(@NotNull PsiElement context, InsertionContext insertionContext) {
if (insertionContext.getCompletionChar() == '<') {
return "";
}
assert context.isValid();
if (myDiamond) {
return "<>";
}
if (getObject() instanceof PsiClass) {
PsiClass psiClass = (PsiClass)getObject();
PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(psiClass.getProject()).getResolveHelper();
PsiSubstitutor substitutor = getSubstitutor();
StringBuilder builder = new StringBuilder();
for (PsiTypeParameter parameter : psiClass.getTypeParameters()) {
PsiType substitute = substitutor.substitute(parameter);
if (substitute == null ||
(PsiUtil.resolveClassInType(substitute) == parameter &&
resolveHelper.resolveReferencedClass(parameter.getName(), context) != CompletionUtil.getOriginalOrSelf(parameter))) {
return "";
}
if (builder.length() > 0) {
builder.append(", ");
}
builder.append(substitute.getCanonicalText());
}
if (builder.length() > 0) {
return "<" + builder + ">";
}
}
return "";
}
@Override
public int hashCode() {
final int fromSuper = super.hashCode();
final int dim = getBracketsCount();
return fromSuper + dim * 31;
}
public int getBracketsCount() {
return myBracketsCount;
}
public static PsiTypeLookupItem createLookupItem(@NotNull PsiType type, @Nullable PsiElement context) {
final boolean diamond = isDiamond(type);
return createLookupItem(type, context, diamond);
}
public static PsiTypeLookupItem createLookupItem(@NotNull PsiType type, @Nullable PsiElement context, boolean isDiamond) {
return createLookupItem(type, context, isDiamond, DEFAULT_IMPORT_FIXER);
}
public static PsiTypeLookupItem createLookupItem(@NotNull PsiType type, @Nullable PsiElement context, boolean isDiamond, InsertHandler<PsiTypeLookupItem> importFixer) {
final PsiType original = type;
int dim = 0;
while (type instanceof PsiArrayType) {
type = ((PsiArrayType)type).getComponentType();
dim++;
}
PsiTypeLookupItem item = doCreateItem(type, context, dim, isDiamond, importFixer);
item.setAttribute(TYPE, original);
return item;
}
private static PsiTypeLookupItem doCreateItem(final PsiType type,
PsiElement context,
int bracketsCount,
boolean diamond,
InsertHandler<PsiTypeLookupItem> importFixer) {
if (type instanceof PsiClassType) {
PsiClassType.ClassResolveResult classResolveResult = ((PsiClassType)type).resolveGenerics();
final PsiClass psiClass = classResolveResult.getElement();
if (psiClass != null) {
String name = psiClass.getName();
if (name != null) {
final PsiSubstitutor substitutor = classResolveResult.getSubstitutor();
PsiClass resolved = JavaPsiFacade.getInstance(psiClass.getProject()).getResolveHelper().resolveReferencedClass(name, context);
Set<String> allStrings = new HashSet<String>();
allStrings.add(name);
if (!psiClass.getManager().areElementsEquivalent(resolved, psiClass) && !PsiUtil.isInnerClass(psiClass)) {
// inner class name should be shown qualified if its not accessible by single name
PsiClass aClass = psiClass.getContainingClass();
while (aClass != null && !PsiUtil.isInnerClass(aClass) && aClass.getName() != null) {
name = aClass.getName() + '.' + name;
allStrings.add(name);
aClass = aClass.getContainingClass();
}
}
PsiTypeLookupItem item = new PsiTypeLookupItem(psiClass, name, diamond, bracketsCount, importFixer);
item.addLookupStrings(ArrayUtil.toStringArray(allStrings));
item.setAttribute(SUBSTITUTOR, substitutor);
return item;
}
}
}
return new PsiTypeLookupItem(type, type.getPresentableText(), false, bracketsCount, importFixer);
}
public static boolean isDiamond(PsiType type) {
boolean diamond = false;
if (type instanceof PsiClassReferenceType) {
final PsiReferenceParameterList parameterList = ((PsiClassReferenceType)type).getReference().getParameterList();
if (parameterList != null) {
final PsiTypeElement[] typeParameterElements = parameterList.getTypeParameterElements();
diamond = typeParameterElements.length == 1 && typeParameterElements[0].getType() instanceof PsiDiamondType;
}
}
return diamond;
}
@NotNull
private PsiSubstitutor getSubstitutor() {
PsiSubstitutor attribute = (PsiSubstitutor)getAttribute(SUBSTITUTOR);
return attribute != null ? attribute : PsiSubstitutor.EMPTY;
}
@Override
public void renderElement(LookupElementPresentation presentation) {
final Object object = getObject();
if (object instanceof PsiClass) {
JavaPsiClassReferenceElement.renderClassItem(presentation, this, (PsiClass)object, myDiamond);
} else {
assert object instanceof PsiType;
if (!(object instanceof PsiPrimitiveType)) {
presentation.setIcon(DefaultLookupItemRenderer.getRawIcon(this, presentation.isReal()));
}
presentation.setItemText(((PsiType)object).getCanonicalText());
presentation.setItemTextBold(getAttribute(LookupItem.HIGHLIGHTED_ATTR) != null || object instanceof PsiPrimitiveType);
if (isAddArrayInitializer()) {
presentation.setTailText("{...}");
}
}
if (myBracketsCount > 0) {
presentation.setTailText(StringUtil.repeat("[]", myBracketsCount) + StringUtil.notNullize(presentation.getTailText()), true);
}
}
public static void addImportForItem(InsertionContext context, PsiClass aClass) {
if (aClass.getQualifiedName() == null) return;
PsiFile file = context.getFile();
int startOffset = context.getStartOffset();
int tail = context.getTailOffset();
int newTail = JavaCompletionUtil.insertClassReference(aClass, file, startOffset, tail);
if (newTail > context.getDocument().getTextLength() || newTail < 0) {
LOG.error(LogMessageEx.createEvent("Invalid offset after insertion ",
"offset=" + newTail + "\n" +
"start=" + startOffset + "\n" +
"tail=" + tail + "\n" +
"file.length=" + file.getTextLength() + "\n" +
"document=" + context.getDocument() + "\n" +
DebugUtil.currentStackTrace(),
AttachmentFactory.createAttachment(context.getDocument())));
return;
}
context.setTailOffset(newTail);
JavaCompletionUtil.shortenReference(file, context.getStartOffset());
PostprocessReformattingAspect.getInstance(context.getProject()).doPostponedFormatting();
}
}