/* * Copyright 2000-2015 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.codeInspection.ex; import com.intellij.analysis.AnalysisScope; import com.intellij.codeInspection.*; import com.intellij.codeInspection.lang.GlobalInspectionContextExtension; import com.intellij.codeInspection.lang.InspectionExtensionsFactory; import com.intellij.codeInspection.reference.*; import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.progress.*; import com.intellij.openapi.progress.util.ProgressWrapper; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.IndexNotReadyException; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.UserDataHolderBase; import com.intellij.profile.Profile; import com.intellij.profile.codeInspection.InspectionProfileManager; import com.intellij.profile.codeInspection.InspectionProjectProfileManager; import com.intellij.psi.*; import com.intellij.psi.search.LocalSearchScope; import com.intellij.psi.search.scope.packageSet.NamedScope; import com.intellij.util.Function; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.HashMap; import gnu.trove.THashMap; import gnu.trove.THashSet; import gnu.trove.TObjectHashingStrategy; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import consulo.annotations.RequiredDispatchThread; import java.util.*; public class GlobalInspectionContextBase extends UserDataHolderBase implements GlobalInspectionContext { private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.ex.GlobalInspectionContextImpl"); private static final TObjectHashingStrategy<Tools> TOOLS_HASHING_STRATEGY = new TObjectHashingStrategy<Tools>() { @Override public int computeHashCode(Tools object) { return object.getShortName().hashCode(); } @Override public boolean equals(Tools o1, Tools o2) { return o1.getShortName().equals(o2.getShortName()); } }; private RefManager myRefManager; private AnalysisScope myCurrentScope; @NotNull private final Project myProject; private final List<JobDescriptor> myJobDescriptors = new ArrayList<JobDescriptor>(); private final StdJobDescriptors myStdJobDescriptors = new StdJobDescriptors(); protected ProgressIndicator myProgressIndicator = new EmptyProgressIndicator(); private InspectionProfile myExternalProfile; protected final Map<Key, GlobalInspectionContextExtension> myExtensions = new HashMap<Key, GlobalInspectionContextExtension>(); protected final Map<String, Tools> myTools = new THashMap<String, Tools>(); @NonNls public static final String LOCAL_TOOL_ATTRIBUTE = "is_local_tool"; public GlobalInspectionContextBase(@NotNull Project project) { myProject = project; for (InspectionExtensionsFactory factory : Extensions.getExtensions(InspectionExtensionsFactory.EP_NAME)) { final GlobalInspectionContextExtension extension = factory.createGlobalInspectionContextExtension(); myExtensions.put(extension.getID(), extension); } } AnalysisScope getCurrentScope() { return myCurrentScope; } @Override @NotNull public Project getProject() { return myProject; } @Override public <T> T getExtension(@NotNull final Key<T> key) { //noinspection unchecked return (T)myExtensions.get(key); } public InspectionProfile getCurrentProfile() { if (myExternalProfile != null) return myExternalProfile; InspectionManagerBase managerEx = (InspectionManagerBase)InspectionManager.getInstance(myProject); String currentProfile = managerEx.getCurrentProfile(); final InspectionProjectProfileManager inspectionProfileManager = InspectionProjectProfileManager.getInstance(myProject); Profile profile = inspectionProfileManager.getProfile(currentProfile, false); if (profile == null) { profile = InspectionProfileManager.getInstance().getProfile(currentProfile); if (profile != null) return (InspectionProfile)profile; final String[] availableProfileNames = inspectionProfileManager.getAvailableProfileNames(); if (availableProfileNames.length == 0) { //can't be return null; } profile = inspectionProfileManager.getProfile(availableProfileNames[0]); } return (InspectionProfile)profile; } @Override public boolean isSuppressed(@NotNull RefEntity entity, @NotNull String id) { return entity instanceof RefElementImpl && ((RefElementImpl)entity).isSuppressed(id); } @Override public boolean shouldCheck(@NotNull RefEntity entity, @NotNull GlobalInspectionTool tool) { return !(entity instanceof RefElementImpl) || isToCheckMember((RefElementImpl)entity, tool); } @Override public boolean isSuppressed(@NotNull PsiElement element, @NotNull String id) { final RefManagerImpl refManager = (RefManagerImpl)getRefManager(); if (refManager.isDeclarationsFound()) { final RefElement refElement = refManager.getReference(element); return refElement instanceof RefElementImpl && ((RefElementImpl)refElement).isSuppressed(id); } return SuppressionUtil.isSuppressed(element, id); } void cleanupTools() { myProgressIndicator.cancel(); for (GlobalInspectionContextExtension extension : myExtensions.values()) { extension.cleanup(); } for (Tools tools : myTools.values()) { for (ScopeToolState state : tools.getTools()) { InspectionToolWrapper toolWrapper = state.getTool(); toolWrapper.cleanup(myProject); } } myTools.clear(); /* EntryPointsManager entryPointsManager = EntryPointsManager.getInstance(getProject()); if (entryPointsManager != null) { entryPointsManager.cleanup(); } */ if (myRefManager != null) { ((RefManagerImpl)myRefManager).cleanup(); myRefManager = null; if (myCurrentScope != null){ myCurrentScope.invalidate(); myCurrentScope = null; } } myJobDescriptors.clear(); } public void setCurrentScope(@NotNull AnalysisScope currentScope) { myCurrentScope = currentScope; } public void doInspections(@NotNull final AnalysisScope scope) { if (!GlobalInspectionContextUtil.canRunInspections(myProject, true)) return; cleanup(); ApplicationManager.getApplication().invokeLater(new Runnable() { @Override public void run() { myCurrentScope = scope; launchInspections(scope); } }, ApplicationManager.getApplication().getDisposed()); } @Override @NotNull public RefManager getRefManager() { if (myRefManager == null) { myRefManager = ApplicationManager.getApplication().runReadAction(new Computable<RefManagerImpl>() { @Override public RefManagerImpl compute() { return new RefManagerImpl(myProject, myCurrentScope, GlobalInspectionContextBase.this); } }); } return myRefManager; } public boolean isToCheckMember(@NotNull RefElement owner, @NotNull InspectionProfileEntry tool) { return isToCheckFile(((RefElementImpl)owner).getContainingFile(), tool) && !((RefElementImpl)owner).isSuppressed(tool.getShortName()); } public boolean isToCheckFile(PsiFile file, @NotNull InspectionProfileEntry tool) { final Tools tools = myTools.get(tool.getShortName()); if (tools != null && file != null) { for (ScopeToolState state : tools.getTools()) { final NamedScope namedScope = state.getScope(file.getProject()); if (namedScope == null || namedScope.getValue().contains(file, getCurrentProfile().getProfileManager().getScopesManager())) { return state.isEnabled() && state.getTool().getTool() == tool; } } } return false; } protected void launchInspections(@NotNull final AnalysisScope scope) { ApplicationManager.getApplication().assertIsDispatchThread(); PsiDocumentManager.getInstance(myProject).commitAllDocuments(); LOG.info("Code inspection started"); ProgressManager.getInstance().run(new Task.Backgroundable(getProject(), InspectionsBundle.message("inspection.progress.title"), true, createOption()) { @Override public void run(@NotNull ProgressIndicator indicator) { performInspectionsWithProgress(scope, false, false); } @RequiredDispatchThread @Override public void onSuccess() { notifyInspectionsFinished(); } }); } @NotNull protected PerformInBackgroundOption createOption() { return new PerformInBackgroundOption(){ @Override public boolean shouldStartInBackground() { return true; } @Override public void processSentToBackground() { } }; } protected void notifyInspectionsFinished() { } public void performInspectionsWithProgress(@NotNull final AnalysisScope scope, final boolean runGlobalToolsOnly, final boolean isOfflineInspections) { myProgressIndicator = ProgressManager.getInstance().getProgressIndicator(); if (myProgressIndicator == null) { throw new IllegalStateException("Inspections must be run under progress"); } final PsiManager psiManager = PsiManager.getInstance(myProject); //init manager in read action RefManagerImpl refManager = (RefManagerImpl)getRefManager(); try { psiManager.startBatchFilesProcessingMode(); refManager.inspectionReadActionStarted(); getStdJobDescriptors().BUILD_GRAPH.setTotalAmount(scope.getFileCount()); getStdJobDescriptors().LOCAL_ANALYSIS.setTotalAmount(scope.getFileCount()); getStdJobDescriptors().FIND_EXTERNAL_USAGES.setTotalAmount(0); //to override current progress in order to hide useless messages/% ProgressManager.getInstance().executeProcessUnderProgress(new Runnable() { @Override public void run() { runTools(scope, runGlobalToolsOnly, isOfflineInspections); } }, ProgressWrapper.wrap(myProgressIndicator)); } catch (ProcessCanceledException e) { cleanup(); throw e; } catch (IndexNotReadyException e) { cleanup(); DumbService.getInstance(myProject).showDumbModeNotification("Usage search is not available until indices are ready"); throw new ProcessCanceledException(); } catch (Throwable e) { LOG.error(e); } finally { refManager.inspectionReadActionFinished(); psiManager.finishBatchFilesProcessingMode(); } } protected void runTools(@NotNull AnalysisScope scope, boolean runGlobalToolsOnly, boolean isOfflineInspections) { } public void initializeTools(@NotNull List<Tools> outGlobalTools, @NotNull List<Tools> outLocalTools, @NotNull List<Tools> outGlobalSimpleTools) { final List<Tools> usedTools = getUsedTools(); for (Tools currentTools : usedTools) { final String shortName = currentTools.getShortName(); myTools.put(shortName, currentTools); InspectionToolWrapper toolWrapper = currentTools.getTool(); classifyTool(outGlobalTools, outLocalTools, outGlobalSimpleTools, currentTools, toolWrapper); for (ScopeToolState state : currentTools.getTools()) { state.getTool().initialize(this); } JobDescriptor[] jobDescriptors = toolWrapper.getJobDescriptors(this); for (JobDescriptor jobDescriptor : jobDescriptors) { appendJobDescriptor(jobDescriptor); } } for (GlobalInspectionContextExtension extension : myExtensions.values()) { extension.performPreRunActivities(outGlobalTools, outLocalTools, this); } } @NotNull protected List<Tools> getUsedTools() { InspectionProfileImpl profile = new InspectionProfileImpl((InspectionProfileImpl)getCurrentProfile()); List<Tools> tools = profile.getAllEnabledInspectionTools(myProject); Set<InspectionToolWrapper> dependentTools = new LinkedHashSet<InspectionToolWrapper>(); for (Tools tool : tools) { profile.collectDependentInspections(tool.getTool(), dependentTools, getProject()); } if (dependentTools.isEmpty()) { return tools; } Set<Tools> set = new THashSet<Tools>(tools, TOOLS_HASHING_STRATEGY); set.addAll(ContainerUtil.map(dependentTools, new Function<InspectionToolWrapper, ToolsImpl>() { @Override public ToolsImpl fun(InspectionToolWrapper toolWrapper) { return new ToolsImpl(toolWrapper, toolWrapper.getDefaultLevel(), true, true); } })); return new ArrayList<Tools>(set); } private static void classifyTool(@NotNull List<Tools> outGlobalTools, @NotNull List<Tools> outLocalTools, @NotNull List<Tools> outGlobalSimpleTools, @NotNull Tools currentTools, @NotNull InspectionToolWrapper toolWrapper) { if (toolWrapper instanceof LocalInspectionToolWrapper) { outLocalTools.add(currentTools); } else if (toolWrapper instanceof GlobalInspectionToolWrapper) { if (toolWrapper.getTool() instanceof GlobalSimpleInspectionTool) { outGlobalSimpleTools.add(currentTools); } else if (toolWrapper.getTool() instanceof GlobalInspectionTool) { outGlobalTools.add(currentTools); } else { throw new RuntimeException("unknown global tool " + toolWrapper); } } else { throw new RuntimeException("unknown tool " + toolWrapper); } } @NotNull public Map<String, Tools> getTools() { return myTools; } private void appendJobDescriptor(@NotNull JobDescriptor job) { if (!myJobDescriptors.contains(job)) { myJobDescriptors.add(job); job.setDoneAmount(0); } } public void codeCleanup(@NotNull Project project, @NotNull AnalysisScope scope, @NotNull InspectionProfile profile, @Nullable String commandName, @Nullable Runnable postRunnable, final boolean modal) {} public static void codeCleanup(@NotNull Project project, @NotNull AnalysisScope scope, @Nullable Runnable runnable) { GlobalInspectionContextBase globalContext = (GlobalInspectionContextBase)InspectionManager.getInstance(project).createNewGlobalContext(false); final InspectionProfile profile = InspectionProjectProfileManager.getInstance(project).getInspectionProfile(); globalContext.codeCleanup(project, scope, profile, null, runnable, false); } public static void cleanupElements(@NotNull final Project project, @Nullable final Runnable runnable, @NotNull PsiElement... scope) { final List<SmartPsiElementPointer<PsiElement>> elements = new ArrayList<SmartPsiElementPointer<PsiElement>>(); final SmartPointerManager manager = SmartPointerManager.getInstance(project); for (PsiElement element : scope) { elements.add(manager.createSmartPsiElementPointer(element)); } Runnable cleanupRunnable = new Runnable() { @Override public void run() { final List<PsiElement> psiElements = new ArrayList<PsiElement>(); for (SmartPsiElementPointer<PsiElement> element : elements) { PsiElement psiElement = element.getElement(); if (psiElement != null && psiElement.isPhysical()) { psiElements.add(psiElement); } } if (psiElements.isEmpty()) { return; } GlobalInspectionContextBase globalContext = (GlobalInspectionContextBase)InspectionManager.getInstance(project).createNewGlobalContext(false); final InspectionProfile profile = InspectionProjectProfileManager.getInstance(project).getInspectionProfile(); AnalysisScope analysisScope = new AnalysisScope(new LocalSearchScope(psiElements.toArray(new PsiElement[psiElements.size()])), project); globalContext.codeCleanup(project, analysisScope, profile, null, runnable, true); } }; Application application = ApplicationManager.getApplication(); if (application.isWriteAccessAllowed() && !application.isUnitTestMode()) { application.invokeLater(cleanupRunnable); } else { cleanupRunnable.run(); } } public void close(boolean noSuspisiousCodeFound) { cleanup(); } @Override public void cleanup() { cleanupTools(); } @Override public void incrementJobDoneAmount(@NotNull JobDescriptor job, @NotNull String message) { if (myProgressIndicator == null) return; ProgressManager.checkCanceled(); int old = job.getDoneAmount(); job.setDoneAmount(old + 1); float totalProgress = getTotalProgress(); myProgressIndicator.setFraction(totalProgress); myProgressIndicator.setText(job.getDisplayName() + " " + message); } private float getTotalProgress() { float totalDone = 0; int totalTotal = 0; for (JobDescriptor jobDescriptor : myJobDescriptors) { totalDone += jobDescriptor.getDoneAmount(); totalTotal += jobDescriptor.getTotalAmount(); } return totalTotal == 0 ? 1 : totalDone / totalTotal; } public void setExternalProfile(InspectionProfile profile) { myExternalProfile = profile; } @Override @NotNull public StdJobDescriptors getStdJobDescriptors() { return myStdJobDescriptors; } }