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