blob: 0ce963e1eba7f89b8a162a1eb230ef9a398ea10d [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 org.jetbrains.plugins.groovy.lang.resolve;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.PsiElement;
import com.intellij.psi.ResolveState;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.psi.util.PsiModificationTracker;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.PairProcessor;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock;
import org.jetbrains.plugins.groovy.lang.resolve.processors.ClassHint;
import org.jetbrains.plugins.groovy.lang.resolve.processors.GrScopeProcessorWithHints;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.concurrent.ConcurrentMap;
/**
* @author peter
*/
class DeclarationCacheKey {
private static final CachedValueProvider<ConcurrentMap<DeclarationCacheKey, List<DeclarationHolder>>> VALUE_PROVIDER =
new CachedValueProvider<ConcurrentMap<DeclarationCacheKey, List<DeclarationHolder>>>() {
@Nullable
@Override
public Result<ConcurrentMap<DeclarationCacheKey, List<DeclarationHolder>>> compute() {
ConcurrentMap<DeclarationCacheKey, List<DeclarationHolder>> map = ContainerUtil.newConcurrentMap();
return Result.create(map, PsiModificationTracker.MODIFICATION_COUNT);
}
};
@Nullable private final String name;
@NotNull private final EnumSet<ClassHint.ResolveKind> kinds;
private final boolean nonCode;
@NotNull private final PsiElement place;
DeclarationCacheKey(@Nullable String name, ClassHint hint, boolean nonCode, @NotNull PsiElement place) {
this.name = name;
this.kinds = getResolveKinds(hint);
this.nonCode = nonCode;
this.place = place;
}
private static EnumSet<ClassHint.ResolveKind> getResolveKinds(ClassHint hint) {
EnumSet<ClassHint.ResolveKind> set = EnumSet.noneOf(ClassHint.ResolveKind.class);
for (ClassHint.ResolveKind kind : ClassHint.ResolveKind.values()) {
if (hint.shouldProcess(kind)) {
set.add(kind);
}
}
return set;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof DeclarationCacheKey)) {
return false;
}
DeclarationCacheKey key = (DeclarationCacheKey)o;
if (nonCode != key.nonCode) {
return false;
}
if (!kinds.equals(key.kinds)) {
return false;
}
if (name != null ? !name.equals(key.name) : key.name != null) {
return false;
}
if (place != key.place) return false;
return true;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + kinds.hashCode();
result = 31 * result + (nonCode ? 1 : 0);
result = 31* result + place.hashCode();
return result;
}
@Override
public String toString() {
return "DeclarationCacheKey{" +
"name='" + name + '\'' +
", kinds=" + kinds +
", nonCode=" + nonCode +
", place=" + place.toString() +
'}';
}
private List<DeclarationHolder> collectDeclarations(final PsiElement place) {
final ArrayList<DeclarationHolder> result = new ArrayList<DeclarationHolder>();
PsiTreeUtil.treeWalkUp(place, null, new PairProcessor<PsiElement, PsiElement>() {
@Override
public boolean process(PsiElement scope, PsiElement lastParent) {
result.add(collectScopeDeclarations(scope, lastParent));
if (nonCode && scope instanceof GrClosableBlock) return false; //closures tree walk up themselves if non code declarataions are acepted
return true;
}
});
return result;
}
private DeclarationHolder collectScopeDeclarations(PsiElement scope, PsiElement lastParent) {
MyCollectProcessor plainCollector = new MyCollectProcessor();
MyCollectProcessor nonCodeCollector = new MyCollectProcessor();
ResolveUtil.doProcessDeclarations(place, lastParent, scope, plainCollector, nonCode ? nonCodeCollector : null, ResolveState.initial());
return new DeclarationHolder(scope, plainCollector.declarations, nonCodeCollector.declarations);
}
private List<DeclarationHolder> getAllDeclarations(PsiElement place) {
ConcurrentMap<DeclarationCacheKey, List<DeclarationHolder>> cache = CachedValuesManager.getCachedValue(place, VALUE_PROVIDER);
List<DeclarationHolder> declarations = cache.get(this);
if (declarations == null) {
declarations = collectDeclarations(place);
cache.putIfAbsent(this, declarations);
}
return declarations;
}
boolean processCachedDeclarations(PsiElement place, PsiScopeProcessor processor) {
for (DeclarationHolder holder : getAllDeclarations(place)) {
ProgressManager.checkCanceled();
if (!holder.processCachedDeclarations(processor)) {
return false;
}
}
return true;
}
private static class DeclarationHolder {
final PsiElement scope;
final List<Pair<PsiElement, ResolveState>> plainDeclarations;
final List<Pair<PsiElement, ResolveState>> nonCodeDeclarations;
private DeclarationHolder(PsiElement scope,
List<Pair<PsiElement, ResolveState>> plainDeclarations,
List<Pair<PsiElement, ResolveState>> nonCodeDeclarations) {
this.scope = scope;
this.plainDeclarations = plainDeclarations;
this.nonCodeDeclarations = nonCodeDeclarations;
}
boolean processCachedDeclarations(PsiScopeProcessor processor) {
PsiScopeProcessor realProcessor = ResolveUtil.substituteProcessor(processor, scope);
for (Pair<PsiElement, ResolveState> pair : plainDeclarations) {
if (!realProcessor.execute(pair.first, pair.second)) {
return false;
}
}
for (Pair<PsiElement, ResolveState> pair : nonCodeDeclarations) {
if (!processor.execute(pair.first, pair.second)) {
return false;
}
}
ResolveUtil.issueLevelChangeEvents(processor, scope);
return true;
}
@Override
public String toString() {
return "[scope=" + scope.toString() + ", plain=" + plainDeclarations.size() + ", nonCode=" + nonCodeDeclarations.size();
}
}
private class MyCollectProcessor extends GrScopeProcessorWithHints {
final List<Pair<PsiElement, ResolveState>> declarations = ContainerUtil.newArrayList();
public MyCollectProcessor() {
super(DeclarationCacheKey.this.name, DeclarationCacheKey.this.kinds);
}
@Override
public boolean execute(@NotNull PsiElement element, @NotNull ResolveState state) {
declarations.add(Pair.create(element, state));
return true;
}
}
}