ComponentWithBrowseButton - optional remove listener on hide
[fedora-idea.git] / platform / lang-impl / src / com / intellij / semantic / SemServiceImpl.java
blob1cbd7e0d39fdc7ee42c2fc891b18b25505d7c512
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.semantic;
18 import com.intellij.ProjectTopics;
19 import com.intellij.openapi.application.ApplicationManager;
20 import com.intellij.openapi.project.Project;
21 import com.intellij.patterns.ElementPattern;
22 import com.intellij.psi.PsiElement;
23 import com.intellij.psi.PsiManager;
24 import com.intellij.psi.impl.PsiManagerEx;
25 import com.intellij.psi.util.PsiModificationTracker;
26 import com.intellij.util.ConcurrencyUtil;
27 import com.intellij.util.NullableFunction;
28 import com.intellij.util.Processor;
29 import com.intellij.util.SmartList;
30 import com.intellij.util.containers.ConcurrentHashMap;
31 import com.intellij.util.containers.ContainerUtil;
32 import com.intellij.util.containers.MultiMap;
33 import gnu.trove.THashMap;
34 import org.jetbrains.annotations.NotNull;
35 import org.jetbrains.annotations.Nullable;
37 import java.util.*;
38 import java.util.concurrent.ConcurrentMap;
40 /**
41 * @author peter
43 @SuppressWarnings({"unchecked"})
44 public class SemServiceImpl extends SemService{
45 private static final Comparator<SemKey> KEY_COMPARATOR = new Comparator<SemKey>() {
46 public int compare(SemKey o1, SemKey o2) {
47 return o2.getUniqueId() - o1.getUniqueId();
50 private final ConcurrentMap<PsiElement, ConcurrentMap<SemKey, List<SemElement>>> myCache = new ConcurrentHashMap<PsiElement, ConcurrentMap<SemKey, List<SemElement>>>();
51 private final Map<SemKey, Collection<NullableFunction<PsiElement, ? extends SemElement>>> myProducers = new THashMap<SemKey, Collection<NullableFunction<PsiElement,? extends SemElement>>>();
52 private final MultiMap<SemKey, SemKey> myInheritors = new MultiMap<SemKey, SemKey>();
53 private final Project myProject;
55 private volatile boolean myInitialized = false;
56 private boolean myBulkChange = false;
58 public SemServiceImpl(Project project, PsiManager psiManager) {
59 myProject = project;
60 project.getMessageBus().connect().subscribe(ProjectTopics.MODIFICATION_TRACKER, new PsiModificationTracker.Listener() {
61 public void modificationCountChanged() {
62 if (!isInsideAtomicChange()) {
63 clearCache();
66 });
67 ((PsiManagerEx)psiManager).registerRunnableToRunOnChange(new Runnable() {
68 public void run() {
69 if (!isInsideAtomicChange()) {
70 clearCache();
73 });
76 public void clearCache() {
77 myCache.clear();
80 @Override
81 public void performAtomicChange(@NotNull Runnable change) {
82 ApplicationManager.getApplication().assertWriteAccessAllowed();
84 final boolean oldValue = myBulkChange;
85 myBulkChange = true;
86 try {
87 change.run();
89 finally {
90 myBulkChange = oldValue;
91 if (!oldValue) {
92 clearCache();
97 @Override
98 public boolean isInsideAtomicChange() {
99 return myBulkChange;
102 private void ensureInitialized() {
103 if (myInitialized) return;
105 final MultiMap<SemKey, NullableFunction<PsiElement, ? extends SemElement>> map = new MultiMap<SemKey, NullableFunction<PsiElement, ? extends SemElement>>();
107 final SemRegistrar registrar = new SemRegistrar() {
108 public <T extends SemElement, V extends PsiElement> void registerSemElementProvider(SemKey<T> key,
109 final ElementPattern<? extends V> place,
110 final NullableFunction<V, T> provider) {
111 map.putValue(key, new NullableFunction<PsiElement, SemElement>() {
112 public SemElement fun(PsiElement element) {
113 if (place.accepts(element)) {
114 return provider.fun((V)element);
116 return null;
121 for (SemContributor contributor : myProject.getExtensions(SemContributor.EP_NAME)) {
122 contributor.registerSemProviders(registrar);
125 synchronized (myCache) {
126 if (myInitialized) return;
128 assert myProducers.isEmpty();
129 assert myInheritors.isEmpty();
131 SemKey[] allKeys = map.keySet().toArray(new SemKey[map.size()]);
132 for (final SemKey key : allKeys) {
133 myProducers.put(key, map.get(key));
135 ContainerUtil.process(allKeys, new Processor<SemKey>() {
136 public boolean process(SemKey key) {
137 myInheritors.putValue(key, key);
138 for (SemKey parent : key.getSupers()) {
139 myInheritors.putValue(parent, key);
140 process(parent);
142 return true;
145 for (final SemKey each : myInheritors.keySet()) {
146 final List<SemKey> inheritors = new ArrayList<SemKey>(myInheritors.get(each));
147 Collections.sort(inheritors, KEY_COMPARATOR);
148 myInheritors.put(each, inheritors);
151 myInitialized = true;
155 @Nullable
156 public <T extends SemElement> List<T> getSemElements(SemKey<T> key, @NotNull PsiElement psi) {
157 ensureInitialized();
159 List<T> cached = _getCachedSemElements(key, psi, true);
160 if (cached != null) {
161 return cached;
164 final ConcurrentMap<SemKey, List<SemElement>> map = cacheOrGetMap(psi);
165 LinkedHashSet<T> result = null;
166 for (final SemKey each : myInheritors.get(key)) {
167 List<SemElement> list = ConcurrencyUtil.cacheOrGet(map, each, createSemElements(each, psi));
168 if (!list.isEmpty()) {
169 if (result == null) result = new LinkedHashSet<T>();
170 result.addAll((List<T>)list);
173 return result == null ? Collections.<T>emptyList() : new ArrayList<T>(result);
176 @NotNull
177 private List<SemElement> createSemElements(SemKey key, PsiElement psi) {
178 List<SemElement> result = null;
179 final Collection<NullableFunction<PsiElement, ? extends SemElement>> producers = myProducers.get(key);
180 if (producers != null && !producers.isEmpty()) {
181 for (final NullableFunction<PsiElement, ? extends SemElement> producer : producers) {
182 final SemElement element = producer.fun(psi);
183 if (element != null) {
184 if (result == null) result = new SmartList<SemElement>();
185 result.add(element);
189 return result == null ? Collections.<SemElement>emptyList() : Collections.unmodifiableList(result);
192 @Nullable
193 public <T extends SemElement> List<T> getCachedSemElements(SemKey<T> key, @NotNull PsiElement psi) {
194 return _getCachedSemElements(key, psi, false);
197 @Nullable
198 private <T extends SemElement> List<T> _getCachedSemElements(SemKey<T> key, PsiElement psi, boolean paranoid) {
199 final ConcurrentMap<SemKey, List<SemElement>> map = myCache.get(psi);
200 if (map == null) return null;
202 List<T> singleList = null;
203 LinkedHashSet<T> result = null;
204 final List<SemKey> inheritors = (List<SemKey>)myInheritors.get(key);
205 //noinspection ForLoopReplaceableByForEach
206 for (int i = 0; i < inheritors.size(); i++) {
207 List<T> cached = (List<T>)map.get(inheritors.get(i));
209 if (cached == null && paranoid) {
210 return null;
213 if (cached != null && cached != Collections.EMPTY_LIST) {
214 if (singleList == null) {
215 singleList = cached;
216 continue;
219 if (result == null) {
220 result = new LinkedHashSet<T>(singleList);
222 result.addAll(cached);
227 if (result == null) {
228 if (singleList != null) {
229 return singleList;
232 return Collections.emptyList();
235 return new ArrayList<T>(result);
238 public <T extends SemElement> void setCachedSemElement(SemKey<T> key, @NotNull PsiElement psi, @Nullable T semElement) {
239 cacheOrGetMap(psi).put(key, ContainerUtil.<SemElement>createMaybeSingletonList(semElement));
242 @Override
243 public <T extends SemElement> void clearCachedSemElements(@NotNull PsiElement psi) {
244 myCache.remove(psi);
247 private ConcurrentMap<SemKey, List<SemElement>> cacheOrGetMap(PsiElement psi) {
248 ConcurrentMap<SemKey, List<SemElement>> map = myCache.get(psi);
249 if (map == null) {
250 map = ConcurrencyUtil.cacheOrGet(myCache, psi, new ConcurrentHashMap<SemKey, List<SemElement>>());
252 return map;