root/trunk/src/java/org/jcoderz/phoenix/report/FindingsSummary.java

Revision 1454, 18.9 kB (checked in by amandel, 3 years ago)

Support for a 'global' finding that is not related to any source.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1/*
2 * $Id$
3 *
4 * Copyright 2006, The jCoderZ.org Project. All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are
8 * met:
9 *
10 *    * Redistributions of source code must retain the above copyright
11 *      notice, this list of conditions and the following disclaimer.
12 *    * Redistributions in binary form must reproduce the above
13 *      copyright notice, this list of conditions and the following
14 *      disclaimer in the documentation and/or other materials
15 *      provided with the distribution.
16 *    * Neither the name of the jCoderZ.org Project nor the names of
17 *      its contributors may be used to endorse or promote products
18 *      derived from this software without specific prior written
19 *      permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
24 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS
25 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
28 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
29 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
30 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
31 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 */
33package org.jcoderz.phoenix.report;
34
35import java.io.IOException;
36import java.io.Writer;
37import java.util.ArrayList;
38import java.util.Arrays;
39import java.util.Collection;
40import java.util.Collections;
41import java.util.HashMap;
42import java.util.Iterator;
43import java.util.List;
44import java.util.Map;
45
46import org.jcoderz.commons.util.XmlUtil;
47import org.jcoderz.phoenix.report.jaxb.Item;
48
49/**
50 * This class holds all findings, by type and file for the project.
51 *
52 * This class in NOT thread save in any way.
53 *
54 * @author Andreas Mandel
55 */
56final class FindingsSummary
57{
58   /** Singleton type findings collector. */
59   private static FindingsSummary sFindingsSummary = new FindingsSummary();
60
61   private final Map<String, FindingSummary> mFindings
62       = new HashMap<String, FindingSummary>();
63   private int mOverallCounter = 0;
64
65   private FindingsSummary ()
66   {
67      // singleton class only instantiated by the factory
68   }
69
70   /**
71    * Utility method to get the Singleton.
72    * @return the one and only findings summary object.
73    */
74   public static FindingsSummary getFindingsSummary ()
75   {
76      return sFindingsSummary;
77   }
78
79   /**
80    * Generates a key unique for kind of the given finding.
81    * @param finding item where to generate the key for.
82    * @return a key unique for kind of the given finding.
83    */
84    public static String getKeyForFinding (Item finding)
85    {
86        return finding.getFindingType() + "_"
87                  + finding.getSeverity().toString();
88    }
89
90    /**
91     * Generates a key unique for kind of the given finding type and
92     *          severity.
93     * @param findingType the type to generate the key for.
94     * @param severity the severity to generate the key for.
95     * @return a key unique for kind of the given finding type and
96     *          severity.
97     */
98     public static String getKeyForFinding (FindingType findingType,
99             Severity severity)
100     {
101         return findingType.getSymbol() + "_" + severity.toString();
102     }
103
104   /**
105    * Adds the finding to the findings data structure.
106    * All references and counters are updated.
107    * @param finding the concrete item that was detected
108    * @param file the FileSummary object of the detected finding.
109    */
110   public static void addFinding (Item finding, FileSummary file)
111   {
112      getFindingsSummary().getFindingSummary(finding)
113         .addFinding(finding, file);
114   }
115
116   /**
117    * Provides access to all findings of the given type.
118    * @param findingType the type of the finding.
119    * @param severity the severity of the finding.
120    * @return a FindingSummary of all findings of
121    *          the given FindingType, might be null if
122    *          no such finding exists.
123    */
124   public FindingSummary getFindingSummary (FindingType findingType,
125         Severity severity)
126   {
127       return mFindings.get(getKeyForFinding(findingType, severity));
128   }
129
130   /**
131    * Returns the FindingSummary appropriate to hold findings of the
132    * type of the given Item.
133    * If no such summary exists yet, a new one is generated and
134    * returned.
135    * @param item the item where to return a summary for.
136    * @return the FindingSummary appropriate to hold findings of the
137    *     type of the given Item.
138    */
139   public FindingSummary getFindingSummary (Item item)
140   {
141      final String key = getKeyForFinding(item);
142      // cast to make sure we get an exception once item.getFindingType
143      // returns a real FindingType
144      FindingSummary result = mFindings.get(key);
145      if (result == null)
146      {
147         result = new FindingSummary(item);
148      }
149      return result;
150   }
151
152   /**
153    * Returns the map mapping from the type/severity string to stored
154    * FindingSummary objects.
155    * The returned map is immutable. Stored objects MUST not be
156    * modified.
157    * @return the map mapping from the type/severity string to stored
158    * FindingSummary objects.
159    */
160   Map<String, FindingSummary> getFindings ()
161   {
162      return Collections.unmodifiableMap(mFindings);
163   }
164
165   /** {@inheritDoc} */
166   public String toString ()
167   {
168      return "[FindingsSummary: " + mFindings + "(" + mOverallCounter + ")]";
169   }
170
171    /**
172     * Generates a page that lists all findings, that links to the
173     * detailed finding pages. The content is ordered by severity and
174     * number of occurrences.
175     * @param out the writer where to write the html data to.
176     * @throws IOException if the data can not be written.
177     */
178    static void createOverallContent (Writer out) throws IOException
179    {
180        final Collection<FindingSummary> colAllFindings
181                = getFindingsSummary().getFindings().values();
182        final FindingSummary[] allFindings
183                = colAllFindings.toArray(
184                    new FindingSummary[colAllFindings.size()]);
185
186         Arrays.sort(allFindings);
187
188         Severity currentSeverity = null;
189
190         out.write("<table border='0' cellpadding='0' cellspacing='0' "
191                 + "width='95%' summary='Summary of all findings.'>");
192         int row = 0;
193         for (final FindingSummary summary : allFindings)
194         {
195            if (summary.getSeverity() != currentSeverity)
196            {
197               out.write("<tr><td colspan='3' class='severityheader'>");
198               currentSeverity = summary.getSeverity();
199               out.write("<a name='" + currentSeverity.toString() + "'/>");
200               out.write("Severity: ");
201               out.write(currentSeverity.toString());
202               out.write("\n</td></tr>");
203               row = 0;
204            }
205            row++;
206            out.write("<tr class='" + currentSeverity
207                  + Java2Html.toOddEvenString(row) + "'>");
208            out.write("<td class='finding-counter'>");
209            out.write(String.valueOf(summary.getCounter()));
210            out.write("</td>");
211            out.write("<td class='finding-origin'>");
212            out.write(summary.getOrigin().toString());
213            out.write("</td>");
214            out.write("<td class='finding-data' width='100%'>");
215
216            out.write("<a href='");
217            out.write(summary.createFindingDetailFilename());
218            out.write("' title='");
219            out.write(summary.getFindingType().getSymbol());
220            out.write("'>");
221   //         if (summary.isFindingsHaveSameMessage()
222   //               && summary.getFindingMessage() != null)
223   //         {
224   //            out.write(summary.getFindingMessage());
225   //         }
226   //         else
227            {
228               out.write(summary.getFindingType().getShortText());
229            }
230            out.write("</a></td></tr>\n");
231         }
232         out.write("</table>");
233      }
234
235
236
237   /**
238    * Holds all findings of a specific type.
239    * @author Andreas Mandel
240    */
241   final class FindingSummary implements Comparable<FindingSummary>
242   {
243      private final Map<String, FindingOccurrence> mOccurrences
244          = new HashMap<String, FindingOccurrence>();
245      private final Severity mSeverity;
246      private final Origin mOrigin;
247      private int mCounter;
248      private boolean mFindingsHaveSameMessage = true;
249      private final String mFindingMessage;
250      private final FindingType mFindingType;
251
252      /**
253       * Creates a new FindingSummary to collect findings similiar
254       * to the given finding.
255       * @param finding the reference Item for the types of findings
256       *     collected in this summary.
257       */
258      public FindingSummary (Item finding)
259      {
260         final String key = getKeyForFinding(finding);
261         mFindingType = FindingType.fromString(finding.getFindingType());
262         mSeverity = finding.getSeverity();
263         mOrigin = finding.getOrigin();
264         mFindingMessage = finding.getMessage();
265         mFindings.put(key, this);
266      }
267
268      /**
269       * @return Returns the counter.
270       */
271      public int getCounter ()
272      {
273         return mCounter;
274      }
275
276      /**
277       * @return Returns the origin of the findings.
278       */
279      public Origin getOrigin ()
280      {
281         return mOrigin;
282      }
283
284      /**
285       * @return Returns the severity.
286       */
287      public Severity getSeverity ()
288      {
289         return mSeverity;
290      }
291
292      /**
293       * @return Returns the findingMessage.
294       */
295      public String getFindingMessage ()
296      {
297         return mFindingMessage;
298      }
299      /**
300       * @return Returns the findingsHaveSameMessage.
301       */
302      public boolean isFindingsHaveSameMessage ()
303      {
304         return mFindingsHaveSameMessage;
305      }
306
307      /**
308       * @return Returns the finding type.
309       */
310      public FindingType getFindingType ()
311      {
312         return mFindingType;
313      }
314
315      /**
316       * @return Returns the occurrences.
317       */
318      public Map<String, FindingOccurrence> getOccurrences ()
319      {
320         return Collections.unmodifiableMap(mOccurrences);
321      }
322
323      public FindingOccurrence getOccurrence (FileSummary fileSummary)
324      {
325         FindingOccurrence result =
326            getOccurrence(fileSummary.getFullClassName());
327
328         if (result == null)
329         {
330            result = new FindingOccurrence(fileSummary);
331         }
332
333         return result;
334      }
335
336      public void addFinding (Item finding, FileSummary summary)
337      {
338         if (mFindingsHaveSameMessage)
339         {
340            if (mFindingMessage == null)
341            {
342               mFindingsHaveSameMessage
343                  = (finding.getMessage() == null);
344            }
345            else
346            {
347               mFindingsHaveSameMessage
348                  = mFindingMessage.equals(finding.getMessage());
349            }
350         }
351         getOccurrence(summary).addFinding(finding);
352      }
353
354      /** {@inheritDoc} */
355      public String toString ()
356      {
357         return "[" + mFindingType + "(" + mSeverity
358               + (mFindingsHaveSameMessage ? " " + mFindingMessage : "")
359               + "): " + mOccurrences + "(" + mCounter + ")]";
360      }
361
362
363      /**
364       * {@inheritDoc}
365       * Be aware that the order (result of {@link #compareTo} can change
366       * if new findings are added.
367       * The order is from severe with most findings to info with
368       * fewer findings.
369       */
370      public int compareTo (FindingSummary other)
371      {
372         int result = -mSeverity.compareTo(other.mSeverity);
373         if (result == 0)
374         {
375            result = other.mCounter - mCounter;
376         }
377         return result;
378      }
379
380      private void addOccurrence (FindingOccurrence occurrence)
381      {
382         mOccurrences.put(occurrence.getFullClassName(), occurrence);
383      }
384
385      private FindingOccurrence getOccurrence (String filename)
386      {
387         return mOccurrences.get(filename);
388      }
389
390      public String createFindingDetailFilename ()
391      {
392         return "finding-" + getSeverity() + "-"
393            + getFindingType().getSymbol() + ".html";
394      }
395
396      public void createFindingTypeContent (Writer out)
397         throws IOException
398      {
399          // TODO: Handle global findings more nice
400         final FindingOccurrence[] allFindings
401                 = mOccurrences.values().toArray(new FindingOccurrence[0]);
402
403         Arrays.sort(allFindings);
404
405         out.write("<h1><a href='index.html'>View by Classes</a></h1>");
406         out.write("<h1><a href='findings.html'>Findings - Overview</a></h1>");
407
408         out.write("<h1 title='");
409         out.write(getFindingType().getSymbol());
410         out.write("'>");
411
412         out.write(getSeverity().toString());
413         out.write(" ");
414         out.write(getFindingType().getShortText());
415         out.write(" (");
416         out.write(getOrigin().toString());
417         out.write(")");
418         out.write("</h1>\n");
419
420         if (isFindingsHaveSameMessage()
421               && getFindingMessage() != null)
422         {
423            out.write("<h2>");
424            out.write(XmlUtil.escape(getFindingMessage()));
425            out.write("</h2>\n");
426         }
427
428         if (getWikiPrefix() != null)
429         {
430            out.write("<a href='" + getWikiPrefix()
431                  + getFindingType().getSymbol()
432                  + "'>Further info on the wiki.</a>\n");
433         }
434
435         out.write("<blockquote>\n");
436         out.write(getFindingType().getDescription());
437         out.write("</blockquote>\n");
438
439
440         out.write("<table border='0' cellpadding='0' cellspacing='0' "
441                 + "width='95%' summary='Places of this finding.'>");
442
443         for (final FindingSummary.FindingOccurrence
444             occurrence : allFindings)
445         {
446            out.write("<tr><td class='findingtype-counter'>");
447            out.write(Integer.toString(occurrence.getFindings().size()));
448            out.write("</td><td class='findingtype-class' width='100%'>");
449//            out.write("<a href='");
450//            out.write(occurrence.getHtmlLink());
451//            out.write("'>");
452            out.write(occurrence.getFullClassName());
453            out.write("</td></tr>");
454
455            out.write("<tr><td class='findingtype-data' colspan='2'>");
456
457            final Iterator<Item> i = occurrence.getFindings().iterator();
458            while (i.hasNext())
459            {
460               final Item item = i.next();
461               final String htmlLink = occurrence.getHtmlLink();
462               if (htmlLink != null)
463               {
464                   out.write("<a href='");
465                   out.write(occurrence.getHtmlLink());
466                   out.write("#LINE");
467                   out.write(Integer.toString(item.getLine()));
468                   out.write("'>");
469               }
470               if (!isFindingsHaveSameMessage() && item.getMessage() != null)
471               {
472                  out.write(XmlUtil.escape(item.getMessage()));
473               }
474               out.write("&#160;[");
475               out.write(Integer.toString(item.getLine()));
476               if (item.getColumn() != 0)
477               {
478                  out.write(":");
479                  out.write(Integer.toString(item.getColumn()));
480               }
481               out.write("]");
482               if (htmlLink != null)
483               {
484                   out.write("</a>");
485               }
486               if (i.hasNext())
487               {
488                  out.write(", ");
489               }
490               if (!isFindingsHaveSameMessage() && item.getMessage() != null)
491               {
492                  out.write("<br />");
493               }
494            }
495            out.write("</td></tr>\n");
496         }
497         out.write("</table>\n");
498      }
499
500      /**
501       * Checks for the wiki prefix to be used.
502       * @return the wiki prefix to be used.
503       */
504      private String getWikiPrefix ()
505      {
506         return System.getProperty(Java2Html.WIKI_BASE_PROPERTY);
507      }
508
509
510      /**
511       * A occurrence of a finding.
512       * This class encapsulates all findings of a single type in one file.
513       * Be aware that the order (result of {@link #compareTo} can change
514       * if new findings are added.
515       *
516       * @author Andreas Mandel
517       */
518      final class FindingOccurrence implements Comparable<FindingOccurrence>
519      {
520         private final FileSummary mFileSummary;
521         private final List<Item> mFindingsInFile = new ArrayList<Item>();
522
523         private FindingOccurrence (FileSummary summary)
524         {
525            mFileSummary = summary;
526            addOccurrence(this);
527         }
528
529         /**
530          * @return the name of the package of this class/file
531          */
532         public String getPackagename ()
533         {
534            return mFileSummary.getPackage();
535         }
536
537         public void addFinding (Item finding)
538         {
539            mFindingsInFile.add(finding);
540            mCounter++;
541            mOverallCounter++;
542         }
543
544         public List<Item> getFindings ()
545         {
546            return Collections.unmodifiableList(mFindingsInFile);
547         }
548
549         /**
550          * @return ClassName including package.
551          */
552         public String getFullClassName ()
553         {
554            return mFileSummary.getFullClassName();
555         }
556
557         public String getClassName ()
558         {
559            return mFileSummary.getClassName();
560         }
561
562         public String getHtmlLink ()
563         {
564            return mFileSummary.getHtmlLink();
565         }
566
567         public int countFindingsInFile ()
568         {
569            return mFindingsInFile.size();
570         }
571
572         /** {@inheritDoc} */
573         public String toString ()
574         {
575            return "[" + getClassName() + ": " + findingsToString()
576                  + "(" + mFindingsInFile.size() + ")]";
577         }
578
579         public String findingsToString ()
580         {
581            final StringBuilder sb = new StringBuilder();
582            sb.append('{');
583            final Iterator<Item> i = mFindingsInFile.iterator();
584            while (i.hasNext())
585            {
586               final Item finding = i.next();
587               sb.append('@');
588               sb.append(finding.getLine());
589               sb.append(':');
590               sb.append(finding.getColumn());
591               if (i.hasNext())
592               {
593                  sb.append(", ");
594               }
595            }
596            sb.append('}');
597            return sb.toString();
598         }
599
600         /**
601          * {@inheritDoc}
602          * Be aware that the order (result of {@link #compareTo} can change
603          * if new findings are added.
604          * The order is from most findings to fewer findings.
605          */
606         public int compareTo (FindingOccurrence o)
607         {
608            return o.mFindingsInFile.size() - this.mFindingsInFile.size();
609         }
610      }
611   }
612}
Note: See TracBrowser for help on using the browser.