blob: d8291fbd7f8d84c7c589f165c33f7cfed8abc191 [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* @author max
*/
package com.intellij.psi;
import com.intellij.lang.Language;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.impl.PsiDocumentManagerBase;
import com.intellij.psi.impl.SharedPsiElementImplUtil;
import com.intellij.psi.impl.source.PsiFileImpl;
import com.intellij.psi.impl.source.tree.FileElement;
import com.intellij.psi.templateLanguages.OuterLanguageElement;
import com.intellij.util.ConcurrencyUtil;
import com.intellij.util.ReflectionUtil;
import com.intellij.util.containers.ConcurrentHashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import java.util.*;
import java.util.concurrent.ConcurrentMap;
public abstract class MultiplePsiFilesPerDocumentFileViewProvider extends SingleRootFileViewProvider {
private final ConcurrentMap<Language, PsiFile> myRoots = new ConcurrentHashMap<Language, PsiFile>(1, ConcurrentHashMap.DEFAULT_LOAD_FACTOR, 1);
private MultiplePsiFilesPerDocumentFileViewProvider myOriginal = null;
public MultiplePsiFilesPerDocumentFileViewProvider(PsiManager manager, VirtualFile virtualFile, boolean eventSystemEnabled) {
super(manager, virtualFile, eventSystemEnabled, Language.ANY);
}
@Override
@NotNull
public abstract Language getBaseLanguage();
@Override
@NotNull
public List<PsiFile> getAllFiles() {
final List<PsiFile> roots = new ArrayList<PsiFile>();
for (Language language : getLanguages()) {
PsiFile psi = getPsi(language);
if (psi != null) roots.add(psi);
}
final PsiFile base = getPsi(getBaseLanguage());
if (!roots.isEmpty() && roots.get(0) != base) {
roots.remove(base);
roots.add(0, base);
}
return roots;
}
protected void removeFile(final Language language) {
myRoots.remove(language);
}
@Override
protected PsiFile getPsiInner(@NotNull final Language target) {
PsiFile file = myRoots.get(target);
if (file == null) {
if (isPhysical()) {
VirtualFile virtualFile = getVirtualFile();
if (isIgnored()) return null;
VirtualFile parent = virtualFile.getParent();
if (parent != null) {
getManager().findDirectory(parent);
}
}
file = createFile(target);
if (file == null) return null;
if (myOriginal != null) {
final PsiFile originalFile = myOriginal.getPsi(target);
if (originalFile != null) {
((PsiFileImpl)file).setOriginalFile(originalFile);
}
}
file = ConcurrencyUtil.cacheOrGet(myRoots, target, file);
}
return file;
}
@Override
public PsiFile getCachedPsi(@NotNull Language target) {
return myRoots.get(target);
}
@NotNull
@Override
public FileElement[] getKnownTreeRoots() {
List<FileElement> files = new ArrayList<FileElement>(myRoots.size());
for (PsiFile file : myRoots.values()) {
final FileElement treeElement = ((PsiFileImpl)file).getTreeElement();
if (treeElement != null) {
files.add(treeElement);
}
}
return files.toArray(new FileElement[files.size()]);
}
@TestOnly
public void checkAllTreesEqual() {
Collection<PsiFile> roots = myRoots.values();
PsiDocumentManager documentManager = PsiDocumentManager.getInstance(getManager().getProject());
documentManager.commitAllDocuments();
for (PsiFile root : roots) {
Document document = documentManager.getDocument(root);
PsiDocumentManagerBase.checkConsistency(root, document);
assert root.getText().equals(document.getText());
}
}
@NotNull
@Override
public final MultiplePsiFilesPerDocumentFileViewProvider createCopy(@NotNull final VirtualFile fileCopy) {
final MultiplePsiFilesPerDocumentFileViewProvider copy = cloneInner(fileCopy);
copy.myOriginal = myOriginal == null ? this : myOriginal;
return copy;
}
protected abstract MultiplePsiFilesPerDocumentFileViewProvider cloneInner(VirtualFile fileCopy);
@Override
@Nullable
public PsiElement findElementAt(int offset, @NotNull Class<? extends Language> lang) {
final PsiFile mainRoot = getPsi(getBaseLanguage());
PsiElement ret = null;
for (final Language language : getLanguages()) {
if (!ReflectionUtil.isAssignable(lang, language.getClass())) continue;
if (lang.equals(Language.class) && !getLanguages().contains(language)) continue;
final PsiFile psiRoot = getPsi(language);
final PsiElement psiElement = findElementAt(psiRoot, offset);
if (psiElement == null || psiElement instanceof OuterLanguageElement) continue;
if (ret == null || psiRoot != mainRoot) {
ret = psiElement;
}
}
return ret;
}
@Override
@Nullable
public PsiElement findElementAt(int offset) {
return findElementAt(offset, Language.class);
}
@Override
@Nullable
public PsiReference findReferenceAt(int offset) {
TextRange minRange = new TextRange(0, getContents().length());
PsiReference ret = null;
for (final Language language : getLanguages()) {
final PsiElement psiRoot = getPsi(language);
final PsiReference reference = SharedPsiElementImplUtil.findReferenceAt(psiRoot, offset, language);
if (reference == null) continue;
final TextRange textRange = reference.getRangeInElement().shiftRight(reference.getElement().getTextRange().getStartOffset());
if (minRange.contains(textRange) && !textRange.contains(minRange)) {
minRange = textRange;
ret = reference;
}
}
return ret;
}
@Override
public void contentsSynchronized() {
super.contentsSynchronized();
Set<Language> languages = getLanguages();
for (Iterator<Map.Entry<Language, PsiFile>> iterator = myRoots.entrySet().iterator(); iterator.hasNext(); ) {
Map.Entry<Language, PsiFile> entry = iterator.next();
if (!languages.contains(entry.getKey())) {
iterator.remove();
}
}
}
}