| /* |
| * 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.impl; |
| |
| import com.intellij.ide.fileTemplates.FileTemplate; |
| import com.intellij.ide.fileTemplates.FileTemplateManager; |
| import com.intellij.ide.fileTemplates.JavaTemplateUtil; |
| import com.intellij.lang.ASTNode; |
| import com.intellij.lang.java.JavaLanguage; |
| import com.intellij.openapi.components.ServiceManager; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.fileTypes.StdFileTypes; |
| import com.intellij.openapi.module.EffectiveLanguageLevelUtil; |
| import com.intellij.openapi.module.Module; |
| import com.intellij.openapi.progress.ProcessCanceledException; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.roots.*; |
| import com.intellij.openapi.roots.impl.DirectoryIndex; |
| import com.intellij.openapi.vfs.VfsUtilCore; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.pom.java.LanguageLevel; |
| import com.intellij.psi.*; |
| import com.intellij.psi.codeStyle.CodeStyleSettings; |
| import com.intellij.psi.codeStyle.CodeStyleSettingsManager; |
| import com.intellij.psi.codeStyle.arrangement.MemberOrderService; |
| import com.intellij.psi.impl.compiled.ClsClassImpl; |
| import com.intellij.psi.impl.source.codeStyle.ImportHelper; |
| import com.intellij.psi.search.GlobalSearchScope; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.psi.util.PsiUtil; |
| import com.intellij.util.IncorrectOperationException; |
| import com.intellij.util.containers.ContainerUtil; |
| import gnu.trove.THashSet; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Properties; |
| import java.util.Set; |
| |
| /** |
| * @author yole |
| */ |
| public class JavaPsiImplementationHelperImpl extends JavaPsiImplementationHelper { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.JavaPsiImplementationHelperImpl"); |
| |
| private final Project myProject; |
| |
| public JavaPsiImplementationHelperImpl(Project project) { |
| myProject = project; |
| } |
| |
| @Override |
| public PsiClass getOriginalClass(PsiClass psiClass) { |
| PsiFile psiFile = psiClass.getContainingFile(); |
| |
| VirtualFile vFile = psiFile.getVirtualFile(); |
| final Project project = psiClass.getProject(); |
| final ProjectFileIndex idx = ProjectRootManager.getInstance(project).getFileIndex(); |
| |
| if (vFile == null || !idx.isInLibrarySource(vFile)) return psiClass; |
| final Set<OrderEntry> orderEntries = new THashSet<OrderEntry>(idx.getOrderEntriesForFile(vFile)); |
| final String fqn = psiClass.getQualifiedName(); |
| if (fqn == null) return psiClass; |
| |
| PsiClass original = JavaPsiFacade.getInstance(project).findClass(fqn, new GlobalSearchScope(project) { |
| @Override |
| public int compare(@NotNull VirtualFile file1, @NotNull VirtualFile file2) { |
| return 0; |
| } |
| |
| @Override |
| public boolean contains(@NotNull VirtualFile file) { |
| // order for file and vFile has non empty intersection. |
| List<OrderEntry> entries = idx.getOrderEntriesForFile(file); |
| //noinspection ForLoopReplaceableByForEach |
| for (int i = 0; i < entries.size(); i++) { |
| final OrderEntry entry = entries.get(i); |
| if (orderEntries.contains(entry)) return true; |
| } |
| return false; |
| } |
| |
| @Override |
| public boolean isSearchInModuleContent(@NotNull Module aModule) { |
| return false; |
| } |
| |
| @Override |
| public boolean isSearchInLibraries() { |
| return true; |
| } |
| }); |
| |
| return original != null ? original : psiClass; |
| } |
| |
| @NotNull |
| @Override |
| public PsiElement getClsFileNavigationElement(PsiJavaFile clsFile) { |
| String packageName = clsFile.getPackageName(); |
| PsiClass[] classes = clsFile.getClasses(); |
| if (classes.length == 0) return clsFile; |
| String sourceFileName = ((ClsClassImpl)classes[0]).getSourceFileName(); |
| String relativeFilePath = packageName.isEmpty() ? sourceFileName : packageName.replace('.', '/') + '/' + sourceFileName; |
| |
| final VirtualFile vFile = clsFile.getContainingFile().getVirtualFile(); |
| ProjectFileIndex projectFileIndex = ProjectFileIndex.SERVICE.getInstance(clsFile.getProject()); |
| final Set<VirtualFile> sourceRoots = ContainerUtil.newLinkedHashSet(); |
| for (OrderEntry orderEntry : projectFileIndex.getOrderEntriesForFile(vFile)) { |
| if (orderEntry instanceof LibraryOrSdkOrderEntry) { |
| Collections.addAll(sourceRoots, orderEntry.getFiles(OrderRootType.SOURCES)); |
| } |
| } |
| for (VirtualFile root : sourceRoots) { |
| VirtualFile source = root.findFileByRelativePath(relativeFilePath); |
| if (source != null) { |
| PsiFile psiSource = clsFile.getManager().findFile(source); |
| if (psiSource instanceof PsiClassOwner) { |
| return psiSource; |
| } |
| } |
| } |
| |
| return clsFile; |
| } |
| |
| @NotNull |
| @Override |
| public LanguageLevel getEffectiveLanguageLevel(@Nullable VirtualFile virtualFile) { |
| if (virtualFile == null) return PsiUtil.getLanguageLevel(myProject); |
| |
| final VirtualFile folder = virtualFile.getParent(); |
| if (folder != null) { |
| final LanguageLevel level = folder.getUserData(LanguageLevel.KEY); |
| if (level != null) return level; |
| } |
| |
| final ProjectFileIndex index = ProjectRootManager.getInstance(myProject).getFileIndex(); |
| Module module = index.getModuleForFile(virtualFile); |
| if (module != null && index.isInSourceContent(virtualFile)) { |
| return EffectiveLanguageLevelUtil.getEffectiveLanguageLevel(module); |
| } |
| |
| LanguageLevel classesLanguageLevel = getClassesLanguageLevel(virtualFile); |
| return classesLanguageLevel != null ? classesLanguageLevel : PsiUtil.getLanguageLevel(myProject); |
| } |
| |
| /** |
| * For files under a library source root, returns the language level configured for the corresponding classes root. |
| * |
| * @param virtualFile virtual file for which language level is requested. |
| * @return language level for classes root or null if file is not under a library source root or no matching classes root is found. |
| */ |
| @Nullable |
| private LanguageLevel getClassesLanguageLevel(VirtualFile virtualFile) { |
| final ProjectFileIndex index = ProjectRootManager.getInstance(myProject).getFileIndex(); |
| final VirtualFile sourceRoot = index.getSourceRootForFile(virtualFile); |
| final VirtualFile folder = virtualFile.getParent(); |
| if (sourceRoot != null && folder != null) { |
| String relativePath = VfsUtilCore.getRelativePath(folder, sourceRoot, '/'); |
| if (relativePath == null) { |
| throw new AssertionError("Null relative path: folder=" + folder + "; root=" + sourceRoot); |
| } |
| List<OrderEntry> orderEntries = index.getOrderEntriesForFile(virtualFile); |
| if (orderEntries.isEmpty()) { |
| LOG.error("Inconsistent: " + DirectoryIndex.getInstance(myProject).getInfoForFile(folder).toString()); |
| } |
| final VirtualFile[] files = orderEntries.get(0).getFiles(OrderRootType.CLASSES); |
| for (VirtualFile rootFile : files) { |
| final VirtualFile classFile = rootFile.findFileByRelativePath(relativePath); |
| if (classFile != null) { |
| final PsiJavaFile javaFile = getPsiFileInRoot(classFile); |
| if (javaFile != null) { |
| return javaFile.getLanguageLevel(); |
| } |
| } |
| } |
| return LanguageLevelProjectExtension.getInstance(myProject).getLanguageLevel(); |
| } |
| return null; |
| } |
| |
| @Nullable |
| private PsiJavaFile getPsiFileInRoot(final VirtualFile dirFile) { |
| final VirtualFile[] children = dirFile.getChildren(); |
| for (VirtualFile child : children) { |
| if (StdFileTypes.CLASS.equals(child.getFileType())) { |
| final PsiFile psiFile = PsiManager.getInstance(myProject).findFile(child); |
| if (psiFile instanceof PsiJavaFile) return (PsiJavaFile)psiFile; |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public ASTNode getDefaultImportAnchor(PsiImportList list, PsiImportStatementBase statement) { |
| CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(list.getProject()); |
| ImportHelper importHelper = new ImportHelper(settings); |
| return importHelper.getDefaultAnchor(list, statement); |
| } |
| |
| @Nullable |
| @Override |
| public PsiElement getDefaultMemberAnchor(@NotNull PsiClass aClass, @NotNull PsiMember member) { |
| CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(aClass.getProject()); |
| MemberOrderService service = ServiceManager.getService(MemberOrderService.class); |
| PsiElement anchor = service.getAnchor(member, settings.getCommonSettings(JavaLanguage.INSTANCE), aClass); |
| |
| PsiElement newAnchor = skipWhitespaces(aClass, anchor); |
| if (newAnchor != null) { |
| return newAnchor; |
| } |
| |
| if (anchor != null && anchor != aClass) { |
| anchor = anchor.getNextSibling(); |
| while (anchor instanceof PsiJavaToken && (anchor.getText().equals(",") || anchor.getText().equals(";"))) { |
| final boolean afterComma = anchor.getText().equals(","); |
| anchor = anchor.getNextSibling(); |
| if (afterComma) { |
| newAnchor = skipWhitespaces(aClass, anchor); |
| if (newAnchor != null) return newAnchor; |
| } |
| } |
| if (anchor != null) { |
| return anchor; |
| } |
| } |
| |
| // The main idea is to avoid to anchor to 'white space' element because that causes reformatting algorithm |
| // to perform incorrectly. The algorithm is encapsulated at the PostprocessReformattingAspect.doPostponedFormattingInner(). |
| final PsiElement lBrace = aClass.getLBrace(); |
| if (lBrace != null) { |
| PsiElement result = lBrace.getNextSibling(); |
| while (result instanceof PsiWhiteSpace) { |
| result = result.getNextSibling(); |
| } |
| return result; |
| } |
| |
| return aClass.getRBrace(); |
| } |
| |
| private static PsiElement skipWhitespaces(PsiClass aClass, PsiElement anchor) { |
| if (anchor != null && PsiTreeUtil.skipSiblingsForward(anchor, PsiWhiteSpace.class) == aClass.getRBrace()) { |
| // Given member should be inserted as the last child. |
| return aClass.getRBrace(); |
| } |
| return null; |
| } |
| |
| @Override |
| public void setupCatchBlock(@NotNull String exceptionName, @NotNull PsiType exceptionType, PsiElement context, @NotNull PsiCatchSection catchSection) { |
| final FileTemplate catchBodyTemplate = FileTemplateManager.getInstance().getCodeTemplate(JavaTemplateUtil.TEMPLATE_CATCH_BODY); |
| LOG.assertTrue(catchBodyTemplate != null); |
| |
| final Properties props = new Properties(); |
| props.setProperty(FileTemplate.ATTRIBUTE_EXCEPTION, exceptionName); |
| props.setProperty(FileTemplate.ATTRIBUTE_EXCEPTION_TYPE, exceptionType.getCanonicalText()); |
| if (context != null && context.isPhysical()) { |
| final PsiDirectory directory = context.getContainingFile().getContainingDirectory(); |
| if (directory != null) { |
| JavaTemplateUtil.setPackageNameAttribute(props, directory); |
| } |
| } |
| |
| final PsiCodeBlock codeBlockFromText; |
| try { |
| codeBlockFromText = PsiElementFactory.SERVICE.getInstance(myProject).createCodeBlockFromText("{\n" + catchBodyTemplate.getText(props) + "\n}", null); |
| } |
| catch (ProcessCanceledException ce) { |
| throw ce; |
| } |
| catch (Exception e) { |
| throw new IncorrectOperationException("Incorrect file template", (Throwable)e); |
| } |
| catchSection.getCatchBlock().replace(codeBlockFromText); |
| } |
| } |