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
;
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
;
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
,
66 @NotNull Language language
) {
67 super(manager
, virtualFile
, physical
, language
);
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()));
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
);
92 LOG
.error("Only light files supported for language extensions, passed: " + root
);
99 protected CompositeLanguageFileViewProvider
cloneInner(VirtualFile copy
) {
100 return new CompositeLanguageFileViewProvider(getManager(), copy
, false);
104 protected PsiFile
getPsiInner(Language target
) {
105 PsiFile file
= super.getPsiInner(target
);
106 if (file
!= null) return file
;
107 file
= myRoots
.get(target
);
109 file
= createFile(target
);
110 if (file
== null) return null;
111 file
= myRoots
.cacheOrGet(target
, 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()]);
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
) {
161 public PsiElement
findElementAt(int offset
) {
162 return findElementAt(offset
, Language
.class);
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
;
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
);
201 public LanguageFilter
[] getLanguageExtensions() {
202 return new LanguageFilter
[0];
205 protected void removeFile(Language lang
) {
206 myRoots
.remove(lang
);
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);
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
;