test isolation
[fedora-idea.git] / platform / lang-impl / src / com / intellij / psi / impl / source / tree / injected / InjectedLanguageManagerImpl.java
blobef1dc49630478536b190e37443ab3f2c258ebfb4
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.
17 package com.intellij.psi.impl.source.tree.injected;
19 import com.intellij.injected.editor.DocumentWindowImpl;
20 import com.intellij.injected.editor.VirtualFileWindow;
21 import com.intellij.lang.Language;
22 import com.intellij.lang.injection.InjectedLanguageManager;
23 import com.intellij.lang.injection.MultiHostInjector;
24 import com.intellij.lang.injection.MultiHostRegistrar;
25 import com.intellij.openapi.Disposable;
26 import com.intellij.openapi.editor.Document;
27 import com.intellij.openapi.extensions.ExtensionPoint;
28 import com.intellij.openapi.extensions.ExtensionPointListener;
29 import com.intellij.openapi.extensions.Extensions;
30 import com.intellij.openapi.extensions.PluginDescriptor;
31 import com.intellij.openapi.project.DumbAware;
32 import com.intellij.openapi.project.DumbService;
33 import com.intellij.openapi.project.Project;
34 import com.intellij.openapi.util.Disposer;
35 import com.intellij.openapi.util.Key;
36 import com.intellij.openapi.util.ProperTextRange;
37 import com.intellij.openapi.util.TextRange;
38 import com.intellij.openapi.vfs.VirtualFile;
39 import com.intellij.psi.*;
40 import com.intellij.psi.impl.PsiManagerEx;
41 import com.intellij.util.ArrayUtil;
42 import com.intellij.util.containers.ConcurrentHashMap;
43 import org.jetbrains.annotations.NonNls;
44 import org.jetbrains.annotations.NotNull;
45 import org.jetbrains.annotations.Nullable;
46 import org.jetbrains.annotations.TestOnly;
48 import java.util.*;
49 import java.util.concurrent.ConcurrentMap;
50 import java.util.concurrent.atomic.AtomicReference;
52 /**
53 * @author cdr
55 public class InjectedLanguageManagerImpl extends InjectedLanguageManager {
56 private final Project myProject;
57 private final DumbService myDumbService;
58 private final AtomicReference<MultiHostInjector> myPsiManagerRegisteredInjectorsAdapter = new AtomicReference<MultiHostInjector>();
60 public static InjectedLanguageManagerImpl getInstanceImpl(Project project) {
61 return (InjectedLanguageManagerImpl)InjectedLanguageManager.getInstance(project);
64 public InjectedLanguageManagerImpl(Project project, DumbService dumbService) {
65 myProject = project;
66 myDumbService = dumbService;
68 final ExtensionPoint<MultiHostInjector> multiPoint = Extensions.getArea(project).getExtensionPoint(MULTIHOST_INJECTOR_EP_NAME);
69 multiPoint.addExtensionPointListener(new ExtensionPointListener<MultiHostInjector>() {
70 public void extensionAdded(MultiHostInjector injector, @Nullable PluginDescriptor pluginDescriptor) {
71 registerMultiHostInjector(injector);
74 public void extensionRemoved(MultiHostInjector injector, @Nullable PluginDescriptor pluginDescriptor) {
75 unregisterMultiHostInjector(injector);
77 });
78 final ExtensionPointListener<LanguageInjector> myListener = new ExtensionPointListener<LanguageInjector>() {
79 public void extensionAdded(LanguageInjector extension, @Nullable PluginDescriptor pluginDescriptor) {
80 psiManagerInjectorsChanged();
83 public void extensionRemoved(LanguageInjector extension, @Nullable PluginDescriptor pluginDescriptor) {
84 psiManagerInjectorsChanged();
87 final ExtensionPoint<LanguageInjector> psiManagerPoint = Extensions.getRootArea().getExtensionPoint(LanguageInjector.EXTENSION_POINT_NAME);
88 psiManagerPoint.addExtensionPointListener(myListener);
89 Disposer.register(project, new Disposable() {
90 public void dispose() {
91 psiManagerPoint.removeExtensionPointListener(myListener);
93 });
96 public void projectOpened() {
99 public void projectClosed() {
102 public void psiManagerInjectorsChanged() {
103 PsiManagerEx psiManager = (PsiManagerEx)PsiManager.getInstance(myProject);
104 List<? extends LanguageInjector> injectors = psiManager.getLanguageInjectors();
105 LanguageInjector[] extensions = Extensions.getExtensions(LanguageInjector.EXTENSION_POINT_NAME);
106 if (injectors.isEmpty() && extensions.length == 0) {
107 MultiHostInjector prev = myPsiManagerRegisteredInjectorsAdapter.getAndSet(null);
108 if (prev != null) {
109 unregisterMultiHostInjector(prev);
112 else {
113 PsiManagerRegisteredInjectorsAdapter adapter = new PsiManagerRegisteredInjectorsAdapter(psiManager);
114 if (myPsiManagerRegisteredInjectorsAdapter.compareAndSet(null, adapter)) {
115 registerMultiHostInjector(adapter);
120 public PsiLanguageInjectionHost getInjectionHost(@NotNull PsiElement element) {
121 final PsiFile file = element.getContainingFile();
122 final VirtualFile virtualFile = file == null ? null : file.getVirtualFile();
123 if (virtualFile instanceof VirtualFileWindow) {
124 PsiElement host = file.getContext();
125 if (host != null) {
126 return (PsiLanguageInjectionHost)host;
129 return null;
132 @NotNull
133 public TextRange injectedToHost(@NotNull PsiElement element, @NotNull TextRange textRange) {
134 ProperTextRange.assertProperRange(textRange);
135 PsiFile file = element.getContainingFile();
136 if (file == null) return textRange;
137 Document document = PsiDocumentManager.getInstance(element.getProject()).getCachedDocument(file);
138 if (!(document instanceof DocumentWindowImpl)) return textRange;
139 DocumentWindowImpl documentWindow = (DocumentWindowImpl)document;
140 return documentWindow.injectedToHost(textRange);
142 public int injectedToHost(@NotNull PsiElement element, int offset) {
143 PsiFile file = element.getContainingFile();
144 if (file == null) return offset;
145 Document document = PsiDocumentManager.getInstance(element.getProject()).getCachedDocument(file);
146 if (!(document instanceof DocumentWindowImpl)) return offset;
147 DocumentWindowImpl documentWindow = (DocumentWindowImpl)document;
148 return documentWindow.injectedToHost(offset);
151 private final ConcurrentMap<Class, MultiHostInjector[]> injectors = new ConcurrentHashMap<Class, MultiHostInjector[]>();
152 private final ClassMapCachingNulls<MultiHostInjector> cachedInjectors = new ClassMapCachingNulls<MultiHostInjector>(injectors, new MultiHostInjector[0]);
154 public void registerMultiHostInjector(@NotNull MultiHostInjector injector) {
155 for (Class<? extends PsiElement> place : injector.elementsToInjectIn()) {
156 while (true) {
157 MultiHostInjector[] injectors = this.injectors.get(place);
158 if (injectors == null) {
159 if (this.injectors.putIfAbsent(place, new MultiHostInjector[]{injector}) == null) break;
161 else {
162 MultiHostInjector[] newInfos = ArrayUtil.append(injectors, injector);
163 if (this.injectors.replace(place, injectors, newInfos)) break;
167 cachedInjectors.clearCache();
170 public boolean unregisterMultiHostInjector(@NotNull MultiHostInjector injector) {
171 boolean removed = false;
172 Iterator<Map.Entry<Class,MultiHostInjector[]>> iterator = injectors.entrySet().iterator();
173 while (iterator.hasNext()) {
174 Map.Entry<Class,MultiHostInjector[]> entry = iterator.next();
175 MultiHostInjector[] infos = entry.getValue();
176 int i = ArrayUtil.find(infos, injector);
177 if (i != -1) {
178 MultiHostInjector[] newInfos = ArrayUtil.remove(infos, i);
179 if (newInfos.length == 0) {
180 iterator.remove();
182 else {
183 injectors.put(entry.getKey(), newInfos);
185 removed = true;
188 cachedInjectors.clearCache();
189 return removed;
193 static final Key<String> UNESCAPED_TEXT = Key.create("INJECTED_UNESCAPED_TEXT");
194 public String getUnescapedText(@NotNull final PsiElement injectedNode) {
195 final StringBuilder text = new StringBuilder(injectedNode.getTextLength());
196 // gather text from (patched) leaves
197 injectedNode.accept(new PsiRecursiveElementWalkingVisitor() {
198 @Override
199 public void visitElement(PsiElement element) {
200 String unescaped = element.getUserData(UNESCAPED_TEXT);
201 if (unescaped != null) {
202 text.append(unescaped);
203 return;
205 if (element.getFirstChild() == null) {
206 text.append(element.getText());
207 return;
209 super.visitElement(element);
212 return text.toString();
213 //String text = injectedNode.getUserData(UNESCAPED_TEXT);
214 //if (text != null) return text;
215 //return injectedNode.getText();
219 * intersection may spread over several injected fragments
220 * @param rangeToEdit range in encoded(raw) PSI
221 * @return list of ranges in encoded (raw) PSI
223 @SuppressWarnings({"ConstantConditions"})
224 @NotNull
225 public List<TextRange> intersectWithAllEditableFragments(@NotNull PsiFile injectedPsi, @NotNull TextRange rangeToEdit) {
226 Place shreds = InjectedLanguageUtil.getShreds(injectedPsi);
227 if (shreds == null) return Collections.emptyList();
228 Object result = null; // optimization: TextRange or ArrayList
229 int count = 0;
230 int offset = 0;
231 for (PsiLanguageInjectionHost.Shred shred : shreds) {
232 TextRange encodedRange = TextRange.from(offset + shred.prefix.length(), shred.getRangeInsideHost().getLength());
233 TextRange intersection = encodedRange.intersection(rangeToEdit);
234 if (intersection != null) {
235 count++;
236 if (count == 1) {
237 result = intersection;
239 else if (count == 2) {
240 TextRange range = (TextRange)result;
241 if (range.isEmpty()) {
242 result = intersection;
243 count = 1;
245 else if (intersection.isEmpty()) {
246 count = 1;
248 else {
249 List<TextRange> list = new ArrayList<TextRange>();
250 list.add(range);
251 list.add(intersection);
252 result = list;
255 else if (intersection.isEmpty()) {
256 count--;
258 else {
259 ((List<TextRange>)result).add(intersection);
262 offset += shred.prefix.length() + shred.getRangeInsideHost().getLength() + shred.suffix.length();
264 return count == 0 ? Collections.<TextRange>emptyList() : count == 1 ? Collections.singletonList((TextRange)result) : (List<TextRange>)result;
267 public boolean isInjectedFragment(final PsiFile file) {
268 return file.getViewProvider() instanceof InjectedFileViewProvider;
271 @Override
272 public PsiElement findInjectedElementAt(@NotNull PsiFile hostFile, int hostDocumentOffset) {
273 return InjectedLanguageUtil.findInjectedElementNoCommitWithOffset(hostFile, hostDocumentOffset);
276 private final Map<Class,MultiHostInjector[]> myInjectorsClone = new HashMap<Class, MultiHostInjector[]>();
277 @TestOnly
278 public void pushInjectors() {
279 try {
280 assert myInjectorsClone.isEmpty() : myInjectorsClone;
282 finally {
283 myInjectorsClone.clear();
285 myInjectorsClone.putAll(injectors);
287 @TestOnly
288 public void checkInjectorsAreDisposed() {
289 try {
290 for (Map.Entry<Class, MultiHostInjector[]> entry : injectors.entrySet()) {
291 Class key = entry.getKey();
292 MultiHostInjector[] oldInjectors = myInjectorsClone.get(key);
293 for (MultiHostInjector injector : entry.getValue()) {
294 if (!ArrayUtil.contains(injector, oldInjectors)) {
295 throw new AssertionError("Injector was not disposed: " + key + " -> " + injector);
300 finally {
301 myInjectorsClone.clear();
305 public interface InjProcessor {
306 boolean process(PsiElement element, MultiHostInjector injector);
308 public void processInPlaceInjectorsFor(@NotNull PsiElement element, @NotNull InjProcessor processor) {
309 final boolean dumb = myDumbService.isDumb();
310 MultiHostInjector[] infos = cachedInjectors.get(element.getClass());
311 if (infos != null) {
312 for (MultiHostInjector injector : infos) {
313 if (dumb && !(injector instanceof DumbAware)) {
314 continue;
317 if (!processor.process(element, injector)) return;
322 @NonNls
323 @NotNull
324 public String getComponentName() {
325 return "InjectedLanguageManager";
328 public void initComponent() {
331 public void disposeComponent() {
334 private static class PsiManagerRegisteredInjectorsAdapter implements MultiHostInjector {
335 private final PsiManagerEx myPsiManager;
337 private PsiManagerRegisteredInjectorsAdapter(PsiManagerEx psiManager) {
338 myPsiManager = psiManager;
341 public void getLanguagesToInject(@NotNull final MultiHostRegistrar injectionPlacesRegistrar, @NotNull PsiElement context) {
342 final PsiLanguageInjectionHost host = (PsiLanguageInjectionHost)context;
343 InjectedLanguagePlaces placesRegistrar = new InjectedLanguagePlaces() {
344 public void addPlace(@NotNull Language language, @NotNull TextRange rangeInsideHost, @NonNls @Nullable String prefix, @NonNls @Nullable String suffix) {
345 ProperTextRange.assertProperRange(rangeInsideHost);
346 injectionPlacesRegistrar
347 .startInjecting(language)
348 .addPlace(prefix, suffix, host, rangeInsideHost)
349 .doneInjecting();
352 for (LanguageInjector injector : myPsiManager.getLanguageInjectors()) {
353 injector.getLanguagesToInject(host, placesRegistrar);
355 for (LanguageInjector injector : Extensions.getExtensions(LanguageInjector.EXTENSION_POINT_NAME)) {
356 injector.getLanguagesToInject(host, placesRegistrar);
360 @NotNull
361 public List<? extends Class<? extends PsiElement>> elementsToInjectIn() {
362 return Arrays.asList(PsiLanguageInjectionHost.class);