cleanup
[fedora-idea.git] / platform / lang-impl / src / com / intellij / psi / impl / source / tree / injected / InjectedLanguageManagerImpl.java
blob6dbea2e861e125dac24d729f3c51ad3ae87b526c
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();
216 * intersection may spread over several injected fragments
217 * @param rangeToEdit range in encoded(raw) PSI
218 * @return list of ranges in encoded (raw) PSI
220 @SuppressWarnings({"ConstantConditions"})
221 @NotNull
222 public List<TextRange> intersectWithAllEditableFragments(@NotNull PsiFile injectedPsi, @NotNull TextRange rangeToEdit) {
223 Place shreds = InjectedLanguageUtil.getShreds(injectedPsi);
224 if (shreds == null) return Collections.emptyList();
225 Object result = null; // optimization: TextRange or ArrayList
226 int count = 0;
227 int offset = 0;
228 for (PsiLanguageInjectionHost.Shred shred : shreds) {
229 TextRange encodedRange = TextRange.from(offset + shred.prefix.length(), shred.getRangeInsideHost().getLength());
230 TextRange intersection = encodedRange.intersection(rangeToEdit);
231 if (intersection != null) {
232 count++;
233 if (count == 1) {
234 result = intersection;
236 else if (count == 2) {
237 TextRange range = (TextRange)result;
238 if (range.isEmpty()) {
239 result = intersection;
240 count = 1;
242 else if (intersection.isEmpty()) {
243 count = 1;
245 else {
246 List<TextRange> list = new ArrayList<TextRange>();
247 list.add(range);
248 list.add(intersection);
249 result = list;
252 else if (intersection.isEmpty()) {
253 count--;
255 else {
256 ((List<TextRange>)result).add(intersection);
259 offset += shred.prefix.length() + shred.getRangeInsideHost().getLength() + shred.suffix.length();
261 return count == 0 ? Collections.<TextRange>emptyList() : count == 1 ? Collections.singletonList((TextRange)result) : (List<TextRange>)result;
264 public boolean isInjectedFragment(final PsiFile file) {
265 return file.getViewProvider() instanceof InjectedFileViewProvider;
268 @Override
269 public PsiElement findInjectedElementAt(@NotNull PsiFile hostFile, int hostDocumentOffset) {
270 return InjectedLanguageUtil.findInjectedElementNoCommitWithOffset(hostFile, hostDocumentOffset);
273 private final Map<Class,MultiHostInjector[]> myInjectorsClone = new HashMap<Class, MultiHostInjector[]>();
274 @TestOnly
275 public void pushInjectors() {
276 try {
277 assert myInjectorsClone.isEmpty() : myInjectorsClone;
279 finally {
280 myInjectorsClone.clear();
282 myInjectorsClone.putAll(injectors);
284 @TestOnly
285 public void checkInjectorsAreDisposed() {
286 try {
287 for (Map.Entry<Class, MultiHostInjector[]> entry : injectors.entrySet()) {
288 Class key = entry.getKey();
289 MultiHostInjector[] oldInjectors = myInjectorsClone.get(key);
290 for (MultiHostInjector injector : entry.getValue()) {
291 if (!ArrayUtil.contains(injector, oldInjectors)) {
292 throw new AssertionError("Injector was not disposed: " + key + " -> " + injector);
297 finally {
298 myInjectorsClone.clear();
302 public interface InjProcessor {
303 boolean process(PsiElement element, MultiHostInjector injector);
305 public void processInPlaceInjectorsFor(@NotNull PsiElement element, @NotNull InjProcessor processor) {
306 final boolean dumb = myDumbService.isDumb();
307 MultiHostInjector[] infos = cachedInjectors.get(element.getClass());
308 if (infos != null) {
309 for (MultiHostInjector injector : infos) {
310 if (dumb && !(injector instanceof DumbAware)) {
311 continue;
314 if (!processor.process(element, injector)) return;
319 @NonNls
320 @NotNull
321 public String getComponentName() {
322 return "InjectedLanguageManager";
325 public void initComponent() {
328 public void disposeComponent() {
331 private static class PsiManagerRegisteredInjectorsAdapter implements MultiHostInjector {
332 private final PsiManagerEx myPsiManager;
334 private PsiManagerRegisteredInjectorsAdapter(PsiManagerEx psiManager) {
335 myPsiManager = psiManager;
338 public void getLanguagesToInject(@NotNull final MultiHostRegistrar injectionPlacesRegistrar, @NotNull PsiElement context) {
339 final PsiLanguageInjectionHost host = (PsiLanguageInjectionHost)context;
340 InjectedLanguagePlaces placesRegistrar = new InjectedLanguagePlaces() {
341 public void addPlace(@NotNull Language language, @NotNull TextRange rangeInsideHost, @NonNls @Nullable String prefix, @NonNls @Nullable String suffix) {
342 ProperTextRange.assertProperRange(rangeInsideHost);
343 injectionPlacesRegistrar
344 .startInjecting(language)
345 .addPlace(prefix, suffix, host, rangeInsideHost)
346 .doneInjecting();
349 for (LanguageInjector injector : myPsiManager.getLanguageInjectors()) {
350 injector.getLanguagesToInject(host, placesRegistrar);
352 for (LanguageInjector injector : Extensions.getExtensions(LanguageInjector.EXTENSION_POINT_NAME)) {
353 injector.getLanguagesToInject(host, placesRegistrar);
357 @NotNull
358 public List<? extends Class<? extends PsiElement>> elementsToInjectIn() {
359 return Arrays.asList(PsiLanguageInjectionHost.class);