/* * Copyright 2000-2012 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.tree.java; import com.intellij.codeInsight.ExceptionUtil; import com.intellij.lang.ASTNode; import com.intellij.openapi.diagnostic.Logger; import com.intellij.pom.java.LanguageLevel; import com.intellij.psi.*; import com.intellij.psi.impl.source.Constants; import com.intellij.psi.impl.source.tree.ChildRole; import com.intellij.psi.impl.source.tree.CompositePsiElement; import com.intellij.psi.scope.PsiScopeProcessor; import com.intellij.psi.scope.util.PsiScopesUtil; import com.intellij.psi.tree.ChildRoleBase; import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.*; import com.intellij.util.NullableFunction; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collection; import java.util.Collections; import java.util.List; /** * @author ven */ public class PsiCatchSectionImpl extends CompositePsiElement implements PsiCatchSection, Constants { private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.tree.java.PsiCatchSectionImpl"); private final Object myTypesCacheLock = new Object(); private CachedValue<List<PsiType>> myTypesCache = null; public PsiCatchSectionImpl() { super(CATCH_SECTION); } @Override public PsiParameter getParameter() { return (PsiParameter)findChildByRoleAsPsiElement(ChildRole.PARAMETER); } @Override public PsiCodeBlock getCatchBlock() { return (PsiCodeBlock)findChildByRoleAsPsiElement(ChildRole.CATCH_BLOCK); } @Override public PsiType getCatchType() { PsiParameter parameter = getParameter(); if (parameter == null) return null; return parameter.getType(); } @Override @NotNull public List<PsiType> getPreciseCatchTypes() { final PsiParameter parameter = getParameter(); if (parameter == null) return Collections.emptyList(); return getTypesCache().getValue(); } @Override public void clearCaches() { super.clearCaches(); synchronized (myTypesCacheLock) { myTypesCache = null; } } private CachedValue<List<PsiType>> getTypesCache() { synchronized (myTypesCacheLock) { if (myTypesCache == null) { final CachedValuesManager cacheManager = CachedValuesManager.getManager(getProject()); myTypesCache = cacheManager.createCachedValue(new CachedValueProvider<List<PsiType>>() { @Override public Result<List<PsiType>> compute() { final List<PsiType> types = computePreciseCatchTypes(getParameter()); return Result.create(types, PsiModificationTracker.MODIFICATION_COUNT); } }, false); } return myTypesCache; } } private List<PsiType> computePreciseCatchTypes(@Nullable final PsiParameter parameter) { if (parameter == null) { return ContainerUtil.emptyList(); } PsiType declaredType = parameter.getType(); // When the thrown expression is an ... exception parameter Ej (parameter) of a catch clause Cj (this) ... if (PsiUtil.getLanguageLevel(parameter).isAtLeast(LanguageLevel.JDK_1_7) && isCatchParameterEffectivelyFinal(parameter, getCatchBlock())) { PsiTryStatement statement = getTryStatement(); // ... and the try block of the try statement which declares Cj (tryBlock) can throw T ... Collection<PsiClassType> thrownTypes = getThrownTypes(statement); if (thrownTypes.isEmpty()) return Collections.emptyList(); // ... and for all exception parameters Ei declared by any catch clauses Ci, 1 <= i < j, // declared to the left of Cj for the same try statement, T is not assignable to Ei ... final PsiParameter[] parameters = statement.getCatchBlockParameters(); List<PsiType> uncaughtTypes = ContainerUtil.mapNotNull(thrownTypes, new NullableFunction<PsiClassType, PsiType>() { @Override public PsiType fun(final PsiClassType thrownType) { for (int i = 0; i < parameters.length && parameters[i] != parameter; i++) { final PsiType catchType = parameters[i].getType(); if (catchType.isAssignableFrom(thrownType)) return null; } return thrownType; } }); // ... and T is assignable to Ej ... boolean passed = true; for (PsiType type : uncaughtTypes) { if (!declaredType.isAssignableFrom(type)) { passed = false; break; } } // ... the throw statement throws precisely the set of exception types T. if (passed) return uncaughtTypes; } return Collections.singletonList(declaredType); } private static Collection<PsiClassType> getThrownTypes(@NotNull PsiTryStatement statement) { Collection<PsiClassType> types = ContainerUtil.newArrayList(); PsiCodeBlock tryBlock = statement.getTryBlock(); if (tryBlock != null) { types.addAll(ExceptionUtil.getThrownExceptions(tryBlock)); } PsiResourceList resourceList = statement.getResourceList(); if (resourceList != null) { types.addAll(ExceptionUtil.getThrownExceptions(resourceList)); } return types; } // do not use control flow here to avoid dead loop private static boolean isCatchParameterEffectivelyFinal(final PsiParameter parameter, @Nullable final PsiCodeBlock catchBlock) { final boolean[] result = {true}; if (catchBlock != null) { catchBlock.accept(new JavaRecursiveElementWalkingVisitor() { @Override public void visitReferenceExpression(PsiReferenceExpression expression) { super.visitReferenceExpression(expression); if (expression.resolve() == parameter && PsiUtil.isAccessedForWriting(expression)) { result[0] = false; stopWalking(); } } }); } return result[0]; } @Override @NotNull public PsiTryStatement getTryStatement() { return (PsiTryStatement)getParent(); } @Override @Nullable public PsiJavaToken getLParenth() { return (PsiJavaToken)findChildByRole(ChildRole.CATCH_BLOCK_PARAMETER_LPARENTH); } @Override @Nullable public PsiJavaToken getRParenth() { return (PsiJavaToken)findChildByRole(ChildRole.CATCH_BLOCK_PARAMETER_RPARENTH); } @Override public void accept(@NotNull PsiElementVisitor visitor) { if (visitor instanceof JavaElementVisitor) { ((JavaElementVisitor)visitor).visitCatchSection(this); } else { visitor.visitElement(this); } } public String toString() { return "PsiCatchSection"; } @Override public ASTNode findChildByRole(int role) { switch(role) { default: return null; case ChildRole.PARAMETER: return findChildByType(PARAMETER); case ChildRole.CATCH_KEYWORD: return findChildByType(CATCH_KEYWORD); case ChildRole.CATCH_BLOCK_PARAMETER_LPARENTH: return findChildByType(LPARENTH); case ChildRole.CATCH_BLOCK_PARAMETER_RPARENTH: return findChildByType(RPARENTH); case ChildRole.CATCH_BLOCK: return findChildByType(CODE_BLOCK); } } @Override public int getChildRole(ASTNode child) { LOG.assertTrue(child.getTreeParent() == this); IElementType i = child.getElementType(); if (i == PARAMETER) { return ChildRole.PARAMETER; } else if (i == CODE_BLOCK) { return ChildRole.CATCH_BLOCK; } else if (i == CATCH_KEYWORD) { return ChildRole.CATCH_KEYWORD; } else if (i == LPARENTH) { return ChildRole.CATCH_BLOCK_PARAMETER_LPARENTH; } else if (i == RPARENTH) { return ChildRole.CATCH_BLOCK_PARAMETER_RPARENTH; } return ChildRoleBase.NONE; } @Override public boolean processDeclarations(@NotNull PsiScopeProcessor processor, @NotNull ResolveState state, PsiElement lastParent, @NotNull PsiElement place) { processor.handleEvent(PsiScopeProcessor.Event.SET_DECLARATION_HOLDER, this); if (lastParent == null || lastParent.getParent() != this) // Parent element should not see our vars return true; final PsiParameter catchParameter = getParameter(); if (catchParameter != null) { return processor.execute(catchParameter, state); } return PsiScopesUtil.walkChildrenScopes(this, processor, state, lastParent, place); } }