5
5
import org .jetbrains .annotations .NotNull ;
6
6
7
7
import java .util .*;
8
+ import java .util .stream .Collectors ;
8
9
9
10
/**
10
11
* A node representing a directory (package) in the coverage tree.
11
12
*/
12
13
@ Data
13
14
@ RequiredArgsConstructor
14
- class DirectoryNode implements FileSystemNode {
15
+ public class DirectoryNode implements FileSystemNode {
15
16
/**
16
17
* Name of the directory/package component
17
18
*/
@@ -29,235 +30,77 @@ class DirectoryNode implements FileSystemNode {
29
30
30
31
@ Override
31
32
public CoverageMetrics getMetrics () {
32
- return aggregateMetrics ();
33
- }
34
-
35
- /**
36
- * Aggregate metrics from this directory's files and subdirectories
37
- */
38
- CoverageMetrics aggregateMetrics () {
39
33
CoverageMetrics aggregated = new CoverageMetrics ();
40
34
sourceFiles .forEach (file -> aggregated .add (file .getMetrics ()));
41
- subdirectories .values ().forEach (subdir -> aggregated .add (subdir .aggregateMetrics ()));
35
+ subdirectories .values ().forEach (subdir -> aggregated .add (subdir .getMetrics ()));
42
36
return aggregated ;
43
37
}
44
38
45
- @ Override
46
- public boolean shouldInclude (boolean showFiles ) {
47
- // Check if this directory has any files (when showing files)
48
- if (showFiles && !sourceFiles .isEmpty ()) {
49
- return true ;
50
- }
51
-
52
- // Check if it has any subdirectories that should be included
53
- for (DirectoryNode subdir : subdirectories .values ()) {
54
- if (subdir .shouldInclude (showFiles )) {
55
- return true ;
56
- }
57
- }
58
-
59
- // Empty directory - skip it
60
- return false ;
39
+ public boolean shouldInclude () {
40
+ return !sourceFiles .isEmpty () || subdirectories .values ().stream ().anyMatch (DirectoryNode ::shouldInclude );
61
41
}
62
42
63
- @ Override
64
- public void printTree (org .apache .maven .plugin .logging .Log log , String prefix ,
65
- String format , String packagePath , boolean showFiles ) {
66
- // Skip empty directories
67
- if (!shouldInclude (showFiles )) {
68
- return ;
69
- }
43
+ private <T extends FileSystemNode > void printNodes (org .apache .maven .plugin .logging .Log log , String prefix ,
44
+ String format , String packagePath , boolean showFiles , @ NotNull List <T > nodes , boolean extraCheck ) {
45
+ for (int i = 0 ; i < nodes .size (); i ++) {
46
+ boolean isLast = (i == nodes .size () - 1 ) && extraCheck ;
47
+ FileSystemNode node = nodes .get (i );
70
48
71
- // Skip printing the empty root node
72
- boolean isRoot = name .isEmpty ();
73
- String currentPath = isRoot ? packagePath :
74
- (packagePath .isEmpty () ? name : packagePath + "." + name );
75
-
76
- if (!isRoot ) {
77
- log .info (String .format (format ,
78
- Defaults .truncateMiddle (prefix + name ),
79
- Defaults .formatCoverage (getMetrics ().getCoveredClasses (), getMetrics ().getTotalClasses ()),
80
- Defaults .formatCoverage (getMetrics ().getCoveredMethods (), getMetrics ().getTotalMethods ()),
81
- Defaults .formatCoverage (getMetrics ().getCoveredBranches (), getMetrics ().getTotalBranches ()),
82
- Defaults .formatCoverage (getMetrics ().getCoveredLines (), getMetrics ().getTotalLines ())));
49
+ node .printTree (log , determineNewPrefix (prefix , isLast ), format , packagePath , showFiles );
83
50
}
51
+ }
84
52
85
- // Collect directory nodes and file nodes separately
86
- List <DirectoryNode > dirNodes = new ArrayList <>();
87
- List <SourceFileNode > fileNodes = new ArrayList <>();
53
+ private @ NotNull String determineNewPrefix (@ NotNull String oldPrefix , boolean isLast ) {
54
+ String prefix = oldPrefix ;
88
55
89
- // Add subdirectories
90
- for (DirectoryNode subdir : subdirectories .values ()) {
91
- if (subdir .shouldInclude (showFiles )) {
92
- dirNodes .add (subdir );
93
- }
56
+ if (prefix .endsWith (Defaults .CORNER )) {
57
+ prefix = prefix .substring (0 , prefix .length () - Defaults .CORNER .length ()) + Defaults .LAST_DIR_SPACE ;
94
58
}
95
-
96
- // Add source files if needed
97
- if (showFiles ) {
98
- fileNodes .addAll (sourceFiles );
99
- }
100
-
101
- // Sort nodes
102
- Collections .sort (dirNodes );
103
- Collections .sort (fileNodes );
104
-
105
- // Determine if we need tree indicators at the first level
106
- boolean useTreeForRoot = dirNodes .size () > 1 || !fileNodes .isEmpty ();
107
-
108
- // Print directory nodes first
109
- for (int i = 0 ; i < dirNodes .size (); i ++) {
110
- boolean isLast = (i == dirNodes .size () - 1 ) && fileNodes .isEmpty ();
111
- DirectoryNode node = dirNodes .get (i );
112
-
113
- if (isRoot ) {
114
- // Handle collapsible directories at root level
115
- if (shouldCollapseDirectory (node , showFiles )) {
116
- String displayPrefix = useTreeForRoot ? (isLast ? Defaults .CORNER : Defaults .TEE ) : "" ;
117
- printCollapsedPath (log , node , displayPrefix , isLast , format , currentPath ,
118
- showFiles , useTreeForRoot );
119
- } else {
120
- // Normal node at root level
121
- String rootPrefix = useTreeForRoot ? (isLast ? Defaults .CORNER : Defaults .TEE ) : "" ;
122
- node .printTree (log , rootPrefix , format , currentPath , showFiles );
123
- }
124
- } else {
125
- // Non-root nodes
126
- String connector = isLast ? Defaults .CORNER : Defaults .TEE ;
127
-
128
- // Handle collapsible directories
129
- if (shouldCollapseDirectory (node , showFiles )) {
130
- printCollapsedPath (log , node , prefix , isLast , format , currentPath , showFiles , true );
131
- } else {
132
- // Print the node normally
133
- node .printTree (log , prefix + connector , format , currentPath , showFiles );
134
- }
135
- }
59
+ else if (prefix .endsWith (Defaults .TEE )) {
60
+ prefix = prefix .substring (0 , prefix .length () - Defaults .TEE .length ()) + Defaults .VERTICAL_LINE ;
136
61
}
137
62
138
- // Print source files after directories
139
- if (!fileNodes .isEmpty ()) {
140
- // Calculate the prefix for files
141
- String filePrefix = isRoot ? "" : prefix .replace (Defaults .TEE , Defaults .VERTICAL_LINE ).replace (Defaults .CORNER , Defaults .LASTDIR_SPACE );
142
-
143
- for (int i = 0 ; i < fileNodes .size (); i ++) {
144
- boolean isLast = (i == fileNodes .size () - 1 );
145
- SourceFileNode node = fileNodes .get (i );
146
-
147
- String connector = isLast ? Defaults .CORNER : Defaults .TEE ;
148
- if (isRoot && useTreeForRoot ) {
149
- node .printTree (log , connector , format , currentPath , showFiles );
150
- } else {
151
- node .printTree (log , filePrefix + connector , format , currentPath , showFiles );
152
- }
153
- }
154
- }
63
+ String connector = isLast ? Defaults .CORNER : Defaults .TEE ;
64
+ return prefix + connector ;
155
65
}
156
66
157
- /**
158
- * Determines if a directory should be collapsed with its children
159
- * (i.e., it has exactly one subdirectory and no files)
160
- */
161
- private boolean shouldCollapseDirectory (DirectoryNode dir , boolean showFiles ) {
162
- if (showFiles && !dir .getSourceFiles ().isEmpty ()) {
163
- return false ;
67
+ @ Override
68
+ public void printTree (org .apache .maven .plugin .logging .@ NotNull Log log , String prefix ,
69
+ String format , String packagePath , boolean showFiles ) {
70
+ // Skip empty directories
71
+ if (!shouldInclude ()) {
72
+ return ;
164
73
}
165
74
166
- if (dir .getSubdirectories ().size () != 1 ) {
167
- return false ;
168
- }
75
+ packagePath = packagePath .replaceAll ("^\\ ." , "" ); // ltrim('.')
169
76
170
- DirectoryNode subdir = dir .getSubdirectories ().values ().iterator ().next ();
171
- return subdir .shouldInclude (showFiles );
172
- }
173
-
174
- /**
175
- * Print a collapsed directory path (e.g., "com.example" instead of "com" -> "example")
176
- */
177
- private void printCollapsedPath (org .apache .maven .plugin .logging .Log log , @ NotNull DirectoryNode dir ,
178
- String prefix , boolean isLast , String format ,
179
- String packagePath , boolean showFiles , boolean useTreeIndicator ) {
180
- // Build the collapsed path string
181
- StringBuilder path = new StringBuilder (dir .getName ());
182
- DirectoryNode current = dir ;
77
+ // Skip printing the empty root node
78
+ final boolean isRoot = name .isEmpty ();
183
79
184
- // Follow the chain of single subdirectories
185
- while (shouldCollapseDirectory (current , showFiles )) {
186
- DirectoryNode subdir = current .getSubdirectories ().values ().iterator ().next ();
187
- path .append ("." ).append (subdir .getName ());
188
- current = subdir ;
189
- }
80
+ // Collect directory nodes and file nodes separately
81
+ List <DirectoryNode > dirNodes = subdirectories .values ().stream ().filter (DirectoryNode ::shouldInclude ).sorted ().collect (Collectors .toList ());
82
+ List <SourceFileNode > fileNodes = showFiles ? sourceFiles .stream ().sorted ().collect (Collectors .toList ()) : new ArrayList <>();
190
83
191
- // Display the collapsed path as a node
192
- CoverageMetrics metrics = dir .getMetrics ();
193
- String displayPath ;
194
- if (useTreeIndicator ) {
195
- displayPath = prefix + path .toString ();
196
- } else {
197
- displayPath = path .toString ();
84
+ boolean shouldCollapse = dirNodes .size () == 1 && fileNodes .isEmpty ();
85
+ if (shouldCollapse ) {
86
+ DirectoryNode onlyNode = dirNodes .get (0 );
87
+ onlyNode .printTree (log , prefix , format , packagePath + "." + getName (), showFiles );
88
+ return ;
198
89
}
199
90
91
+ String printableName = isRoot ? "<root>" : prefix + packagePath + (packagePath .isEmpty () ? "" : "." ) + name ;
200
92
log .info (String .format (format ,
201
- Defaults .truncateMiddle (displayPath ),
202
- Defaults .formatCoverage (metrics .getCoveredClasses (), metrics .getTotalClasses ()),
203
- Defaults .formatCoverage (metrics .getCoveredMethods (), metrics .getTotalMethods ()),
204
- Defaults .formatCoverage (metrics .getCoveredBranches (), metrics .getTotalBranches ()),
205
- Defaults .formatCoverage (metrics .getCoveredLines (), metrics .getTotalLines ())));
93
+ Defaults .truncateMiddle (printableName ),
94
+ Defaults .formatCoverage (getMetrics () .getCoveredClasses (), getMetrics () .getTotalClasses ()),
95
+ Defaults .formatCoverage (getMetrics () .getCoveredMethods (), getMetrics () .getTotalMethods ()),
96
+ Defaults .formatCoverage (getMetrics () .getCoveredBranches (), getMetrics () .getTotalBranches ()),
97
+ Defaults .formatCoverage (getMetrics () .getCoveredLines (), getMetrics () .getTotalLines ())));
206
98
207
- // Calculate the full package path for children
208
- String fullPath = packagePath .isEmpty () ? path .toString () :
209
- packagePath + "." + path .toString ();
99
+ packagePath = "" ; // Reset because we shouldn't collapse now anymore
210
100
211
- // Separate directories and files for consistent ordering
212
- List <DirectoryNode > childDirs = new ArrayList <>();
213
- List <SourceFileNode > childFiles = new ArrayList <>();
214
-
215
- // Get the contents of the last directory in the chain
216
- for (DirectoryNode subdir : current .getSubdirectories ().values ()) {
217
- if (subdir .shouldInclude (showFiles )) {
218
- childDirs .add (subdir );
219
- }
220
- }
221
-
222
- if (showFiles ) {
223
- childFiles .addAll (current .getSourceFiles ());
224
- }
225
-
226
- // Sort the child nodes
227
- Collections .sort (childDirs );
228
- Collections .sort (childFiles );
229
-
230
- // Calculate the base prefix for children
231
- String basePrefixForChildren ;
232
- if (useTreeIndicator ) {
233
- basePrefixForChildren = isLast ? Defaults .LASTDIR_SPACE : Defaults .VERTICAL_LINE ;
234
- } else {
235
- basePrefixForChildren = " " ; // No tree indicator at root level
236
- }
237
-
238
- // Print directories first
239
- for (int i = 0 ; i < childDirs .size (); i ++) {
240
- boolean isLastDir = (i == childDirs .size () - 1 ) && childFiles .isEmpty ();
241
- DirectoryNode node = childDirs .get (i );
242
- String childConnector = isLastDir ? Defaults .CORNER : Defaults .TEE ;
243
-
244
- if (shouldCollapseDirectory (node , showFiles )) {
245
- printCollapsedPath (log , node , basePrefixForChildren , isLastDir , format ,
246
- fullPath , showFiles , true );
247
- } else {
248
- node .printTree (log , basePrefixForChildren + childConnector , format ,
249
- fullPath , showFiles );
250
- }
251
- }
252
-
253
- // Print files after directories
254
- for (int i = 0 ; i < childFiles .size (); i ++) {
255
- boolean isLastFile = (i == childFiles .size () - 1 );
256
- SourceFileNode node = childFiles .get (i );
257
- String childConnector = isLastFile ? Defaults .CORNER : Defaults .TEE ;
258
-
259
- node .printTree (log , basePrefixForChildren + childConnector , format ,
260
- fullPath , showFiles );
261
- }
101
+ // Print directory nodes first
102
+ printNodes (log , prefix , format , packagePath , showFiles , dirNodes , fileNodes .isEmpty ());
103
+ // Then files
104
+ printNodes (log , prefix , format , packagePath , showFiles , fileNodes , true );
262
105
}
263
106
}
0 commit comments