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

Revision 1533, 18.2 kB (checked in by amandel, 3 years ago)

Fix: Last commit was broken.

  • 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.File;
36import java.io.FileNotFoundException;
37import java.io.FileOutputStream;
38import java.io.IOException;
39import java.util.ArrayList;
40import java.util.Iterator;
41import java.util.List;
42import java.util.logging.Level;
43import java.util.logging.Logger;
44
45import javax.xml.bind.JAXBContext;
46import javax.xml.bind.JAXBException;
47import javax.xml.bind.Marshaller;
48import javax.xml.bind.PropertyException;
49import javax.xml.transform.Transformer;
50import javax.xml.transform.TransformerException;
51import javax.xml.transform.TransformerFactory;
52import javax.xml.transform.stream.StreamResult;
53import javax.xml.transform.stream.StreamSource;
54
55import org.jcoderz.commons.ArgumentMalformedException;
56import org.jcoderz.commons.types.Date;
57import org.jcoderz.commons.util.Assert;
58import org.jcoderz.commons.util.FileUtils;
59import org.jcoderz.commons.util.IoUtil;
60import org.jcoderz.commons.util.LoggingUtils;
61import org.jcoderz.commons.util.ObjectUtil;
62import org.jcoderz.commons.util.StringUtil;
63import org.jcoderz.phoenix.report.jaxb.Item;
64import org.jcoderz.phoenix.report.jaxb.ObjectFactory;
65import org.jcoderz.phoenix.report.jaxb.Report;
66
67/**
68 * Provides merging and filtering of various jcoderz-report.xml files.
69 * It combines parts of the functions from ReportNormalizer and XmlMergeAntTask.
70 *
71 * @author Michael Rumpf
72 */
73public class ReportMerger
74{
75
76   /** The Constant CLASSNAME. */
77   private static final String CLASSNAME = ReportNormalizer.class.getName();
78
79   /** The Constant logger. */
80   private static final Logger logger = Logger.getLogger(CLASSNAME);
81
82   /** The length of an unique part of a c&p finding message. */
83   private static final int CPD_UNIQUE_STRING_LENGTH
84       = "Copied and pasted code. 341 equal".length();
85   
86   /** The log level. */
87   private Level mLogLevel;
88
89   /** The out file. */
90   private File mOutFile = null;
91
92   /** The reports. */
93   private final List<File> mReports = new ArrayList<File>();
94
95   /** The filters. */
96   private final List<File> mFilters = new ArrayList<File>();
97
98   /** The old Report. */
99   private File mOldReport;
100
101   /** The old Report. */
102   private final Date mReportDate = Date.now();
103
104   /**
105    * Merge input reports.
106    * @throws JAXBException if a xml handling error occurs.
107    * @throws FileNotFoundException in case of an IO issue.
108    */
109   public void merge ()
110       throws JAXBException, FileNotFoundException
111   {
112     logger.log(Level.FINE, "Merging jcoderz-report.xml files...");
113     // merge the reports
114     final Report mergedReport = new ObjectFactory().createReport();
115     for (final File reportFile : mReports)
116     {
117        logger.log(Level.FINE, "Report: " + reportFile);
118        try
119        {
120           final Report report = (Report) new ObjectFactory()
121                 .createUnmarshaller().unmarshal(reportFile);
122           mergedReport.getFile().addAll(report.getFile());
123        }
124        catch (JAXBException ex)
125        {
126           // TODO: ADD ISSUE AS system ITEM TO THE REPORT
127           ex.printStackTrace();
128        }
129     }
130     writeResult(mergedReport, mOutFile);
131   }
132
133
134   /**
135    * Filters the report XML file using the JDK XSL processor.
136    * @throws TransformerException if the transformation fails.
137    * @throws IOException if an io operation fails.
138    */
139   public void filter () throws TransformerException, IOException
140   {
141       logger.log(Level.FINE, "Filtering jcoderz-report.xml files...");
142       for (final File filterFile : mFilters)
143       {
144           logger.log(Level.FINE, "Filter: " + filterFile);
145           final TransformerFactory tFactory
146               = TransformerFactory.newInstance();
147
148           final Transformer transformer
149               = tFactory.newTransformer(new StreamSource(filterFile));
150
151           final File tempOutputFile
152               = new File(mOutFile.getCanonicalPath() + ".tmp");
153           FileUtils.createNewFile(tempOutputFile);
154
155           final FileOutputStream out = new FileOutputStream(tempOutputFile);
156           transformer.transform(new StreamSource(mOutFile),
157               new StreamResult(out));
158           IoUtil.close(out);
159           FileUtils.copyFile(tempOutputFile, mOutFile);
160           FileUtils.delete(tempOutputFile);
161       }
162   }
163
164   /**
165    * Searches for new findings based on the old jcReport and increases the
166    * severity of such findings to NEW.
167    */
168   public void flagNewFindings () 
169   {
170       logger.log(Level.FINE, "Searching for NEW findings...");
171       try
172       {
173           final Report currentReport
174               = (Report) new ObjectFactory().createUnmarshaller().unmarshal(
175                   mOutFile);
176           final Report oldReport
177               = (Report) new ObjectFactory().createUnmarshaller().unmarshal(
178                   mOldReport);
179           for (org.jcoderz.phoenix.report.jaxb.File newFile
180               : (List<org.jcoderz.phoenix.report.jaxb.File>) 
181                   currentReport.getFile())
182           {
183               final org.jcoderz.phoenix.report.jaxb.File oldFile
184                   = findFile(newFile, oldReport);
185               if (oldFile != null)
186               {
187                   findNewFindings(newFile, oldFile);
188               }
189               else
190               {
191                   flaggAllAsNew(newFile.getItem());
192               }
193           }
194           
195           writeResult(currentReport, mOutFile);
196       }
197       catch (Exception ex)
198       {
199           logger.log(Level.WARNING, 
200               "Failed to flagNewFindings. Cause " + ex.getMessage(), ex);
201       }
202   }
203
204    private void findNewFindings (org.jcoderz.phoenix.report.jaxb.File newFile,
205        org.jcoderz.phoenix.report.jaxb.File oldFile)
206    {
207        final List<Item> newFindings
208            = new ArrayList<Item>((List<Item>) newFile.getItem());
209        final List<Item> oldFindings
210            = new ArrayList<Item>((List<Item>) oldFile.getItem());
211
212        filterLowSeverity(newFindings);
213        filterLowSeverity(oldFindings);
214        filterFullMatches(newFindings, oldFindings);
215        filterPartialMatches(newFindings, oldFindings);
216
217        // the rest...
218        flaggAllAsNew(newFindings);
219        for (Item item : oldFindings)
220        {
221            addAsOld(newFile.getItem(), item);
222        }
223       
224    }
225
226    private void flaggAllAsNew (final List<Item> newFindings)
227    {
228        for (Item item : newFindings)
229        {
230            if (item.getSeverity().getPenalty() > 0
231                && item.getSeverity() != Severity.COVERAGE)
232            {
233                flagAsNew(item);
234            }
235        }
236    }
237
238    private void addAsOld (List<Item> newFindings, Item item)
239    {
240        if (item.getSeverity().getPenalty() > 0
241            && item.getSeverity() != Severity.COVERAGE)
242        {
243            item.setSeverity(Severity.OK);
244            item.unsetNew();
245            item.setOld(true);
246            newFindings.add(item);
247        }
248    }
249
250
251    private void filterFullMatches (final List<Item> newFindings,
252        final List<Item> oldFindings)
253    {
254        // Filter 100% matches:
255        final Iterator<Item> newIterator = newFindings.iterator();
256        while (newIterator.hasNext())
257        {
258            final Item newItem = newIterator.next();
259            final Iterator<Item> oldIterator = oldFindings.iterator();
260            while (oldIterator.hasNext())
261            {
262                final Item oldItem = oldIterator.next();
263                if (isSameFinding(newItem, oldItem))
264                {
265                    newItem.setSince(oldItem.getSince());
266                    newIterator.remove();
267                    oldIterator.remove();
268                    break;
269                }
270            }
271        }
272    }
273
274
275    /* private */ static boolean isSameFinding (Item newItem, Item oldItem)
276    {
277        final boolean result;
278        if (oldItem.getFindingType().equals(newItem.getFindingType()))
279        {
280            if (oldItem.getOrigin().equals(Origin.CPD))
281            {
282                // Fuzzy compare CPD Findings
283                // see also http://www.jcoderz.org/fawkez/ticket/71
284                result = oldItem.getLine() == newItem.getLine()
285                    && oldItem.getMessage().regionMatches(
286                        0, newItem.getMessage(), 0, CPD_UNIQUE_STRING_LENGTH);
287            }
288            else
289            {
290                result = oldItem.getLine() == newItem.getLine()
291                    && oldItem.getColumn() == newItem.getColumn()
292                    && oldItem.getMessage().equals(newItem.getMessage())
293                    && oldItem.getCounter() <= newItem.getCounter();
294            }
295        }
296        else
297        {
298            result = false;
299        }
300        return result;
301    }
302
303    private void filterPartialMatches (final List<Item> newFindings,
304        final List<Item> oldFindings)
305    {
306        // Filter matches that 'moved' within the file.
307        // There is for sure a better algorithm possible..
308        final Iterator<Item> newIterator = newFindings.iterator();
309        while (newIterator.hasNext())
310        {
311            final Item newItem = newIterator.next();
312            final Iterator<Item> oldIterator = oldFindings.iterator();
313            while (oldIterator.hasNext())
314            {
315                final Item oldItem = oldIterator.next();
316                if (isPartialSameFinding(newItem, oldItem))
317                {
318                    newItem.setSince(oldItem.getSince());
319                    newIterator.remove();
320                    oldIterator.remove();
321                    break;
322                }
323            }
324        }
325    }
326
327
328    private boolean isPartialSameFinding (Item newItem, Item oldItem)
329    {
330        final boolean result;
331        if (oldItem.getFindingType().equals(newItem.getFindingType()))
332        {
333            if (oldItem.getOrigin().equals(Origin.CPD))
334            {
335                // Fuzzy compare CPD Findings
336                // see also http://www.jcoderz.org/fawkez/ticket/71
337                // The or is by intention due to resistant findings
338                // reported as new frequently.
339                result = oldItem.getLine() == newItem.getLine()
340                    || oldItem.getMessage().regionMatches(
341                        0, newItem.getMessage(), 0, CPD_UNIQUE_STRING_LENGTH);
342            }
343            else
344            {
345                result = oldItem.getMessage().equals(newItem.getMessage())
346                    && oldItem.getCounter() <= newItem.getCounter();
347            }
348        }
349        else
350        {
351            result = false;
352        }
353        return result;
354    }
355
356    private void filterLowSeverity (final List<Item> newFindings)
357    {
358        final Iterator<Item> i = newFindings.iterator();
359        while (i.hasNext())
360        {
361            final Item item = i.next();
362            if (item.getSeverity().getPenalty() == 0
363                || item.getSeverity() == Severity.COVERAGE)
364            {
365                i.remove();
366            }
367        }
368    }
369
370    private void flagAsNew (Item item)
371    {
372        item.unsetOld();
373        item.setNew(true);
374        item.setSince(mReportDate);
375    }
376
377
378   // This could be done faster, might be restructure the data first for
379   // faster lookup.
380   private org.jcoderz.phoenix.report.jaxb.File findFile (
381       org.jcoderz.phoenix.report.jaxb.File newFile, Report oldReport)
382   {
383       final String className = newFile.getClassname();
384       final String packageName = newFile.getPackage();
385       final String fileName = newFile.getName();
386       org.jcoderz.phoenix.report.jaxb.File result = null;
387       for (org.jcoderz.phoenix.report.jaxb.File file
388           : (List<org.jcoderz.phoenix.report.jaxb.File>) oldReport.getFile())
389       {
390           if (ObjectUtil.equals(file.getName(), fileName) 
391               || (!StringUtil.isEmptyOrNull(className) 
392                   && packageName != null
393                   && ObjectUtil.equals(file.getClassname(), className) 
394                   && ObjectUtil.equals(file.getPackage(), packageName)))
395           {
396               result = file;
397               break;
398           }
399       }
400       return result;
401   }
402
403
404   /**
405    * Parses the arguments.
406    *
407    * @param args the args
408    */
409   private void parseArguments (String[] args)
410   {
411      try
412      {
413         for (int i = 0; i < args.length; )
414         {
415            logger.fine("Parsing argument '" + args[i] + "' = '"
416                  + args[i + 1] + "'");
417
418            if ("-jcreport".equals(args[i]))
419            {
420               addReport(new File(args[i + 1]));
421            }
422            else if ("-filter".equals(args[i]))
423            {
424               addFilter(new File(args[i + 1]));
425            }
426            else if ("-old".equals(args[i]))
427            {
428               setOldFile(new File(args[i + 1]));
429            }
430            else if ("-loglevel".equals(args[i]))
431            {
432                setLogLevel(Level.parse(args[i + 1]));
433            }
434            else if ("-out".equals(args[i]))
435            {
436                setOutFile(new File(args[i + 1]));
437            }
438            else
439            {
440               throw new IllegalArgumentException(
441                       "Invalid argument '" + args[i]  + "'");
442            }
443
444            ++i;
445            ++i;
446         }
447      }
448      catch (IndexOutOfBoundsException e)
449      {
450         final IllegalArgumentException ex = new IllegalArgumentException(
451            "Missing value for " + args[args.length - 1]);
452         ex.initCause(e);
453         throw ex;
454      }
455      catch (IOException e)
456      {
457         final IllegalArgumentException ex = new IllegalArgumentException(
458            "Wrong out folder " + args[args.length - 1]);
459         ex.initCause(e);
460         throw ex;
461      }
462   }
463
464   /**
465    * The main method.
466    *
467    * @param args the arguments
468    * @throws Exception in case of a technical issue.
469    */
470   public static void main (String[] args)
471       throws Exception
472   {
473      final ReportMerger rm = new ReportMerger();
474      rm.parseArguments(args);
475      rm.merge();
476      rm.filter();
477   }
478
479    /**
480     * Adds the report.
481     * @param report the report
482     */
483    public void addReport (File report)
484    {
485        mReports.add(report);
486    }
487
488    /**
489     * Adds the filter.
490     * @param filter the filter
491     */
492    public void addFilter (File filter)
493    {
494        mFilters.add(filter);
495    }
496
497    /**
498     * Gets the log level.
499     *
500     * @return the log level
501     */
502    public Level getLogLevel ()
503    {
504        return mLogLevel;
505    }
506
507
508    /**
509     * Sets the log level.
510     *
511     * @param logLevel the new log level
512     */
513    public void setLogLevel (Level logLevel)
514    {
515        mLogLevel = logLevel;
516        LoggingUtils.setGlobalHandlerLogLevel(mLogLevel);
517        logger.fine("Setting log level: " + mLogLevel);
518        logger.setLevel(mLogLevel);
519    }
520
521
522    /**
523     * Gets the out file.
524     *
525     * @return the out file
526     */
527    public File getOutFile ()
528    {
529        return mOutFile;
530    }
531
532
533    /**
534     * Set the old report to compare with.
535     * @param file old report file.
536     * @throws IOException if the file name conversion fails
537     */
538    public void setOldFile (File file)
539        throws IOException
540    {
541        Assert.notNull(file, "file");
542        if (mOldReport != null)
543        {
544            throw new ArgumentMalformedException("old", file,
545                "Old Report File has already set to '" + mOldReport + "'.");
546        }
547        mOldReport = file.getCanonicalFile();
548    }
549   
550    /**
551     * Sets the out file.
552     *
553     * @param outFile the new out file
554     *
555     * @throws IOException Signals that an I/O exception has occurred.
556     */
557    public void setOutFile (File outFile)
558        throws IOException
559    {
560        if (mOutFile != null)
561        {
562            throw new ArgumentMalformedException("outFile", outFile,
563                "Out File already set to '" + mOutFile + "'.");
564        }
565        mOutFile = outFile;
566        if (mOutFile.isDirectory())
567        {
568            FileUtils.mkdirs(mOutFile);
569            mOutFile = new File(mOutFile,
570                ReportNormalizer.JCODERZ_REPORT_XML).getCanonicalFile();
571        }
572        else
573        {
574           mOutFile = mOutFile.getCanonicalFile();
575        }
576
577    }
578
579    private void writeResult (final Report mergedReport, File outFile)
580        throws JAXBException, PropertyException, FileNotFoundException
581    {
582        // create the file
583         final JAXBContext mJaxbContext
584             = JAXBContext.newInstance("org.jcoderz.phoenix.report.jaxb",
585           this.getClass().getClassLoader());
586         final Marshaller marshaller = mJaxbContext.createMarshaller();
587         marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,
588                 Boolean.TRUE);
589         final FileOutputStream out = new FileOutputStream(outFile);
590         try
591         {
592             marshaller.marshal(mergedReport, out);
593         }
594         finally
595         {
596             IoUtil.close(out);
597         }
598    }
599   
600
601}
Note: See TracBrowser for help on using the browser.