blob: 9c7ebcfa0e407fbaae3e39dc509e6a648f6c86b1 [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.psi.impl.source;
import com.intellij.lang.ASTNode;
import com.intellij.psi.*;
import com.intellij.psi.impl.PsiImplUtil;
import com.intellij.psi.impl.source.codeStyle.CodeEditUtil;
import com.intellij.psi.impl.source.tree.CompositePsiElement;
import com.intellij.psi.impl.source.tree.ElementType;
import com.intellij.psi.impl.source.tree.JavaElementType;
import com.intellij.psi.impl.source.tree.TreeElement;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.Function;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.SmartList;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
public class PsiTypeElementImpl extends CompositePsiElement implements PsiTypeElement {
private volatile PsiType myCachedType = null;
@SuppressWarnings({"UnusedDeclaration"})
public PsiTypeElementImpl() {
this(JavaElementType.TYPE);
}
protected PsiTypeElementImpl(IElementType type) {
super(type);
}
@Override
public void clearCaches() {
super.clearCaches();
myCachedType = null;
}
@Override
public void accept(@NotNull PsiElementVisitor visitor) {
if (visitor instanceof JavaElementVisitor) {
((JavaElementVisitor)visitor).visitTypeElement(this);
}
else {
visitor.visitElement(this);
}
}
@Override
@NotNull
public PsiType getType() {
PsiType cachedType = myCachedType;
if (cachedType != null) return cachedType;
cachedType = calculateType();
myCachedType = cachedType;
return cachedType;
}
private PsiType calculateType() {
PsiType type = null;
SmartList<PsiAnnotation> annotations = new SmartList<PsiAnnotation>();
for (PsiElement child = getFirstChild(); child != null; child = child.getNextSibling()) {
if (child instanceof PsiComment || child instanceof PsiWhiteSpace) continue;
if (child instanceof PsiAnnotation) {
annotations.add((PsiAnnotation)child);
}
else if (child instanceof PsiTypeElement) {
assert type == null : this;
if (child instanceof PsiDiamondTypeElementImpl) {
type = new PsiDiamondTypeImpl(getManager(), this);
break;
}
else {
type = ((PsiTypeElement)child).getType();
}
}
else if (PsiUtil.isJavaToken(child, ElementType.PRIMITIVE_TYPE_BIT_SET)) {
assert type == null : this;
addTypeUseAnnotations(annotations);
PsiAnnotation[] array = ContainerUtil.copyAndClear(annotations, PsiAnnotation.ARRAY_FACTORY, true);
type = JavaPsiFacade.getInstance(getProject()).getElementFactory().createPrimitiveType(child.getText(), array);
}
else if (child instanceof PsiJavaCodeReferenceElement) {
assert type == null : this;
addTypeUseAnnotations(annotations);
PsiAnnotation[] array = ContainerUtil.copyAndClear(annotations, PsiAnnotation.ARRAY_FACTORY, true);
type = new PsiClassReferenceType((PsiJavaCodeReferenceElement)child, null, array);
}
else if (PsiUtil.isJavaToken(child, JavaTokenType.LBRACKET)) {
assert type != null : this;
PsiAnnotation[] array = ContainerUtil.copyAndClear(annotations, PsiAnnotation.ARRAY_FACTORY, true);
type = type.createArrayType(array);
}
else if (PsiUtil.isJavaToken(child, JavaTokenType.ELLIPSIS)) {
assert type != null : this;
PsiAnnotation[] array = ContainerUtil.copyAndClear(annotations, PsiAnnotation.ARRAY_FACTORY, true);
type = PsiEllipsisType.createEllipsis(type, array);
}
if (PsiUtil.isJavaToken(child, JavaTokenType.QUEST)) {
assert type == null : this;
PsiElement boundKind = PsiTreeUtil.skipSiblingsForward(child, PsiComment.class, PsiWhiteSpace.class);
PsiElement boundType = PsiTreeUtil.skipSiblingsForward(boundKind, PsiComment.class, PsiWhiteSpace.class);
if (PsiUtil.isJavaToken(boundKind, JavaTokenType.EXTENDS_KEYWORD) && boundType instanceof PsiTypeElement) {
type = PsiWildcardType.createExtends(getManager(), ((PsiTypeElement)boundType).getType());
}
else if (PsiUtil.isJavaToken(boundKind, JavaTokenType.SUPER_KEYWORD) && boundType instanceof PsiTypeElement) {
type = PsiWildcardType.createSuper(getManager(), ((PsiTypeElement)boundType).getType());
}
else {
type = PsiWildcardType.createUnbounded(getManager());
}
PsiAnnotation[] array = ContainerUtil.copyAndClear(annotations, PsiAnnotation.ARRAY_FACTORY, true);
type = ((PsiWildcardType)type).annotate(array);
break;
}
if (PsiUtil.isJavaToken(child, JavaTokenType.AND)) {
List<PsiType> types = collectTypes();
assert !types.isEmpty() : this;
type = PsiIntersectionType.createIntersection(types);
break;
}
if (PsiUtil.isJavaToken(child, JavaTokenType.OR)) {
List<PsiType> types = collectTypes();
assert !types.isEmpty() : this;
type = PsiDisjunctionType.createDisjunction(types, getManager());
break;
}
}
return type == null ? PsiType.NULL : type;
}
private void addTypeUseAnnotations(List<PsiAnnotation> list) {
PsiElement parent = this;
while (parent instanceof PsiTypeElement) {
PsiElement left = PsiTreeUtil.skipSiblingsBackward(parent, PsiComment.class, PsiWhiteSpace.class, PsiAnnotation.class);
if (left instanceof PsiModifierList) {
List<PsiAnnotation> annotations = PsiImplUtil.getTypeUseAnnotations((PsiModifierList)left);
if (annotations != null && !annotations.isEmpty()) {
list.addAll(annotations);
}
break;
}
if (left != null) break;
parent = parent.getParent();
}
}
private List<PsiType> collectTypes() {
List<PsiTypeElement> typeElements = PsiTreeUtil.getChildrenOfTypeAsList(this, PsiTypeElement.class);
return ContainerUtil.map(typeElements, new Function<PsiTypeElement, PsiType>() {
@Override
public PsiType fun(PsiTypeElement typeElement) {
return typeElement.getType();
}
});
}
@Override
public PsiJavaCodeReferenceElement getInnermostComponentReferenceElement() {
TreeElement firstChildNode = getFirstChildNode();
if (firstChildNode == null) return null;
if (firstChildNode.getElementType() == JavaElementType.TYPE) {
return SourceTreeToPsiMap.<PsiTypeElement>treeToPsiNotNull(firstChildNode).getInnermostComponentReferenceElement();
}
else {
return getReferenceElement();
}
}
@Nullable
private PsiJavaCodeReferenceElement getReferenceElement() {
ASTNode ref = findChildByType(JavaElementType.JAVA_CODE_REFERENCE);
if (ref == null) return null;
return (PsiJavaCodeReferenceElement)SourceTreeToPsiMap.treeElementToPsi(ref);
}
@Override
public boolean processDeclarations(@NotNull PsiScopeProcessor processor,
@NotNull ResolveState state,
PsiElement lastParent,
@NotNull PsiElement place) {
processor.handleEvent(PsiScopeProcessor.Event.SET_DECLARATION_HOLDER, this);
return true;
}
@Override
@NotNull
public PsiAnnotation[] getAnnotations() {
PsiAnnotation[] annotations = PsiTreeUtil.getChildrenOfType(this, PsiAnnotation.class);
return annotations != null ? annotations : PsiAnnotation.EMPTY_ARRAY;
}
@Override
@NotNull
public PsiAnnotation[] getApplicableAnnotations() {
List<PsiAnnotation> annotations = PsiTreeUtil.getChildrenOfTypeAsList(this, PsiAnnotation.class);
addTypeUseAnnotations(annotations);
return annotations.toArray(PsiAnnotation.ARRAY_FACTORY.create(annotations.size()));
}
@Override
public PsiAnnotation findAnnotation(@NotNull @NonNls String qualifiedName) {
return PsiImplUtil.findAnnotation(this, qualifiedName);
}
@Override
@NotNull
public PsiAnnotation addAnnotation(@NotNull @NonNls String qualifiedName) {
throw new UnsupportedOperationException();//todo
}
@Override
public PsiElement replace(@NotNull PsiElement newElement) throws IncorrectOperationException {
// neighbouring type annotations are logical part of this type element and should be dropped
PsiImplUtil.markTypeAnnotations(this);
PsiElement result = super.replace(newElement);
PsiImplUtil.deleteTypeAnnotations((PsiTypeElement)result);
// We want to reformat method call arguments on method return type change because there is a possible situation that they are aligned
// and the change breaks the alignment.
// Example:
// Object test(1,
// 2) {}
// Suppose we're changing return type to 'MyCustomClass'. We get the following if parameter list is not reformatted:
// MyCustomClass test(1,
// 2) {}
PsiElement parent = result.getParent();
if (parent instanceof PsiMethod) {
PsiMethod method = (PsiMethod)parent;
CodeEditUtil.markToReformat(method.getParameterList().getNode(), true);
}
// We cover situation like below here:
// int test(int i, int j) {}
// ...
// int i = test(1,
// 2);
// I.e. the point is to avoid code like below during changing 'test()' return type from 'int' to 'long':
// long i = test(1,
// 2);
else if (parent instanceof PsiVariable) {
PsiVariable variable = (PsiVariable)parent;
if (variable.hasInitializer()) {
PsiExpression methodCallCandidate = variable.getInitializer();
if (methodCallCandidate instanceof PsiMethodCallExpression) {
PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)methodCallCandidate;
CodeEditUtil.markToReformat(methodCallExpression.getArgumentList().getNode(), true);
}
}
}
return result;
}
@Override
public String toString() {
return "PsiTypeElement:" + getText();
}
}