/** @author peter */ public class JavaChainLookupElement extends LookupElementDecorator<LookupElement> implements TypedLookupItem { private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.completion.JavaChainLookupElement"); public static final ClassConditionKey<JavaChainLookupElement> CLASS_CONDITION_KEY = ClassConditionKey.create(JavaChainLookupElement.class); private final LookupElement myQualifier; public JavaChainLookupElement(LookupElement qualifier, LookupElement main) { super(main); myQualifier = qualifier; } @NotNull @Override public String getLookupString() { return maybeAddParentheses(myQualifier.getLookupString()) + "." + getDelegate().getLookupString(); } public LookupElement getQualifier() { return myQualifier; } @Override public Set<String> getAllLookupStrings() { final Set<String> strings = getDelegate().getAllLookupStrings(); final THashSet<String> result = new THashSet<String>(); result.addAll(strings); result.add(getLookupString()); return result; } @NotNull @Override public String toString() { return maybeAddParentheses(myQualifier.toString()) + "." + getDelegate(); } private String maybeAddParentheses(String s) { return getQualifierObject() instanceof PsiMethod ? s + "()" : s; } @Nullable private Object getQualifierObject() { Object qObject = myQualifier.getObject(); if (qObject instanceof ResolveResult) { qObject = ((ResolveResult) qObject).getElement(); } return qObject; } @Override public void renderElement(LookupElementPresentation presentation) { super.renderElement(presentation); final LookupElementPresentation qualifierPresentation = new LookupElementPresentation(); myQualifier.renderElement(qualifierPresentation); String name = maybeAddParentheses(qualifierPresentation.getItemText()); final String qualifierText = myQualifier.as(CastingLookupElementDecorator.CLASS_CONDITION_KEY) != null ? "(" + name + ")" : name; presentation.setItemText(qualifierText + "." + presentation.getItemText()); if (myQualifier instanceof LookupItem && getQualifierObject() instanceof PsiClass) { String locationString = JavaPsiClassReferenceElement.getLocationString((LookupItem) myQualifier); presentation.setTailText(StringUtil.notNullize(presentation.getTailText()) + locationString); } } @Override public void handleInsert(InsertionContext context) { final Document document = context.getEditor().getDocument(); document.replaceString(context.getStartOffset(), context.getTailOffset(), ";"); final InsertionContext qualifierContext = CompletionUtil.emulateInsertion(context, context.getStartOffset(), myQualifier); OffsetKey oldStart = context.trackOffset(context.getStartOffset(), false); int start = CharArrayUtil.shiftForward( context.getDocument().getCharsSequence(), context.getStartOffset(), " \t\n"); if (shouldParenthesizeQualifier(context.getFile(), start, qualifierContext.getTailOffset())) { final String space = CodeStyleSettingsManager.getSettings(qualifierContext.getProject()) .SPACE_WITHIN_PARENTHESES ? " " : ""; document.insertString(start, "(" + space); document.insertString(qualifierContext.getTailOffset(), space + ")"); } final char atTail = document.getCharsSequence().charAt(context.getTailOffset() - 1); if (atTail != ';') { LOG.error( LogMessageEx.createEvent( "Unexpected character", "atTail=" + atTail + "\n" + "offset=" + context.getTailOffset() + "\n" + "item=" + this + "\n" + "item.class=" + this.getClass() + "\n" + DebugUtil.currentStackTrace(), AttachmentFactory.createAttachment(context.getDocument()))); } document.replaceString(context.getTailOffset() - 1, context.getTailOffset(), "."); CompletionUtil.emulateInsertion(getDelegate(), context.getTailOffset(), context); context.commitDocument(); int formatStart = context.getOffset(oldStart); int formatEnd = context.getTailOffset(); if (formatStart >= 0 && formatEnd >= 0) { CodeStyleManager.getInstance(context.getProject()) .reformatText(context.getFile(), formatStart, formatEnd); } } protected boolean shouldParenthesizeQualifier( final PsiFile file, final int startOffset, final int endOffset) { PsiElement element = file.findElementAt(startOffset); if (element == null) { return false; } PsiElement last = element; while (element != null && element.getTextRange().getStartOffset() >= startOffset && element.getTextRange().getEndOffset() <= endOffset && !(element instanceof PsiExpressionStatement)) { last = element; element = element.getParent(); } PsiExpression expr = PsiTreeUtil.getParentOfType(last, PsiExpression.class, false, PsiClass.class); if (expr == null) { return false; } if (expr.getTextRange().getEndOffset() > endOffset) { return true; } if (expr instanceof PsiJavaCodeReferenceElement || expr instanceof PsiMethodCallExpression || expr instanceof PsiArrayAccessExpression) { return false; } return true; } @NotNull private LookupElement getComparableQualifier() { final CastingLookupElementDecorator casting = myQualifier.as(CastingLookupElementDecorator.CLASS_CONDITION_KEY); return casting == null ? myQualifier : casting.getDelegate(); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; if (!super.equals(o)) return false; return getComparableQualifier().equals(((JavaChainLookupElement) o).getComparableQualifier()); } @Override public int hashCode() { return 31 * super.hashCode() + getComparableQualifier().hashCode(); } @Override public PsiType getType() { final Object object = getObject(); if (object instanceof PsiMember) { return JavaCompletionUtil.getQualifiedMemberReferenceType( JavaCompletionUtil.getLookupElementType(myQualifier), (PsiMember) object); } return ((PsiVariable) object).getType(); } }
/** @author peter */ public class PsiTypeLookupItem extends LookupItem implements TypedLookupItem { 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; @NotNull private final PsiSubstitutor mySubstitutor; private boolean myAddArrayInitializer; private String myLocationString = ""; private PsiTypeLookupItem( Object o, @NotNull @NonNls String lookupString, boolean diamond, int bracketsCount, InsertHandler<PsiTypeLookupItem> fixer, @NotNull PsiSubstitutor substitutor) { super(o, lookupString); myDiamond = diamond; myBracketsCount = bracketsCount; myImportFixer = fixer; mySubstitutor = substitutor; } @NotNull @Override public PsiType getType() { Object object = getObject(); PsiType type = object instanceof PsiType ? getSubstitutor().substitute((PsiType) object) : JavaPsiFacade.getElementFactory(((PsiClass) object).getProject()) .createType((PsiClass) object, getSubstitutor()); 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) { //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) { int dim = 0; while (type instanceof PsiArrayType) { type = ((PsiArrayType) type).getComponentType(); dim++; } return doCreateItem(type, context, dim, isDiamond, importFixer); } 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, substitutor); item.addLookupStrings(ArrayUtil.toStringArray(allStrings)); return item; } } } return new PsiTypeLookupItem( type, type.getPresentableText(), false, bracketsCount, importFixer, PsiSubstitutor.EMPTY); } 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() { return mySubstitutor; } @Override public void renderElement(LookupElementPresentation presentation) { final Object object = getObject(); if (object instanceof PsiClass) { JavaPsiClassReferenceElement.renderClassItem( presentation, this, (PsiClass) object, myDiamond, myLocationString, mySubstitutor); } else { assert object instanceof PsiType; if (!(object instanceof PsiPrimitiveType)) { presentation.setIcon(DefaultLookupItemRenderer.getRawIcon(this, presentation.isReal())); } presentation.setItemText(((PsiType) object).getCanonicalText()); presentation.setItemTextBold(object instanceof PsiPrimitiveType); if (isAddArrayInitializer()) { presentation.setTailText("{...}"); } } if (myBracketsCount > 0) { presentation.setTailText( StringUtil.repeat("[]", myBracketsCount) + StringUtil.notNullize(presentation.getTailText()), true); } } public PsiTypeLookupItem setShowPackage() { Object object = getObject(); if (object instanceof PsiClass) { myLocationString = " (" + PsiFormatUtil.getPackageDisplayName((PsiClass) object) + ")"; } return this; } 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(); } }
/** @author peter */ public class JavaPsiClassReferenceElement extends LookupItem<Object> { public static final ClassConditionKey<JavaPsiClassReferenceElement> CLASS_CONDITION_KEY = ClassConditionKey.create(JavaPsiClassReferenceElement.class); private final Object myClass; private volatile Reference<PsiClass> myCache; private final String myQualifiedName; private String myForcedPresentableName; public JavaPsiClassReferenceElement(PsiClass psiClass) { super(psiClass.getName(), psiClass.getName()); myClass = psiClass.getContainingFile().getVirtualFile() == null ? psiClass : PsiAnchor.create(psiClass); myQualifiedName = psiClass.getQualifiedName(); JavaCompletionUtil.setShowFQN(this); setInsertHandler(AllClassesGetter.TRY_SHORTENING); setTailType(TailType.NONE); } public String getForcedPresentableName() { return myForcedPresentableName; } @NotNull @Override public String getLookupString() { if (myForcedPresentableName != null) { return myForcedPresentableName; } return super.getLookupString(); } @Override public Set<String> getAllLookupStrings() { if (myForcedPresentableName != null) { return Collections.singleton(myForcedPresentableName); } return super.getAllLookupStrings(); } public void setForcedPresentableName(String forcedPresentableName) { myForcedPresentableName = forcedPresentableName; } @NotNull @Override public PsiClass getObject() { if (myClass instanceof PsiAnchor) { Reference<PsiClass> cache = myCache; if (cache != null) { PsiClass psiClass = cache.get(); if (psiClass != null) { return psiClass; } } final PsiClass retrieve = (PsiClass) ((PsiAnchor) myClass).retrieve(); assert retrieve != null : myQualifiedName; myCache = new WeakReference<PsiClass>(retrieve); return retrieve; } return (PsiClass) myClass; } @Override public boolean isValid() { if (myClass instanceof PsiClass) { return ((PsiClass) myClass).isValid(); } return ((PsiAnchor) myClass).retrieve() != null; } @Override public boolean equals(final Object o) { if (this == o) return true; if (!(o instanceof JavaPsiClassReferenceElement)) return false; final JavaPsiClassReferenceElement that = (JavaPsiClassReferenceElement) o; if (myQualifiedName != null) { return myQualifiedName.equals(that.myQualifiedName); } return Comparing.equal(myClass, that.myClass); } public String getQualifiedName() { return myQualifiedName; } @Override public int hashCode() { final String s = myQualifiedName; return s == null ? 239 : s.hashCode(); } @Override public void renderElement(LookupElementPresentation presentation) { LookupItem item = this; PsiClass psiClass = getObject(); renderClassItem(presentation, item, psiClass, false); } public static void renderClassItem( LookupElementPresentation presentation, LookupItem item, PsiClass psiClass, boolean diamond) { if (!(psiClass instanceof PsiTypeParameter)) { presentation.setIcon(DefaultLookupItemRenderer.getRawIcon(item, presentation.isReal())); } final boolean bold = item.getAttribute(LookupItem.HIGHLIGHTED_ATTR) != null; boolean strikeout = JavaElementLookupRenderer.isToStrikeout(item); presentation.setItemText(getName(psiClass, item, diamond)); presentation.setStrikeout(strikeout); presentation.setItemTextBold(bold); String tailText = StringUtil.notNullize((String) item.getAttribute(LookupItem.TAIL_TEXT_ATTR)); PsiSubstitutor substitutor = (PsiSubstitutor) item.getAttribute(LookupItem.SUBSTITUTOR); if (item instanceof PsiTypeLookupItem && ((PsiTypeLookupItem) item).isIndicateAnonymous() && (psiClass.isInterface() || psiClass.hasModifierProperty(PsiModifier.ABSTRACT))) { tailText = "{...}" + tailText; } if (substitutor == null && !diamond && psiClass.getTypeParameters().length > 0) { tailText = "<" + StringUtil.join( psiClass.getTypeParameters(), new Function<PsiTypeParameter, String>() { @Override public String fun(PsiTypeParameter psiTypeParameter) { return psiTypeParameter.getName(); } }, "," + (showSpaceAfterComma(psiClass) ? " " : "")) + ">" + tailText; } presentation.setTailText(tailText, true); } private static String getName( final PsiClass psiClass, final LookupItem<?> item, boolean diamond) { if (item instanceof JavaPsiClassReferenceElement) { String forced = ((JavaPsiClassReferenceElement) item).getForcedPresentableName(); if (forced != null) { return forced; } } String name = PsiUtilCore.getName(psiClass); if (item.getAttribute(LookupItem.FORCE_QUALIFY) != null) { if (psiClass.getContainingClass() != null) { name = psiClass.getContainingClass().getName() + "." + name; } } if (diamond) { return name + "<>"; } PsiSubstitutor substitutor = (PsiSubstitutor) item.getAttribute(LookupItem.SUBSTITUTOR); if (substitutor != null) { final PsiTypeParameter[] params = psiClass.getTypeParameters(); if (params.length > 0) { return name + formatTypeParameters(substitutor, params); } } return StringUtil.notNullize(name); } @Nullable private static String formatTypeParameters( @NotNull final PsiSubstitutor substitutor, final PsiTypeParameter[] params) { final boolean space = showSpaceAfterComma(params[0]); StringBuilder buffer = new StringBuilder(); buffer.append("<"); for (int i = 0; i < params.length; i++) { final PsiTypeParameter param = params[i]; final PsiType type = substitutor.substitute(param); if (type == null) { return ""; } if (type instanceof PsiClassType && ((PsiClassType) type).getParameters().length > 0) { buffer.append(((PsiClassType) type).rawType().getPresentableText()).append("<...>"); } else { buffer.append(type.getPresentableText()); } if (i < params.length - 1) { buffer.append(","); if (space) { buffer.append(" "); } } } buffer.append(">"); return buffer.toString(); } private static boolean showSpaceAfterComma(PsiClass element) { return CodeStyleSettingsManager.getSettings(element.getProject()).SPACE_AFTER_COMMA; } }