1 package org.apache.maven.report.projectinfo;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import org.apache.maven.doxia.sink.Sink;
23 import org.apache.maven.doxia.sink.SinkEventAttributeSet;
24 import org.apache.maven.doxia.sink.SinkEventAttributes;
25 import org.apache.maven.model.Dependency;
26 import org.apache.maven.plugins.annotations.Mojo;
27 import org.apache.maven.plugins.annotations.Parameter;
28 import org.apache.maven.project.MavenProject;
29 import org.apache.maven.reporting.MavenReportException;
30 import org.codehaus.plexus.util.StringUtils;
31
32 import java.util.ArrayList;
33 import java.util.Collections;
34 import java.util.Comparator;
35 import java.util.Iterator;
36 import java.util.List;
37 import java.util.Locale;
38 import java.util.Map;
39 import java.util.TreeMap;
40
41
42
43
44
45
46
47
48
49 @Mojo( name = "dependency-convergence", aggregator = true )
50 public class DependencyConvergenceReport
51 extends AbstractProjectInfoReport
52 {
53 private static final int PERCENTAGE = 100;
54
55
56
57
58
59
60
61
62
63 @Parameter( property = "reactorProjects", required = true, readonly = true )
64 private List<MavenProject> reactorProjects;
65
66
67
68
69
70
71 public String getOutputName()
72 {
73 return "dependency-convergence";
74 }
75
76 @Override
77 protected String getI18Nsection()
78 {
79 return "dependency-convergence";
80 }
81
82 @Override
83 public boolean canGenerateReport()
84 {
85
86 return reactorProjects.size() > 1;
87 }
88
89
90
91
92
93 @Override
94 protected void executeReport( Locale locale )
95 throws MavenReportException
96 {
97 Sink sink = getSink();
98
99 sink.head();
100 sink.title();
101 sink.text( getI18nString( locale, "title" ) );
102 sink.title_();
103 sink.head_();
104
105 sink.body();
106
107 sink.section1();
108
109 sink.sectionTitle1();
110 sink.text( getI18nString( locale, "title" ) );
111 sink.sectionTitle1_();
112
113 Map<String, List<ReverseDependencyLink>> dependencyMap = getDependencyMap();
114
115
116 generateLegend( locale, sink );
117
118 sink.lineBreak();
119
120
121 generateStats( locale, sink, dependencyMap );
122
123 sink.section1_();
124
125
126 generateConvergence( locale, sink, dependencyMap );
127
128 sink.body_();
129 sink.flush();
130 sink.close();
131 }
132
133
134
135
136
137
138
139
140
141
142
143
144 private void generateConvergence( Locale locale, Sink sink, Map<String, List<ReverseDependencyLink>> dependencyMap )
145 {
146 sink.section2();
147
148 sink.sectionTitle2();
149 sink.text( getI18nString( locale, "convergence.caption" ) );
150 sink.sectionTitle2_();
151
152 for ( Map.Entry<String, List<ReverseDependencyLink>> entry : dependencyMap.entrySet() )
153 {
154 String key = entry.getKey();
155 List<ReverseDependencyLink> depList = entry.getValue();
156
157 sink.section3();
158 sink.sectionTitle3();
159 sink.text( key );
160 sink.sectionTitle3_();
161
162 generateDependencyDetails( sink, depList );
163
164 sink.section3_();
165 }
166
167 sink.section2_();
168 }
169
170
171
172
173
174
175
176 private void generateDependencyDetails( Sink sink, List<ReverseDependencyLink> depList )
177 {
178 sink.table();
179
180 Map<String, List<ReverseDependencyLink>> artifactMap = getSortedUniqueArtifactMap( depList );
181
182 sink.tableRow();
183
184 sink.tableCell( );
185 if ( artifactMap.size() > 1 )
186 {
187 iconError( sink );
188 }
189 else
190 {
191 iconSuccess( sink );
192 }
193 sink.tableCell_();
194
195 sink.tableCell();
196
197 sink.table();
198
199 for ( String version : artifactMap.keySet() )
200 {
201 sink.tableRow();
202 sink.tableCell( new SinkEventAttributeSet( new String[] {SinkEventAttributes.WIDTH, "25%"} ) );
203 sink.text( version );
204 sink.tableCell_();
205
206 sink.tableCell();
207 generateVersionDetails( sink, artifactMap, version );
208 sink.tableCell_();
209
210 sink.tableRow_();
211 }
212 sink.table_();
213 sink.tableCell_();
214
215 sink.tableRow_();
216
217 sink.table_();
218 }
219
220 private void generateVersionDetails( Sink sink, Map<String, List<ReverseDependencyLink>> artifactMap,
221 String version )
222 {
223 sink.numberedList( 1 );
224 List<ReverseDependencyLink> depList = artifactMap.get( version );
225 Collections.sort( depList, new ReverseDependencyLinkComparator() );
226
227 for ( ReverseDependencyLink rdl : depList )
228 {
229 sink.numberedListItem();
230 if ( StringUtils.isNotEmpty( rdl.project.getUrl() ) )
231 {
232 sink.link( rdl.project.getUrl() );
233 }
234 sink.text( rdl.project.getGroupId() + ":" + rdl.project.getArtifactId() );
235 if ( StringUtils.isNotEmpty( rdl.project.getUrl() ) )
236 {
237 sink.link_();
238 }
239 sink.numberedListItem_();
240 }
241 sink.numberedList_();
242 }
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263 private Map<String, List<ReverseDependencyLink>> getSortedUniqueArtifactMap( List<ReverseDependencyLink> depList )
264 {
265 Map<String, List<ReverseDependencyLink>> uniqueArtifactMap = new TreeMap<String, List<ReverseDependencyLink>>();
266
267 for ( ReverseDependencyLink rdl : depList )
268 {
269 String key = rdl.getDependency().getVersion();
270 List<ReverseDependencyLink> projectList = uniqueArtifactMap.get( key );
271 if ( projectList == null )
272 {
273 projectList = new ArrayList<ReverseDependencyLink>();
274 }
275 projectList.add( rdl );
276 uniqueArtifactMap.put( key, projectList );
277 }
278
279 return uniqueArtifactMap;
280 }
281
282
283
284
285
286
287
288 private void generateLegend( Locale locale, Sink sink )
289 {
290 sink.table();
291 sink.tableCaption();
292 sink.bold();
293 sink.text( getI18nString( locale, "legend" ) );
294 sink.bold_();
295 sink.tableCaption_();
296
297 sink.tableRow();
298
299 sink.tableCell( );
300 iconSuccess( sink );
301 sink.tableCell_();
302 sink.tableCell();
303 sink.text( getI18nString( locale, "legend.shared" ) );
304 sink.tableCell_();
305
306 sink.tableRow_();
307
308 sink.tableRow();
309
310 sink.tableCell( );
311 iconError( sink );
312 sink.tableCell_();
313 sink.tableCell();
314 sink.text( getI18nString( locale, "legend.different" ) );
315 sink.tableCell_();
316
317 sink.tableRow_();
318
319 sink.table_();
320 }
321
322
323
324
325
326
327
328
329 private void generateStats( Locale locale, Sink sink, Map<String, List<ReverseDependencyLink>> dependencyMap )
330 {
331 int depCount = dependencyMap.size();
332 int artifactCount = 0;
333 int snapshotCount = 0;
334
335 for ( List<ReverseDependencyLink> depList : dependencyMap.values() )
336 {
337 Map<String, List<ReverseDependencyLink>> artifactMap = getSortedUniqueArtifactMap( depList );
338 snapshotCount += countSnapshots( artifactMap );
339 artifactCount += artifactMap.size();
340 }
341
342 int convergence = (int) ( ( (double) depCount / (double) artifactCount ) * PERCENTAGE );
343
344
345 sink.table();
346 sink.tableCaption();
347 sink.bold();
348 sink.text( getI18nString( locale, "stats.caption" ) );
349 sink.bold_();
350 sink.tableCaption_();
351
352 sink.tableRow();
353 sink.tableHeaderCell( );
354 sink.text( getI18nString( locale, "stats.subprojects" ) );
355 sink.tableHeaderCell_();
356 sink.tableCell();
357 sink.text( String.valueOf( reactorProjects.size() ) );
358 sink.tableCell_();
359 sink.tableRow_();
360
361 sink.tableRow();
362 sink.tableHeaderCell( );
363 sink.text( getI18nString( locale, "stats.dependencies" ) );
364 sink.tableHeaderCell_();
365 sink.tableCell();
366 sink.text( String.valueOf( depCount ) );
367 sink.tableCell_();
368 sink.tableRow_();
369
370 sink.tableRow();
371 sink.tableHeaderCell( );
372 sink.text( getI18nString( locale, "stats.artifacts" ) );
373 sink.tableHeaderCell_();
374 sink.tableCell();
375 sink.text( String.valueOf( artifactCount ) );
376 sink.tableCell_();
377 sink.tableRow_();
378
379 sink.tableRow();
380 sink.tableHeaderCell( );
381 sink.text( getI18nString( locale, "stats.snapshots" ) );
382 sink.tableHeaderCell_();
383 sink.tableCell();
384 sink.text( String.valueOf( snapshotCount ) );
385 sink.tableCell_();
386 sink.tableRow_();
387
388 sink.tableRow();
389 sink.tableHeaderCell( );
390 sink.text( getI18nString( locale, "stats.convergence" ) );
391 sink.tableHeaderCell_();
392 sink.tableCell();
393 if ( convergence < PERCENTAGE )
394 {
395 iconError( sink );
396 }
397 else
398 {
399 iconSuccess( sink );
400 }
401 sink.nonBreakingSpace();
402 sink.bold();
403 sink.text( String.valueOf( convergence ) + "%" );
404 sink.bold_();
405 sink.tableCell_();
406 sink.tableRow_();
407
408 sink.tableRow();
409 sink.tableHeaderCell( );
410 sink.text( getI18nString( locale, "stats.readyrelease" ) );
411 sink.tableHeaderCell_();
412 sink.tableCell();
413 if ( convergence >= PERCENTAGE && snapshotCount <= 0 )
414 {
415 iconSuccess( sink );
416 sink.nonBreakingSpace();
417 sink.bold();
418 sink.text( getI18nString( locale, "stats.readyrelease.success" ) );
419 sink.bold_();
420 }
421 else
422 {
423 iconError( sink );
424 sink.nonBreakingSpace();
425 sink.bold();
426 sink.text( getI18nString( locale, "stats.readyrelease.error" ) );
427 sink.bold_();
428 if ( convergence < PERCENTAGE )
429 {
430 sink.lineBreak();
431 sink.text( getI18nString( locale, "stats.readyrelease.error.convergence" ) );
432 }
433 if ( snapshotCount > 0 )
434 {
435 sink.lineBreak();
436 sink.text( getI18nString( locale, "stats.readyrelease.error.snapshots" ) );
437 }
438 }
439 sink.tableCell_();
440 sink.tableRow_();
441
442 sink.table_();
443 }
444
445 private int countSnapshots( Map<String, List<ReverseDependencyLink>> artifactMap )
446 {
447 int count = 0;
448 for ( Map.Entry<String, List<ReverseDependencyLink>> entry : artifactMap.entrySet() )
449 {
450 String version = entry.getKey();
451 boolean isReactorProject = false;
452
453 Iterator<ReverseDependencyLink> iterator = entry.getValue().iterator();
454
455
456
457 if ( iterator.hasNext() )
458 {
459 ReverseDependencyLink rdl = iterator.next();
460 if ( isReactorProject( rdl.getDependency() ) )
461 {
462 isReactorProject = true;
463 }
464 }
465
466 if ( version.endsWith( "-SNAPSHOT" ) && !isReactorProject )
467 {
468 count++;
469 }
470 }
471 return count;
472 }
473
474
475
476
477
478
479
480 private boolean isReactorProject( Dependency dependency )
481 {
482 for ( MavenProject mavenProject : reactorProjects )
483 {
484 if ( mavenProject.getGroupId().equals( dependency.getGroupId() )
485 && mavenProject.getArtifactId().equals( dependency.getArtifactId() ) )
486 {
487 if ( getLog().isDebugEnabled() )
488 {
489 getLog().debug( dependency + " is a reactor project" );
490 }
491 return true;
492 }
493 }
494 return false;
495 }
496
497 private void iconSuccess( Sink sink )
498 {
499 sink.figure();
500 sink.figureCaption();
501 sink.text( "success" );
502 sink.figureCaption_();
503 sink.figureGraphics( "images/icon_success_sml.gif" );
504 sink.figure_();
505 }
506
507 private void iconError( Sink sink )
508 {
509 sink.figure();
510 sink.figureCaption();
511 sink.text( "error" );
512 sink.figureCaption_();
513 sink.figureGraphics( "images/icon_error_sml.gif" );
514 sink.figure_();
515 }
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536 private Map<String, List<ReverseDependencyLink>> getDependencyMap()
537 {
538 Map<String, List<ReverseDependencyLink>> dependencyMap = new TreeMap<String, List<ReverseDependencyLink>>();
539
540 for ( MavenProject reactorProject : reactorProjects )
541 {
542 @SuppressWarnings( "unchecked" )
543 Iterator<Dependency> itdep = reactorProject.getDependencies().iterator();
544 while ( itdep.hasNext() )
545 {
546 Dependency dep = itdep.next();
547 String key = dep.getGroupId() + ":" + dep.getArtifactId();
548 List<ReverseDependencyLink> depList = dependencyMap.get( key );
549 if ( depList == null )
550 {
551 depList = new ArrayList<ReverseDependencyLink>();
552 }
553 depList.add( new ReverseDependencyLink( dep, reactorProject ) );
554 dependencyMap.put( key, depList );
555 }
556 }
557
558 return dependencyMap;
559 }
560
561
562
563
564 private static class ReverseDependencyLink
565 {
566 private Dependency dependency;
567
568 protected MavenProject project;
569
570 ReverseDependencyLink( Dependency dependency, MavenProject project )
571 {
572 this.dependency = dependency;
573 this.project = project;
574 }
575
576 public Dependency getDependency()
577 {
578 return dependency;
579 }
580
581 public MavenProject getProject()
582 {
583 return project;
584 }
585
586 @Override
587 public String toString()
588 {
589 return project.getId();
590 }
591 }
592
593
594
595
596 static class ReverseDependencyLinkComparator
597 implements Comparator<ReverseDependencyLink>
598 {
599
600 public int compare( ReverseDependencyLink p1, ReverseDependencyLink p2 )
601 {
602 return p1.getProject().getId().compareTo( p2.getProject().getId() );
603 }
604 }
605 }