AE: PsiFileBase.findLanguage
[fedora-idea.git] / xml / impl / src / com / intellij / psi / CompositeLanguageFileViewProvider.java
blob577f58b5d536eb5a29c4decd0428ee2cf5527add
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.psi;
18 import com.intellij.lang.*;
19 import com.intellij.openapi.application.ex.ApplicationManagerEx;
20 import com.intellij.openapi.diagnostic.Logger;
21 import com.intellij.openapi.util.TextRange;
22 import com.intellij.openapi.vfs.VirtualFile;
23 import com.intellij.psi.impl.SharedPsiElementImplUtil;
24 import com.intellij.psi.impl.source.LightPsiFileImpl;
25 import com.intellij.psi.impl.source.PsiFileImpl;
26 import com.intellij.psi.impl.source.tree.FileElement;
27 import com.intellij.psi.templateLanguages.OuterLanguageElement;
28 import com.intellij.testFramework.LightVirtualFile;
29 import com.intellij.util.ReflectionCache;
30 import com.intellij.util.containers.ConcurrentHashMap;
31 import org.jetbrains.annotations.NonNls;
32 import org.jetbrains.annotations.NotNull;
33 import org.jetbrains.annotations.Nullable;
35 import java.util.*;
37 public class CompositeLanguageFileViewProvider extends SingleRootFileViewProvider {
38 private static final Logger LOG = Logger.getInstance("#com.intellij.psi.CompositeLanguageFileViewProvider");
39 private final ConcurrentHashMap<Language, PsiFile> myRoots = new ConcurrentHashMap<Language, PsiFile>(1, ConcurrentHashMap.DEFAULT_LOAD_FACTOR, 1);
40 private Set<Language> myRelevantLanguages;
42 @NotNull
43 public Set<Language> getLanguages() {
44 if (myRelevantLanguages != null) return myRelevantLanguages;
45 Set<Language> relevantLanguages = new HashSet<Language>();
46 final Language baseLanguage = getBaseLanguage();
47 relevantLanguages.add(baseLanguage);
48 relevantLanguages.addAll(myRoots.keySet());
49 return myRelevantLanguages = new LinkedHashSet<Language>(relevantLanguages);
52 public void contentsSynchronized() {
53 super.contentsSynchronized();
54 myRelevantLanguages = null;
57 private final Set<PsiFile> myRootsInUpdate = new HashSet<PsiFile>(4);
59 public CompositeLanguageFileViewProvider(final PsiManager manager, final VirtualFile virtualFile, final boolean physical) {
60 super(manager, virtualFile, physical);
63 protected CompositeLanguageFileViewProvider(@NotNull PsiManager manager,
64 @NotNull VirtualFile virtualFile,
65 boolean physical,
66 @NotNull Language language) {
67 super(manager, virtualFile, physical, language);
70 @NotNull
71 public SingleRootFileViewProvider createCopy(final LightVirtualFile copy) {
72 final CompositeLanguageFileViewProvider viewProvider = cloneInner(copy);
73 final PsiFileImpl psiFile = (PsiFileImpl)viewProvider.getPsi(getBaseLanguage());
74 assert psiFile != null;
75 psiFile.setOriginalFile(getPsi(getBaseLanguage()));
77 // copying main tree
78 final FileElement treeClone = (FileElement)psiFile.calcTreeElement().clone(); // base language tree clone
79 psiFile.setTreeElementPointer(treeClone); // should not use setTreeElement here because cloned file still have VirtualFile (SCR17963)
80 treeClone.setPsi(psiFile);
82 for (Map.Entry<Language, PsiFile> entry : myRoots.entrySet()) {
83 final PsiFile root = entry.getValue();
84 if (root != psiFile && root != null && root.getLanguage() != getBaseLanguage()) {
85 if (root instanceof LightPsiFileImpl) {
86 final LightPsiFileImpl lightFile = (LightPsiFileImpl)root;
87 final LightPsiFileImpl clone = lightFile.copyLight(viewProvider);
88 clone.setOriginalFile(root);
89 viewProvider.myRoots.put(entry.getKey(), clone);
91 else {
92 LOG.error("Only light files supported for language extensions, passed: " + root);
96 return viewProvider;
99 protected CompositeLanguageFileViewProvider cloneInner(VirtualFile copy) {
100 return new CompositeLanguageFileViewProvider(getManager(), copy, false);
103 @Nullable
104 protected PsiFile getPsiInner(Language target) {
105 PsiFile file = super.getPsiInner(target);
106 if (file != null) return file;
107 file = myRoots.get(target);
108 if (file == null) {
109 file = createFile(target);
110 if (file == null) return null;
111 file = myRoots.cacheOrGet(target, file);
113 return file;
116 public PsiFile getCachedPsi(Language target) {
117 if (target == getBaseLanguage()) return super.getCachedPsi(target);
118 return myRoots.get(target);
121 public void checkAllTreesEqual() {
122 final String psiText = getPsi(getBaseLanguage()).getText();
123 for (Map.Entry<Language, PsiFile> entry : myRoots.entrySet()) {
124 final PsiFile psiFile = entry.getValue();
125 LOG.assertTrue(psiFile.getTextLength() == psiText.length(), entry.getKey().getID() + " tree text differs from base!");
126 LOG.assertTrue(psiFile.getText().equals(psiText), entry.getKey().getID() + " tree text differs from base!");
130 public FileElement[] getKnownTreeRoots() {
131 final List<FileElement> knownRoots = new ArrayList<FileElement>();
132 knownRoots.addAll(Arrays.asList(super.getKnownTreeRoots()));
133 for (PsiFile psiFile : myRoots.values()) {
134 if (psiFile == null || !(psiFile instanceof PsiFileImpl)) continue;
135 final FileElement fileElement = ((PsiFileImpl)psiFile).getTreeElement();
136 if (fileElement == null) continue;
137 knownRoots.add(fileElement);
139 return knownRoots.toArray(new FileElement[knownRoots.size()]);
142 @Nullable
143 public PsiElement findElementAt(int offset, Class<? extends Language> lang) {
144 final PsiFile mainRoot = getPsi(getBaseLanguage());
145 PsiElement ret = null;
146 for (final Language language : getLanguages()) {
147 if (!ReflectionCache.isAssignable(lang, language.getClass())) continue;
148 if (lang.equals(Language.class) && !getLanguages().contains(language)) continue;
150 final PsiFile psiRoot = getPsi(language);
151 final PsiElement psiElement = findElementAt(psiRoot, offset);
152 if (psiElement == null || psiElement instanceof OuterLanguageElement) continue;
153 if (ret == null || psiRoot != mainRoot) {
154 ret = psiElement;
157 return ret;
160 @Nullable
161 public PsiElement findElementAt(int offset) {
162 return findElementAt(offset, Language.class);
165 @Nullable
166 public PsiReference findReferenceAt(int offset) {
167 TextRange minRange = new TextRange(0, getContents().length());
168 PsiReference ret = null;
169 for (final Language language : getLanguages()) {
170 final PsiElement psiRoot = getPsi(language);
171 final PsiReference reference = SharedPsiElementImplUtil.findReferenceAt(psiRoot, offset);
172 if (reference == null) continue;
173 final TextRange textRange = reference.getRangeInElement().shiftRight(reference.getElement().getTextRange().getStartOffset());
174 if (minRange.contains(textRange)) {
175 minRange = textRange;
176 ret = reference;
179 return ret;
182 private void checkConsistensy(final PsiFile oldFile) {
183 ASTNode oldNode = oldFile.getNode();
184 if (oldNode.getTextLength() != getContents().length() ||
185 !oldNode.getText().equals(getContents().toString())) {
186 @NonNls String message = "Check consistency failed for: " + oldFile;
187 message += "\n oldFile.getNode().getTextLength() = " + oldNode.getTextLength();
188 message += "\n getContents().length() = " + getContents().length();
189 message += "\n language = " + oldFile.getLanguage();
191 if (ApplicationManagerEx.getApplicationEx().isInternal()) {
192 message += "\n oldFileText:\n" + oldNode.getText();
193 message += "\n contentsText:\n" + getContents().toString();
194 message += "\n jspText:\n" + getPsi(getBaseLanguage()).getNode().getText();
196 LOG.assertTrue(false, message);
197 assert false;
201 public LanguageFilter[] getLanguageExtensions() {
202 return new LanguageFilter[0];
205 protected void removeFile(Language lang) {
206 myRoots.remove(lang);
209 @Nullable
210 protected PsiFile createFile(Language lang) {
211 final PsiFile psiFile = super.createFile(lang);
212 if (psiFile != null) return psiFile;
213 if (isIgnored()) return null;
215 if (isRelevantLanguage(lang)) {
216 final ParserDefinition parserDefinition = LanguageParserDefinitions.INSTANCE.forLanguage(lang);
217 assert parserDefinition != null;
218 return parserDefinition.createFile(this);
220 return null;
223 protected boolean isRelevantLanguage(final Language lang) {
224 return getLanguages().contains(lang);
227 public void rootChanged(PsiFile psiFile) {
228 if (myRootsInUpdate.contains(psiFile)) return;
229 if (psiFile.getLanguage() == getBaseLanguage()) {
230 super.rootChanged(psiFile);
232 else if (!myRootsInUpdate.contains(getPsi(getBaseLanguage()))) {
233 LOG.error("Changing PSI for aux trees is not supported");
237 public Set<PsiFile> getRootsInUpdate() {
238 return myRootsInUpdate;