View Javadoc
1   /*
2    * MIT License
3    * Copyright (c) 2006-2025 JMockit developers
4    * See LICENSE file for full license text.
5    */
6   package mockit.coverage.reporting;
7   
8   import edu.umd.cs.findbugs.annotations.NonNull;
9   import edu.umd.cs.findbugs.annotations.Nullable;
10  
11  import java.io.File;
12  import java.io.IOException;
13  import java.nio.file.Path;
14  import java.util.ArrayList;
15  import java.util.Collection;
16  import java.util.HashMap;
17  import java.util.List;
18  import java.util.Map;
19  import java.util.Map.Entry;
20  import java.util.Set;
21  
22  import mockit.coverage.Configuration;
23  import mockit.coverage.data.CoverageData;
24  import mockit.coverage.data.FileCoverageData;
25  import mockit.coverage.reporting.packages.IndexPage;
26  import mockit.coverage.reporting.sourceFiles.FileCoverageReport;
27  import mockit.coverage.reporting.sourceFiles.InputFile;
28  
29  import org.slf4j.Logger;
30  import org.slf4j.LoggerFactory;
31  
32  public final class CoverageReport {
33  
34      /** The logger. */
35      private static final Logger logger = LoggerFactory.getLogger(CoverageReport.class);
36  
37      @NonNull
38      private final String outputDir;
39      private boolean outputDirCreated;
40      @Nullable
41      private final List<File> sourceDirs;
42      @NonNull
43      private final Map<String, FileCoverageData> fileToFileData;
44      @NonNull
45      private final Map<String, List<String>> packageToFiles;
46      private final boolean withCallPoints;
47      @Nullable
48      private final Collection<String> sourceFilesNotFound;
49  
50      public CoverageReport(@NonNull String outputDir, boolean outputDirCreated, @Nullable String[] srcDirs,
51              @NonNull CoverageData coverageData, boolean withCallPoints) {
52          this.outputDir = Configuration.getOrChooseOutputDirectory(outputDir, "coverage-report");
53          this.outputDirCreated = outputDirCreated;
54          sourceDirs = srcDirs == null ? null : new SourceFiles().buildListOfSourceDirectories(srcDirs);
55          fileToFileData = coverageData.getFileToFileData();
56          packageToFiles = new HashMap<>();
57          this.withCallPoints = withCallPoints;
58          sourceFilesNotFound = srcDirs == null ? null : new ArrayList<>();
59      }
60  
61      public void generate() throws IOException {
62          createReportOutputDirIfNotExists();
63  
64          File outputFile = createOutputFileForIndexPage();
65  
66          if (outputFile == null) {
67              return;
68          }
69  
70          boolean withSourceFilePages = sourceDirs != null;
71  
72          if (withSourceFilePages && sourceDirs.size() > 1) {
73              logger.info("JMockit: Coverage source dirs: {}", sourceDirs);
74          }
75  
76          generateFileCoverageReportsWhileBuildingPackageLists();
77  
78          new StaticFiles(outputDir).copyToOutputDir(withSourceFilePages);
79          new IndexPage(outputFile, sourceDirs, sourceFilesNotFound, packageToFiles, fileToFileData).generate();
80  
81          logger.info("JMockit: Coverage report written to {}", outputFile.getParentFile().getCanonicalPath());
82      }
83  
84      private void createReportOutputDirIfNotExists() {
85          if (!outputDirCreated) {
86              File outDir = Path.of(outputDir).toFile();
87              outputDirCreated = outDir.mkdirs();
88          }
89      }
90  
91      @Nullable
92      private File createOutputFileForIndexPage() throws IOException {
93          File outputFile = Path.of(outputDir, "index.html").toFile();
94  
95          if (outputFile.exists() && !outputFile.canWrite()) {
96              logger.info("JMockit: {} is read-only; report generation canceled", outputFile.getCanonicalPath());
97              return null;
98          }
99  
100         return outputFile;
101     }
102 
103     private void generateFileCoverageReportsWhileBuildingPackageLists() throws IOException {
104         Set<Entry<String, FileCoverageData>> files = fileToFileData.entrySet();
105 
106         for (Entry<String, FileCoverageData> fileAndFileData : files) {
107             generateFileCoverageReport(fileAndFileData.getKey(), fileAndFileData.getValue());
108         }
109     }
110 
111     private void generateFileCoverageReport(@NonNull String sourceFile, @NonNull FileCoverageData fileData)
112             throws IOException {
113         if (sourceDirs != null) {
114             InputFile inputFile = InputFile.createIfFileExists(sourceDirs, sourceFile);
115 
116             if (inputFile != null) {
117                 new FileCoverageReport(outputDir, inputFile, fileData, withCallPoints).generate();
118             } else {
119                 deleteOutdatedHTMLFileIfExists(sourceFile);
120 
121                 if (sourceFilesNotFound != null) {
122                     sourceFilesNotFound.add(sourceFile);
123                 }
124             }
125         }
126 
127         addFileToPackageFileList(sourceFile);
128     }
129 
130     private void addFileToPackageFileList(@NonNull String file) {
131         int p = file.lastIndexOf('/');
132         String filePackage = p < 0 ? "" : file.substring(0, p);
133         List<String> filesInPackage = packageToFiles.computeIfAbsent(filePackage, k -> new ArrayList<>());
134         filesInPackage.add(file.substring(p + 1));
135     }
136 
137     private void deleteOutdatedHTMLFileIfExists(@NonNull String filePath) {
138         if (!outputDirCreated) {
139             File outputFile = OutputFile.getOutputFile(outputDir, filePath);
140             // noinspection ResultOfMethodCallIgnored
141             outputFile.delete();
142         }
143     }
144 }