dc00f6bfb3dd1ce5addeeb4068db06f671f157cb
[idea/community.git] / platform / lang-impl / src / com / intellij / codeInspection / InspectionApplication.java
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.codeInspection;
17
18 import com.intellij.analysis.AnalysisScope;
19 import com.intellij.codeInspection.ex.*;
20 import com.intellij.conversion.ConversionListener;
21 import com.intellij.conversion.ConversionService;
22 import com.intellij.ide.impl.PatchProjectUtil;
23 import com.intellij.ide.impl.ProjectUtil;
24 import com.intellij.openapi.application.ApplicationInfo;
25 import com.intellij.openapi.application.ApplicationManager;
26 import com.intellij.openapi.application.ex.ApplicationEx;
27 import com.intellij.openapi.application.ex.ApplicationInfoEx;
28 import com.intellij.openapi.application.ex.ApplicationManagerEx;
29 import com.intellij.openapi.diagnostic.Logger;
30 import com.intellij.openapi.progress.ProgressManager;
31 import com.intellij.openapi.progress.util.ProgressIndicatorBase;
32 import com.intellij.openapi.project.Project;
33 import com.intellij.openapi.util.Comparing;
34 import com.intellij.openapi.util.io.FileUtil;
35 import com.intellij.openapi.vfs.LocalFileSystem;
36 import com.intellij.openapi.vfs.VirtualFile;
37 import com.intellij.openapi.vfs.VirtualFileManager;
38 import com.intellij.profile.Profile;
39 import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
40 import com.intellij.psi.PsiDirectory;
41 import com.intellij.psi.PsiManager;
42 import com.intellij.psi.search.GlobalSearchScopesCore;
43 import com.intellij.psi.search.scope.packageSet.NamedScope;
44 import com.intellij.psi.search.scope.packageSet.NamedScopesHolder;
45 import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
46 import org.jdom.JDOMException;
47 import org.jetbrains.annotations.NonNls;
48 import org.jetbrains.annotations.Nullable;
49
50 import java.io.File;
51 import java.io.FileWriter;
52 import java.io.IOException;
53 import java.util.*;
54
55 /**
56  * @author max
57  */
58 @SuppressWarnings({"UseOfSystemOutOrSystemErr"})
59 public class InspectionApplication {
60   private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.InspectionApplication");
61
62   public InspectionToolCmdlineOptionHelpProvider myHelpProvider = null;
63   public String myProjectPath = null;
64   public String myOutPath = null;
65   public String mySourceDirectory = null;
66   public String myStubProfile = null;
67   public String myProfileName = null;
68   public String myProfilePath = null;
69   public boolean myRunWithEditorSettings = false;
70   public boolean myRunGlobalToolsOnly = false;
71   private Project myProject;
72   private int myVerboseLevel = 0;
73   public String myOutputFormat = null;
74
75   public boolean myErrorCodeRequired = true;
76
77   @NonNls public static final String DESCRIPTIONS = ".descriptions";
78   @NonNls public static final String PROFILE = "profile";
79   @NonNls public static final String INSPECTIONS_NODE = "inspections";
80   @NonNls public static final String XML_EXTENSION = ".xml";
81
82   public void startup() {
83     if (myProjectPath == null) {
84       logError("Project to inspect is not defined");
85       printHelp();
86     }
87
88     if (myProfileName == null && myProfilePath == null && myStubProfile == null) {
89       logError("Profile to inspect with is not defined");
90       printHelp();
91     }
92
93     final ApplicationEx application = ApplicationManagerEx.getApplicationEx();
94     application.runReadAction(() -> {
95       try {
96         final ApplicationInfoEx appInfo = (ApplicationInfoEx)ApplicationInfo.getInstance();
97         logMessage(1, InspectionsBundle.message("inspection.application.starting.up",
98                                                 appInfo.getFullApplicationName() + " (build " + appInfo.getBuild().asString() + ")"));
99         application.doNotSave();
100         logMessageLn(1, InspectionsBundle.message("inspection.done"));
101
102         this.run();
103       }
104       catch (Exception e) {
105         LOG.error(e);
106       }
107       finally {
108         if (myErrorCodeRequired) application.exit(true, true);
109       }
110     });
111   }
112
113   private void printHelp() {
114     assert myHelpProvider != null;
115
116     myHelpProvider.printHelpAndExit();
117   }
118
119   private void run() {
120
121     File tmpDir = null;
122     try {
123       myProjectPath = myProjectPath.replace(File.separatorChar, '/');
124       VirtualFile vfsProject = LocalFileSystem.getInstance().findFileByPath(myProjectPath);
125       if (vfsProject == null) {
126         logError(InspectionsBundle.message("inspection.application.file.cannot.be.found", myProjectPath));
127         printHelp();
128       }
129
130       logMessage(1, InspectionsBundle.message("inspection.application.opening.project"));
131       final ConversionService conversionService = ConversionService.getInstance();
132       if (conversionService.convertSilently(myProjectPath, createConversionListener()).openingIsCanceled()) {
133         gracefulExit();
134         return;
135       }
136       myProject = ProjectUtil.openOrImport(myProjectPath, null, false);
137
138       if (myProject == null) {
139         logError("Unable to open project");
140         gracefulExit();
141         return;
142       }
143
144       ApplicationManager.getApplication().runWriteAction(() -> VirtualFileManager.getInstance().refreshWithoutFileWatcher(false));
145
146       PatchProjectUtil.patchProject(myProject);
147
148       logMessageLn(1, InspectionsBundle.message("inspection.done"));
149       logMessage(1, InspectionsBundle.message("inspection.application.initializing.project"));
150
151       Profile inspectionProfile = loadInspectionProfile();
152       if (inspectionProfile == null) return;
153
154       final InspectionManagerEx im = (InspectionManagerEx)InspectionManager.getInstance(myProject);
155
156       final GlobalInspectionContextImpl inspectionContext = im.createNewGlobalContext(true);
157       inspectionContext.setExternalProfile((InspectionProfile)inspectionProfile);
158       im.setProfile(inspectionProfile.getName());
159
160       final AnalysisScope scope;
161       if (mySourceDirectory == null) {
162         final String scopeName = System.getProperty("idea.analyze.scope");
163         final NamedScope namedScope = scopeName != null ? NamedScopesHolder.getScope(myProject, scopeName) : null;
164         scope = namedScope != null ? new AnalysisScope(GlobalSearchScopesCore.filterScope(myProject, namedScope), myProject)
165                                    : new AnalysisScope(myProject);
166       }
167       else {
168         mySourceDirectory = mySourceDirectory.replace(File.separatorChar, '/');
169
170         VirtualFile vfsDir = LocalFileSystem.getInstance().findFileByPath(mySourceDirectory);
171         if (vfsDir == null) {
172           logError(InspectionsBundle.message("inspection.application.directory.cannot.be.found", mySourceDirectory));
173           printHelp();
174         }
175
176         PsiDirectory psiDirectory = PsiManager.getInstance(myProject).findDirectory(vfsDir);
177         scope = new AnalysisScope(psiDirectory);
178       }
179
180       logMessageLn(1, InspectionsBundle.message("inspection.done"));
181
182       if (!myRunWithEditorSettings) {
183         logMessageLn(1, InspectionsBundle.message("inspection.application.chosen.profile.log message", inspectionProfile.getName()));
184       }
185
186       InspectionsReportConverter reportConverter = getReportConverter(myOutputFormat);
187       if (reportConverter == null && myOutputFormat != null && myOutputFormat.endsWith(".xsl")) {
188         // xslt converter
189         reportConverter = new XSLTReportConverter(myOutputFormat);
190       }
191
192       final String resultsDataPath;
193       if ((reportConverter == null || !reportConverter.useTmpDirForRawData()) // use default xml converter(if null( or don't store default xml report in tmp dir
194           && myOutPath != null) {  // and don't use STDOUT stream
195         resultsDataPath = myOutPath;
196       }
197       else {
198         try {
199           tmpDir = FileUtil.createTempDirectory("inspections", "data");
200           resultsDataPath = tmpDir.getPath();
201         }
202         catch (IOException e) {
203           LOG.error(e);
204           System.err.println("Cannot create tmp directory.");
205           System.exit(1);
206           return;
207         }
208       }
209
210       final List<File> inspectionsResults = new ArrayList<File>();
211       ProgressManager.getInstance().runProcess(() -> {
212         if (!GlobalInspectionContextUtil.canRunInspections(myProject, false)) {
213           gracefulExit();
214           return;
215         }
216         inspectionContext.launchInspectionsOffline(scope, resultsDataPath, myRunGlobalToolsOnly, inspectionsResults);
217         logMessageLn(1, "\n" + InspectionsBundle.message("inspection.capitalized.done") + "\n");
218         if (!myErrorCodeRequired) {
219           closeProject();
220         }
221       }, new ProgressIndicatorBase() {
222         private String lastPrefix = "";
223         private int myLastPercent = -1;
224
225         @Override
226         public void setText(String text) {
227           if (myVerboseLevel == 0) return;
228
229           if (myVerboseLevel == 1) {
230             String prefix = getPrefix(text);
231             if (prefix == null) return;
232             if (prefix.equals(lastPrefix)) {
233               logMessage(1, ".");
234               return;
235             }
236             lastPrefix = prefix;
237             logMessageLn(1, "");
238             logMessageLn(1, prefix);
239             return;
240           }
241
242           if (myVerboseLevel == 3) {
243             if (!isIndeterminate() && getFraction() > 0) {
244               final int percent = (int)(getFraction() * 100);
245               if (myLastPercent == percent) return;
246               myLastPercent = percent;
247               String msg = InspectionsBundle.message("inspection.display.name") + " " + percent + "%";
248               logMessageLn(2, msg);
249             }
250             return;
251           }
252
253           logMessageLn(2, text);
254         }
255       });
256       final String descriptionsFile = resultsDataPath + File.separatorChar + DESCRIPTIONS + XML_EXTENSION;
257       describeInspections(descriptionsFile,
258                           myRunWithEditorSettings ? null : inspectionProfile.getName());
259       inspectionsResults.add(new File(descriptionsFile));
260       // convert report
261       if (reportConverter != null) {
262         try {
263           reportConverter.convert(resultsDataPath, myOutPath, inspectionContext.getTools(), inspectionsResults);
264         }
265         catch (InspectionsReportConverter.ConversionException e) {
266           logError("\n" + e.getMessage());
267           printHelp();
268         }
269       }
270     }
271     catch (IOException e) {
272       LOG.error(e);
273       logError(e.getMessage());
274       printHelp();
275     }
276     catch (Throwable e) {
277       LOG.error(e);
278       logError(e.getMessage());
279       gracefulExit();
280     }
281     finally {
282       // delete tmp dir
283       if (tmpDir != null) {
284         FileUtil.delete(tmpDir);
285       }
286     }
287   }
288
289   private void gracefulExit() {
290     if (myErrorCodeRequired) {
291       System.exit(1);
292     }
293     else {
294       closeProject();
295       throw new RuntimeException("Failed to proceed");
296     }
297   }
298
299   private void closeProject() {
300     if (myProject != null && !myProject.isDisposed()) {
301       ProjectUtil.closeAndDispose(myProject);
302       myProject = null;
303     }
304   }
305
306   @Nullable
307   private Profile loadInspectionProfile() throws IOException, JDOMException {
308     Profile inspectionProfile = null;
309
310     //fetch profile by name from project file (project profiles can be disabled)
311     if (myProfileName != null) {
312       inspectionProfile = loadProfileByName(myProfileName);
313       if (inspectionProfile == null) {
314         logError("Profile with configured name (" + myProfileName + ") was not found (neither in project nor in config directory)");
315         gracefulExit();
316         return null;
317       }
318       return inspectionProfile;
319     }
320
321     if (myProfilePath != null) {
322       inspectionProfile = loadProfileByPath(myProfilePath);
323       if (inspectionProfile == null) {
324         logError("Failed to load profile from \'" + myProfilePath + "\'");
325         gracefulExit();
326         return null;
327       }
328       return inspectionProfile;
329     }
330
331     if (myStubProfile != null) {
332       if (!myRunWithEditorSettings) {
333         inspectionProfile = loadProfileByName(myStubProfile);
334         if (inspectionProfile != null) return inspectionProfile;
335
336         inspectionProfile = loadProfileByPath(myStubProfile);
337         if (inspectionProfile != null) return inspectionProfile;
338       }
339
340       inspectionProfile = InspectionProjectProfileManager.getInstance(myProject).getCurrentProfile();
341       logError("Using default project profile");
342     }
343     return inspectionProfile;
344   }
345
346   @Nullable
347   private Profile loadProfileByPath(final String profilePath) throws IOException, JDOMException {
348     Profile inspectionProfile = ApplicationInspectionProfileManagerImpl.getInstanceImpl().loadProfile(profilePath);
349     if (inspectionProfile != null) {
350       logMessageLn(1, "Loaded profile \'" + inspectionProfile.getName() + "\' from file \'" + profilePath + "\'");
351     }
352     return inspectionProfile;
353   }
354
355   @Nullable
356   private Profile loadProfileByName(final String profileName) {
357     Profile inspectionProfile = InspectionProjectProfileManager.getInstance(myProject).getProfile(profileName, false);
358     if (inspectionProfile != null) {
359       logMessageLn(1, "Loaded shared project profile \'" + profileName + "\'");
360     }
361     else {
362       //check if ide profile is used for project
363       final Collection<Profile> profiles = InspectionProjectProfileManager.getInstance(myProject).getProfiles();
364       for (Profile profile : profiles) {
365         if (Comparing.strEqual(profile.getName(), profileName)) {
366           inspectionProfile = profile;
367           logMessageLn(1, "Loaded local profile \'" + profileName + "\'");
368           break;
369         }
370       }
371     }
372
373     return inspectionProfile;
374   }
375
376
377   @Nullable
378   private static InspectionsReportConverter getReportConverter(@Nullable final String outputFormat) {
379     for (InspectionsReportConverter converter : InspectionsReportConverter.EP_NAME.getExtensions()) {
380       if (converter.getFormatName().equals(outputFormat)) {
381         return converter;
382       }
383     }
384     return null;
385   }
386
387   private ConversionListener createConversionListener() {
388     return new ConversionListener() {
389       @Override
390       public void conversionNeeded() {
391         logMessageLn(1, InspectionsBundle.message("inspection.application.project.has.older.format.and.will.be.converted"));
392       }
393
394       @Override
395       public void successfullyConverted(final File backupDir) {
396         logMessageLn(1, InspectionsBundle.message(
397           "inspection.application.project.was.succesfully.converted.old.project.files.were.saved.to.0",
398                                                   backupDir.getAbsolutePath()));
399       }
400
401       @Override
402       public void error(final String message) {
403         logError(InspectionsBundle.message("inspection.application.cannot.convert.project.0", message));
404       }
405
406       @Override
407       public void cannotWriteToFiles(final List<File> readonlyFiles) {
408         StringBuilder files = new StringBuilder();
409         for (File file : readonlyFiles) {
410           files.append(file.getAbsolutePath()).append("; ");
411         }
412         logError(InspectionsBundle.message("inspection.application.cannot.convert.the.project.the.following.files.are.read.only.0", files.toString()));
413       }
414     };
415   }
416
417   @Nullable
418   private static String getPrefix(final String text) {
419     //noinspection HardCodedStringLiteral
420     int idx = text.indexOf(" in ");
421     if (idx == -1) {
422       //noinspection HardCodedStringLiteral
423       idx = text.indexOf(" of ");
424     }
425
426     return idx == -1 ? null : text.substring(0, idx);
427   }
428
429   public void setVerboseLevel(int verboseLevel) {
430     myVerboseLevel = verboseLevel;
431   }
432
433   private void logMessage(int minVerboseLevel, String message) {
434     if (myVerboseLevel >= minVerboseLevel) {
435       System.out.print(message);
436     }
437   }
438
439   private static void logError(String message) {
440     System.err.println(message);
441   }
442
443   private void logMessageLn(int minVerboseLevel, String message) {
444     if (myVerboseLevel >= minVerboseLevel) {
445       System.out.println(message);
446     }
447   }
448
449   private static void describeInspections(@NonNls String myOutputPath, final String name) throws IOException {
450     final InspectionToolWrapper[] toolWrappers = InspectionProfileImpl.getDefaultProfile().getInspectionTools(null);
451     final Map<String, Set<InspectionToolWrapper>> map = new HashMap<String, Set<InspectionToolWrapper>>();
452     for (InspectionToolWrapper toolWrapper : toolWrappers) {
453       final String groupName = toolWrapper.getGroupDisplayName();
454       Set<InspectionToolWrapper> groupInspections = map.get(groupName);
455       if (groupInspections == null) {
456         groupInspections = new HashSet<InspectionToolWrapper>();
457         map.put(groupName, groupInspections);
458       }
459       groupInspections.add(toolWrapper);
460     }
461
462     FileWriter fw = new FileWriter(myOutputPath);
463     try {
464       @NonNls final PrettyPrintWriter xmlWriter = new PrettyPrintWriter(fw);
465       xmlWriter.startNode(INSPECTIONS_NODE);
466       if (name != null) {
467         xmlWriter.addAttribute(PROFILE, name);
468       }
469       for (String groupName : map.keySet()) {
470         xmlWriter.startNode("group");
471         xmlWriter.addAttribute("name", groupName);
472         final Set<InspectionToolWrapper> entries = map.get(groupName);
473         for (InspectionToolWrapper toolWrapper : entries) {
474           xmlWriter.startNode("inspection");
475           xmlWriter.addAttribute("shortName", toolWrapper.getShortName());
476           xmlWriter.addAttribute("displayName", toolWrapper.getDisplayName());
477           final String description = toolWrapper.loadDescription();
478           if (description != null) {
479             xmlWriter.setValue(description);
480           }
481           else {
482             LOG.error(toolWrapper.getShortName() + " descriptionUrl==" + toolWrapper);
483           }
484           xmlWriter.endNode();
485         }
486         xmlWriter.endNode();
487       }
488       xmlWriter.endNode();
489     }
490     finally {
491       fw.close();
492     }
493   }
494 }