2 * Copyright 2000-2016 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.codeInspection;
18 import com.intellij.analysis.AnalysisScope;
19 import com.intellij.codeInsight.daemon.HighlightDisplayKey;
20 import com.intellij.codeInspection.ex.*;
21 import com.intellij.conversion.ConversionListener;
22 import com.intellij.conversion.ConversionService;
23 import com.intellij.ide.impl.PatchProjectUtil;
24 import com.intellij.ide.impl.ProjectUtil;
25 import com.intellij.openapi.application.ApplicationInfo;
26 import com.intellij.openapi.application.ApplicationManager;
27 import com.intellij.openapi.application.ex.ApplicationEx;
28 import com.intellij.openapi.application.ex.ApplicationInfoEx;
29 import com.intellij.openapi.application.ex.ApplicationManagerEx;
30 import com.intellij.openapi.diagnostic.Logger;
31 import com.intellij.openapi.progress.ProgressManager;
32 import com.intellij.openapi.progress.util.ProgressIndicatorBase;
33 import com.intellij.openapi.project.Project;
34 import com.intellij.openapi.util.Comparing;
35 import com.intellij.openapi.util.io.FileUtil;
36 import com.intellij.openapi.vfs.LocalFileSystem;
37 import com.intellij.openapi.vfs.VirtualFile;
38 import com.intellij.openapi.vfs.VirtualFileManager;
39 import com.intellij.profile.Profile;
40 import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
41 import com.intellij.psi.PsiDirectory;
42 import com.intellij.psi.PsiManager;
43 import com.intellij.psi.search.GlobalSearchScopesCore;
44 import com.intellij.psi.search.scope.packageSet.NamedScope;
45 import com.intellij.psi.search.scope.packageSet.NamedScopesHolder;
46 import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
47 import org.jdom.JDOMException;
48 import org.jetbrains.annotations.NonNls;
49 import org.jetbrains.annotations.Nullable;
52 import java.io.FileWriter;
53 import java.io.IOException;
59 @SuppressWarnings({"UseOfSystemOutOrSystemErr"})
60 public class InspectionApplication {
61 private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.InspectionApplication");
63 public InspectionToolCmdlineOptionHelpProvider myHelpProvider = null;
64 public String myProjectPath = null;
65 public String myOutPath = null;
66 public String mySourceDirectory = null;
67 public String myStubProfile = null;
68 public String myProfileName = null;
69 public String myProfilePath = null;
70 public boolean myRunWithEditorSettings = false;
71 public boolean myRunGlobalToolsOnly = false;
72 private Project myProject;
73 private int myVerboseLevel = 0;
74 public String myOutputFormat = null;
76 public boolean myErrorCodeRequired = true;
78 @NonNls public static final String DESCRIPTIONS = ".descriptions";
79 @NonNls public static final String PROFILE = "profile";
80 @NonNls public static final String INSPECTIONS_NODE = "inspections";
81 @NonNls public static final String XML_EXTENSION = ".xml";
83 public void startup() {
84 if (myProjectPath == null) {
85 logError("Project to inspect is not defined");
89 if (myProfileName == null && myProfilePath == null && myStubProfile == null) {
90 logError("Profile to inspect with is not defined");
94 final ApplicationEx application = ApplicationManagerEx.getApplicationEx();
95 application.runReadAction(() -> {
97 final ApplicationInfoEx appInfo = (ApplicationInfoEx)ApplicationInfo.getInstance();
98 logMessage(1, InspectionsBundle.message("inspection.application.starting.up",
99 appInfo.getFullApplicationName() + " (build " + appInfo.getBuild().asString() + ")"));
100 application.doNotSave();
101 logMessageLn(1, InspectionsBundle.message("inspection.done"));
105 catch (Exception e) {
109 if (myErrorCodeRequired) application.exit(true, true);
114 private void printHelp() {
115 assert myHelpProvider != null;
117 myHelpProvider.printHelpAndExit();
124 myProjectPath = myProjectPath.replace(File.separatorChar, '/');
125 VirtualFile vfsProject = LocalFileSystem.getInstance().findFileByPath(myProjectPath);
126 if (vfsProject == null) {
127 logError(InspectionsBundle.message("inspection.application.file.cannot.be.found", myProjectPath));
131 logMessage(1, InspectionsBundle.message("inspection.application.opening.project"));
132 final ConversionService conversionService = ConversionService.getInstance();
133 if (conversionService.convertSilently(myProjectPath, createConversionListener()).openingIsCanceled()) {
137 myProject = ProjectUtil.openOrImport(myProjectPath, null, false);
139 if (myProject == null) {
140 logError("Unable to open project");
145 ApplicationManager.getApplication().runWriteAction(() -> VirtualFileManager.getInstance().refreshWithoutFileWatcher(false));
147 PatchProjectUtil.patchProject(myProject);
149 logMessageLn(1, InspectionsBundle.message("inspection.done"));
150 logMessage(1, InspectionsBundle.message("inspection.application.initializing.project"));
152 Profile inspectionProfile = loadInspectionProfile();
153 if (inspectionProfile == null) return;
155 final InspectionManagerEx im = (InspectionManagerEx)InspectionManager.getInstance(myProject);
157 final GlobalInspectionContextImpl inspectionContext = im.createNewGlobalContext(true);
158 inspectionContext.setExternalProfile((InspectionProfile)inspectionProfile);
159 im.setProfile(inspectionProfile.getName());
161 final AnalysisScope scope;
162 if (mySourceDirectory == null) {
163 final String scopeName = System.getProperty("idea.analyze.scope");
164 final NamedScope namedScope = scopeName != null ? NamedScopesHolder.getScope(myProject, scopeName) : null;
165 scope = namedScope != null ? new AnalysisScope(GlobalSearchScopesCore.filterScope(myProject, namedScope), myProject)
166 : new AnalysisScope(myProject);
169 mySourceDirectory = mySourceDirectory.replace(File.separatorChar, '/');
171 VirtualFile vfsDir = LocalFileSystem.getInstance().findFileByPath(mySourceDirectory);
172 if (vfsDir == null) {
173 logError(InspectionsBundle.message("inspection.application.directory.cannot.be.found", mySourceDirectory));
177 PsiDirectory psiDirectory = PsiManager.getInstance(myProject).findDirectory(vfsDir);
178 scope = new AnalysisScope(psiDirectory);
181 logMessageLn(1, InspectionsBundle.message("inspection.done"));
183 if (!myRunWithEditorSettings) {
184 logMessageLn(1, InspectionsBundle.message("inspection.application.chosen.profile.log message", inspectionProfile.getName()));
187 InspectionsReportConverter reportConverter = getReportConverter(myOutputFormat);
188 if (reportConverter == null && myOutputFormat != null && myOutputFormat.endsWith(".xsl")) {
190 reportConverter = new XSLTReportConverter(myOutputFormat);
193 final String resultsDataPath;
194 if ((reportConverter == null || !reportConverter.useTmpDirForRawData()) // use default xml converter(if null( or don't store default xml report in tmp dir
195 && myOutPath != null) { // and don't use STDOUT stream
196 resultsDataPath = myOutPath;
200 tmpDir = FileUtil.createTempDirectory("inspections", "data");
201 resultsDataPath = tmpDir.getPath();
203 catch (IOException e) {
205 System.err.println("Cannot create tmp directory.");
211 final List<File> inspectionsResults = new ArrayList<File>();
212 ProgressManager.getInstance().runProcess(() -> {
213 if (!GlobalInspectionContextUtil.canRunInspections(myProject, false)) {
217 inspectionContext.launchInspectionsOffline(scope, resultsDataPath, myRunGlobalToolsOnly, inspectionsResults);
218 logMessageLn(1, "\n" + InspectionsBundle.message("inspection.capitalized.done") + "\n");
219 if (!myErrorCodeRequired) {
222 }, new ProgressIndicatorBase() {
223 private String lastPrefix = "";
224 private int myLastPercent = -1;
227 public void setText(String text) {
228 if (myVerboseLevel == 0) return;
230 if (myVerboseLevel == 1) {
231 String prefix = getPrefix(text);
232 if (prefix == null) return;
233 if (prefix.equals(lastPrefix)) {
239 logMessageLn(1, prefix);
243 if (myVerboseLevel == 3) {
244 if (!isIndeterminate() && getFraction() > 0) {
245 final int percent = (int)(getFraction() * 100);
246 if (myLastPercent == percent) return;
247 myLastPercent = percent;
248 String msg = InspectionsBundle.message("inspection.display.name") + " " + percent + "%";
249 logMessageLn(2, msg);
254 logMessageLn(2, text);
257 final String descriptionsFile = resultsDataPath + File.separatorChar + DESCRIPTIONS + XML_EXTENSION;
258 describeInspections(descriptionsFile,
259 myRunWithEditorSettings ? null : inspectionProfile.getName(),
260 (InspectionProfile)inspectionProfile);
261 inspectionsResults.add(new File(descriptionsFile));
263 if (reportConverter != null) {
265 reportConverter.convert(resultsDataPath, myOutPath, inspectionContext.getTools(), inspectionsResults);
267 catch (InspectionsReportConverter.ConversionException e) {
268 logError("\n" + e.getMessage());
273 catch (IOException e) {
275 logError(e.getMessage());
278 catch (Throwable e) {
280 logError(e.getMessage());
285 if (tmpDir != null) {
286 FileUtil.delete(tmpDir);
291 private void gracefulExit() {
292 if (myErrorCodeRequired) {
297 throw new RuntimeException("Failed to proceed");
301 private void closeProject() {
302 if (myProject != null && !myProject.isDisposed()) {
303 ProjectUtil.closeAndDispose(myProject);
309 private Profile loadInspectionProfile() throws IOException, JDOMException {
310 Profile inspectionProfile = null;
312 //fetch profile by name from project file (project profiles can be disabled)
313 if (myProfileName != null) {
314 inspectionProfile = loadProfileByName(myProfileName);
315 if (inspectionProfile == null) {
316 logError("Profile with configured name (" + myProfileName + ") was not found (neither in project nor in config directory)");
320 return inspectionProfile;
323 if (myProfilePath != null) {
324 inspectionProfile = loadProfileByPath(myProfilePath);
325 if (inspectionProfile == null) {
326 logError("Failed to load profile from \'" + myProfilePath + "\'");
330 return inspectionProfile;
333 if (myStubProfile != null) {
334 if (!myRunWithEditorSettings) {
335 inspectionProfile = loadProfileByName(myStubProfile);
336 if (inspectionProfile != null) return inspectionProfile;
338 inspectionProfile = loadProfileByPath(myStubProfile);
339 if (inspectionProfile != null) return inspectionProfile;
342 inspectionProfile = InspectionProjectProfileManager.getInstance(myProject).getCurrentProfile();
343 logError("Using default project profile");
345 return inspectionProfile;
349 private Profile loadProfileByPath(final String profilePath) throws IOException, JDOMException {
350 Profile inspectionProfile = ApplicationInspectionProfileManagerImpl.getInstanceImpl().loadProfile(profilePath);
351 if (inspectionProfile != null) {
352 logMessageLn(1, "Loaded profile \'" + inspectionProfile.getName() + "\' from file \'" + profilePath + "\'");
354 return inspectionProfile;
358 private Profile loadProfileByName(final String profileName) {
359 Profile inspectionProfile = InspectionProjectProfileManager.getInstance(myProject).getProfile(profileName, false);
360 if (inspectionProfile != null) {
361 logMessageLn(1, "Loaded shared project profile \'" + profileName + "\'");
364 //check if ide profile is used for project
365 final Collection<Profile> profiles = InspectionProjectProfileManager.getInstance(myProject).getProfiles();
366 for (Profile profile : profiles) {
367 if (Comparing.strEqual(profile.getName(), profileName)) {
368 inspectionProfile = profile;
369 logMessageLn(1, "Loaded local profile \'" + profileName + "\'");
375 return inspectionProfile;
380 private static InspectionsReportConverter getReportConverter(@Nullable final String outputFormat) {
381 for (InspectionsReportConverter converter : InspectionsReportConverter.EP_NAME.getExtensions()) {
382 if (converter.getFormatName().equals(outputFormat)) {
389 private ConversionListener createConversionListener() {
390 return new ConversionListener() {
392 public void conversionNeeded() {
393 logMessageLn(1, InspectionsBundle.message("inspection.application.project.has.older.format.and.will.be.converted"));
397 public void successfullyConverted(final File backupDir) {
398 logMessageLn(1, InspectionsBundle.message(
399 "inspection.application.project.was.succesfully.converted.old.project.files.were.saved.to.0",
400 backupDir.getAbsolutePath()));
404 public void error(final String message) {
405 logError(InspectionsBundle.message("inspection.application.cannot.convert.project.0", message));
409 public void cannotWriteToFiles(final List<File> readonlyFiles) {
410 StringBuilder files = new StringBuilder();
411 for (File file : readonlyFiles) {
412 files.append(file.getAbsolutePath()).append("; ");
414 logError(InspectionsBundle.message("inspection.application.cannot.convert.the.project.the.following.files.are.read.only.0", files.toString()));
420 private static String getPrefix(final String text) {
421 //noinspection HardCodedStringLiteral
422 int idx = text.indexOf(" in ");
424 //noinspection HardCodedStringLiteral
425 idx = text.indexOf(" of ");
428 return idx == -1 ? null : text.substring(0, idx);
431 public void setVerboseLevel(int verboseLevel) {
432 myVerboseLevel = verboseLevel;
435 private void logMessage(int minVerboseLevel, String message) {
436 if (myVerboseLevel >= minVerboseLevel) {
437 System.out.print(message);
441 private static void logError(String message) {
442 System.err.println(message);
445 private void logMessageLn(int minVerboseLevel, String message) {
446 if (myVerboseLevel >= minVerboseLevel) {
447 System.out.println(message);
451 private static void describeInspections(@NonNls String myOutputPath, final String name, final InspectionProfile profile) throws IOException {
452 final InspectionToolWrapper[] toolWrappers = profile.getInspectionTools(null);
453 final Map<String, Set<InspectionToolWrapper>> map = new HashMap<String, Set<InspectionToolWrapper>>();
454 for (InspectionToolWrapper toolWrapper : toolWrappers) {
455 final String groupName = toolWrapper.getGroupDisplayName();
456 Set<InspectionToolWrapper> groupInspections = map.get(groupName);
457 if (groupInspections == null) {
458 groupInspections = new HashSet<InspectionToolWrapper>();
459 map.put(groupName, groupInspections);
461 groupInspections.add(toolWrapper);
464 FileWriter fw = new FileWriter(myOutputPath);
466 @NonNls final PrettyPrintWriter xmlWriter = new PrettyPrintWriter(fw);
467 xmlWriter.startNode(INSPECTIONS_NODE);
469 xmlWriter.addAttribute(PROFILE, name);
471 for (String groupName : map.keySet()) {
472 xmlWriter.startNode("group");
473 xmlWriter.addAttribute("name", groupName);
474 final Set<InspectionToolWrapper> entries = map.get(groupName);
475 for (InspectionToolWrapper toolWrapper : entries) {
476 xmlWriter.startNode("inspection");
477 final String shortName = toolWrapper.getShortName();
478 xmlWriter.addAttribute("shortName", shortName);
479 xmlWriter.addAttribute("displayName", toolWrapper.getDisplayName());
480 final boolean toolEnabled = profile.isToolEnabled(HighlightDisplayKey.find(shortName));
481 xmlWriter.addAttribute("enabled", Boolean.toString(toolEnabled));
482 final String description = toolWrapper.loadDescription();
483 if (description != null) {
484 xmlWriter.setValue(description);
487 LOG.error(shortName + " descriptionUrl==" + toolWrapper);