| /* |
| * 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. |
| */ |
| package com.intellij.psi.util; |
| |
| import com.intellij.lang.ASTNode; |
| import com.intellij.lang.Language; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.Comparing; |
| import com.intellij.openapi.util.Computable; |
| import com.intellij.openapi.util.Key; |
| import com.intellij.openapi.util.TextRange; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.psi.*; |
| import com.intellij.psi.meta.PsiMetaData; |
| import com.intellij.psi.meta.PsiMetaOwner; |
| import com.intellij.psi.scope.PsiScopeProcessor; |
| import com.intellij.psi.search.GlobalSearchScope; |
| import com.intellij.psi.search.SearchScope; |
| import com.intellij.util.TimeoutUtil; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import java.util.Collection; |
| |
| /** |
| * @author yole |
| */ |
| public class PsiUtilCore { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.psi.util.PsiUtilCore"); |
| public static final PsiElement NULL_PSI_ELEMENT = new PsiElement() { |
| @Override |
| @NotNull |
| public Project getProject() { |
| throw createException(); |
| } |
| |
| @Override |
| @NotNull |
| public Language getLanguage() { |
| throw createException(); |
| } |
| |
| @Override |
| public PsiManager getManager() { |
| throw createException(); |
| } |
| |
| @Override |
| @NotNull |
| public PsiElement[] getChildren() { |
| throw createException(); |
| } |
| |
| @Override |
| public PsiElement getParent() { |
| throw createException(); |
| } |
| |
| @Override |
| @Nullable |
| public PsiElement getFirstChild() { |
| throw createException(); |
| } |
| |
| @Override |
| @Nullable |
| public PsiElement getLastChild() { |
| throw createException(); |
| } |
| |
| @Override |
| @Nullable |
| public PsiElement getNextSibling() { |
| throw createException(); |
| } |
| |
| @Override |
| @Nullable |
| public PsiElement getPrevSibling() { |
| throw createException(); |
| } |
| |
| @Override |
| public PsiFile getContainingFile() { |
| throw createException(); |
| } |
| |
| @Override |
| public TextRange getTextRange() { |
| throw createException(); |
| } |
| |
| @Override |
| public int getStartOffsetInParent() { |
| throw createException(); |
| } |
| |
| @Override |
| public int getTextLength() { |
| throw createException(); |
| } |
| |
| @Override |
| public PsiElement findElementAt(int offset) { |
| throw createException(); |
| } |
| |
| @Override |
| @Nullable |
| public PsiReference findReferenceAt(int offset) { |
| throw createException(); |
| } |
| |
| @Override |
| public int getTextOffset() { |
| throw createException(); |
| } |
| |
| @Override |
| public String getText() { |
| throw createException(); |
| } |
| |
| @Override |
| @NotNull |
| public char[] textToCharArray() { |
| throw createException(); |
| } |
| |
| @Override |
| public PsiElement getNavigationElement() { |
| throw createException(); |
| } |
| |
| @Override |
| public PsiElement getOriginalElement() { |
| throw createException(); |
| } |
| |
| @Override |
| public boolean textMatches(@NotNull CharSequence text) { |
| throw createException(); |
| } |
| |
| @Override |
| public boolean textMatches(@NotNull PsiElement element) { |
| throw createException(); |
| } |
| |
| @Override |
| public boolean textContains(char c) { |
| throw createException(); |
| } |
| |
| @Override |
| public void accept(@NotNull PsiElementVisitor visitor) { |
| throw createException(); |
| } |
| |
| @Override |
| public void acceptChildren(@NotNull PsiElementVisitor visitor) { |
| throw createException(); |
| } |
| |
| @Override |
| public PsiElement copy() { |
| throw createException(); |
| } |
| |
| @Override |
| public PsiElement add(@NotNull PsiElement element) { |
| throw createException(); |
| } |
| |
| @Override |
| public PsiElement addBefore(@NotNull PsiElement element, PsiElement anchor) { |
| throw createException(); |
| } |
| |
| @Override |
| public PsiElement addAfter(@NotNull PsiElement element, PsiElement anchor) { |
| throw createException(); |
| } |
| |
| @Override |
| public void checkAdd(@NotNull PsiElement element) { |
| throw createException(); |
| } |
| |
| @Override |
| public PsiElement addRange(PsiElement first, PsiElement last) { |
| throw createException(); |
| } |
| |
| @Override |
| public PsiElement addRangeBefore(@NotNull PsiElement first, @NotNull PsiElement last, PsiElement anchor) { |
| throw createException(); |
| } |
| |
| @Override |
| public PsiElement addRangeAfter(PsiElement first, PsiElement last, PsiElement anchor) { |
| throw createException(); |
| } |
| |
| @Override |
| public void delete() { |
| throw createException(); |
| } |
| |
| @Override |
| public void checkDelete() { |
| throw createException(); |
| } |
| |
| @Override |
| public void deleteChildRange(PsiElement first, PsiElement last) { |
| throw createException(); |
| } |
| |
| @Override |
| public PsiElement replace(@NotNull PsiElement newElement) { |
| throw createException(); |
| } |
| |
| @Override |
| public boolean isValid() { |
| throw createException(); |
| } |
| |
| @Override |
| public boolean isWritable() { |
| throw createException(); |
| } |
| |
| private PsiInvalidElementAccessException createException() { |
| return new PsiInvalidElementAccessException(this, "NULL_PSI_ELEMENT", null); |
| } |
| |
| @Override |
| @Nullable |
| public PsiReference getReference() { |
| throw createException(); |
| } |
| |
| @Override |
| @NotNull |
| public PsiReference[] getReferences() { |
| throw createException(); |
| } |
| |
| @Override |
| public <T> T getCopyableUserData(Key<T> key) { |
| throw createException(); |
| } |
| |
| @Override |
| public <T> void putCopyableUserData(Key<T> key, T value) { |
| throw createException(); |
| } |
| |
| @Override |
| public boolean processDeclarations(@NotNull PsiScopeProcessor processor, |
| @NotNull ResolveState state, |
| PsiElement lastParent, |
| @NotNull PsiElement place) { |
| throw createException(); |
| } |
| |
| @Override |
| public PsiElement getContext() { |
| throw createException(); |
| } |
| |
| @Override |
| public boolean isPhysical() { |
| throw createException(); |
| } |
| |
| @Override |
| @NotNull |
| public GlobalSearchScope getResolveScope() { |
| throw createException(); |
| } |
| |
| @Override |
| @NotNull |
| public SearchScope getUseScope() { |
| throw createException(); |
| } |
| |
| @Override |
| public ASTNode getNode() { |
| throw createException(); |
| } |
| |
| @Override |
| public <T> T getUserData(@NotNull Key<T> key) { |
| throw createException(); |
| } |
| |
| @Override |
| public <T> void putUserData(@NotNull Key<T> key, T value) { |
| throw createException(); |
| } |
| |
| @Override |
| public Icon getIcon(int flags) { |
| throw createException(); |
| } |
| |
| @Override |
| public boolean isEquivalentTo(final PsiElement another) { |
| return this == another; |
| } |
| |
| @Override |
| public String toString() { |
| return "NULL_PSI_ELEMENT"; |
| } |
| }; |
| |
| @NotNull |
| public static PsiElement[] toPsiElementArray(@NotNull Collection<? extends PsiElement> collection) { |
| if (collection.isEmpty()) return PsiElement.EMPTY_ARRAY; |
| //noinspection SSBasedInspection |
| return collection.toArray(new PsiElement[collection.size()]); |
| } |
| |
| public static Language getNotAnyLanguage(ASTNode node) { |
| if (node == null) return Language.ANY; |
| |
| final Language lang = node.getElementType().getLanguage(); |
| return lang == Language.ANY ? getNotAnyLanguage(node.getTreeParent()) : lang; |
| } |
| |
| @Nullable |
| public static VirtualFile getVirtualFile(@Nullable PsiElement element) { |
| if (element == null) { |
| return null; |
| } |
| if (element instanceof PsiFileSystemItem) { |
| return element.isValid() ? ((PsiFileSystemItem)element).getVirtualFile() : null; |
| } |
| final PsiFile containingFile = element.getContainingFile(); |
| if (containingFile == null || !containingFile.isValid()) { |
| return null; |
| } |
| |
| return containingFile.getVirtualFile(); |
| } |
| |
| public static int compareElementsByPosition(final PsiElement element1, final PsiElement element2) { |
| if (element1 != null && element2 != null) { |
| final PsiFile psiFile1 = element1.getContainingFile(); |
| final PsiFile psiFile2 = element2.getContainingFile(); |
| if (Comparing.equal(psiFile1, psiFile2)){ |
| final TextRange textRange1 = element1.getTextRange(); |
| final TextRange textRange2 = element2.getTextRange(); |
| if (textRange1 != null && textRange2 != null) { |
| return textRange1.getStartOffset() - textRange2.getStartOffset(); |
| } |
| } else if (psiFile1 != null && psiFile2 != null){ |
| final String name1 = psiFile1.getName(); |
| final String name2 = psiFile2.getName(); |
| return name1.compareToIgnoreCase(name2); |
| } |
| } |
| return 0; |
| } |
| |
| public static boolean hasErrorElementChild(@NotNull PsiElement element) { |
| for (PsiElement child = element.getFirstChild(); child != null; child = child.getNextSibling()) { |
| if (child instanceof PsiErrorElement) return true; |
| } |
| return false; |
| } |
| |
| @NotNull |
| public static PsiElement getElementAtOffset(@NotNull PsiFile file, int offset) { |
| PsiElement elt = file.findElementAt(offset); |
| if (elt == null && offset > 0) { |
| elt = file.findElementAt(offset - 1); |
| } |
| if (elt == null) { |
| return file; |
| } |
| return elt; |
| } |
| |
| @Nullable |
| public static PsiFile getTemplateLanguageFile(final PsiElement element) { |
| if (element == null) return null; |
| final PsiFile containingFile = element.getContainingFile(); |
| if (containingFile == null) return null; |
| |
| final FileViewProvider viewProvider = containingFile.getViewProvider(); |
| return viewProvider.getPsi(viewProvider.getBaseLanguage()); |
| } |
| |
| @NotNull |
| public static PsiFile[] toPsiFileArray(@NotNull Collection<? extends PsiFile> collection) { |
| if (collection.isEmpty()) return PsiFile.EMPTY_ARRAY; |
| //noinspection SSBasedInspection |
| return collection.toArray(new PsiFile[collection.size()]); |
| } |
| |
| /** |
| * @return name for element using element structure info |
| */ |
| @Nullable |
| public static String getName(PsiElement element) { |
| String name = null; |
| if (element instanceof PsiMetaOwner) { |
| final PsiMetaData data = ((PsiMetaOwner) element).getMetaData(); |
| if (data != null) { |
| name = data.getName(element); |
| } |
| } |
| if (name == null && element instanceof PsiNamedElement) { |
| name = ((PsiNamedElement) element).getName(); |
| } |
| return name; |
| } |
| |
| public static String getQualifiedNameAfterRename(String qName, String newName) { |
| if (qName == null) return newName; |
| int index = qName.lastIndexOf('.'); |
| return index < 0 ? newName : qName.substring(0, index + 1) + newName; |
| } |
| |
| public static Language getDialect(@NotNull PsiElement element) { |
| return narrowLanguage(element.getLanguage(), element.getContainingFile().getLanguage()); |
| } |
| |
| protected static Language narrowLanguage(final Language language, final Language candidate) { |
| if (candidate.isKindOf(language)) return candidate; |
| return language; |
| } |
| |
| /** |
| * Checks if the element is valid. If not, throws {@link com.intellij.psi.PsiInvalidElementAccessException} with |
| * a meaningful message that points to the reasons why the element is not valid and may contain the stack trace |
| * when it was invalidated. |
| * @param element |
| */ |
| public static void ensureValid(@NotNull PsiElement element) { |
| if (!element.isValid()) { |
| TimeoutUtil.sleep(1); // to see if processing in another thread suddenly makes the element valid again (which is a bug) |
| if (element.isValid()) { |
| LOG.error("PSI resurrected: " + element + " of " + element.getClass()); |
| return; |
| } |
| throw new PsiInvalidElementAccessException(element); |
| } |
| } |
| |
| /** |
| * @deprecated use CompletionUtil#getOriginalElement where appropriate instead |
| */ |
| @Nullable |
| public static <T extends PsiElement> T getOriginalElement(@NotNull T psiElement, final Class<? extends T> elementClass) { |
| final PsiFile psiFile = psiElement.getContainingFile(); |
| final PsiFile originalFile = psiFile.getOriginalFile(); |
| if (originalFile == psiFile) return psiElement; |
| final TextRange range = psiElement.getTextRange(); |
| final PsiElement element = originalFile.findElementAt(range.getStartOffset()); |
| final int maxLength = range.getLength(); |
| T parent = PsiTreeUtil.getParentOfType(element, elementClass, false); |
| T next = parent ; |
| while (next != null && next.getTextLength() <= maxLength) { |
| parent = next; |
| next = PsiTreeUtil.getParentOfType(next, elementClass, true); |
| } |
| return parent; |
| } |
| |
| @NotNull |
| public static Language findLanguageFromElement(final PsiElement elt) { |
| if (!(elt instanceof PsiFile) && elt.getFirstChild() == null) { //is leaf |
| final PsiElement parent = elt.getParent(); |
| if (parent != null) { |
| return parent.getLanguage(); |
| } |
| } |
| |
| return elt.getLanguage(); |
| } |
| |
| @NotNull |
| public static Language getLanguageAtOffset (@NotNull PsiFile file, int offset) { |
| final PsiElement elt = file.findElementAt(offset); |
| if (elt == null) return file.getLanguage(); |
| if (elt instanceof PsiWhiteSpace) { |
| TextRange textRange = elt.getTextRange(); |
| if (!textRange.contains(offset)) { |
| LOG.error("PSI corrupted: in file "+file+" ("+file.getViewProvider().getVirtualFile()+") offset="+offset+" returned element "+elt+" with text range "+textRange); |
| } |
| final int decremented = textRange.getStartOffset() - 1; |
| if (decremented >= 0) { |
| return getLanguageAtOffset(file, decremented); |
| } |
| } |
| return findLanguageFromElement(elt); |
| } |
| |
| public static Project getProjectInReadAction(@NotNull final PsiElement element) { |
| return ApplicationManager.getApplication().runReadAction(new Computable<Project>() { |
| @Override |
| public Project compute() { |
| return element.getProject(); |
| } |
| }); |
| } |
| } |