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

Revision 1606, 16.9 kB (checked in by amandel, 2 years ago)

- use current PMD from public repository
- remove implied dependencies from explicit list
- ignore compiler classpath warning.

Line 
1/*
2 * $Id: SourceDirectoryReader.java 1408 2009-04-14 16:06:46Z amandel $
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.BufferedReader;
36import java.io.File;
37import java.io.FileInputStream;
38import java.io.FileNotFoundException;
39import java.io.FileReader;
40import java.io.IOException;
41import java.io.InputStream;
42import java.io.Reader;
43import java.util.ArrayList;
44import java.util.Collections;
45import java.util.HashMap;
46import java.util.Iterator;
47import java.util.List;
48import java.util.Map;
49import java.util.logging.Level;
50import java.util.logging.Logger;
51import java.util.regex.Matcher;
52import java.util.regex.Pattern;
53
54import javax.xml.bind.JAXBException;
55
56import org.jcoderz.commons.util.Assert;
57import org.jcoderz.commons.util.Constants;
58import org.jcoderz.commons.util.IoUtil;
59import org.jcoderz.commons.util.JaxbUtil;
60import org.jcoderz.commons.util.ObjectUtil;
61import org.jcoderz.commons.util.StringUtil;
62import org.jcoderz.commons.util.JaxbUtil.UnmarshalResult;
63import org.jcoderz.phoenix.report.ftf.jaxb.FindingDescription;
64import org.jcoderz.phoenix.report.ftf.jaxb.FindingTypeFormat;
65import org.jcoderz.phoenix.report.jaxb.Item;
66import org.xml.sax.InputSource;
67
68/**
69 * Reads reports with format definitions described in the
70 * finding-type-format-definition.xds.
71 *
72 * To find the finding type format definition for requested format
73 * the following locations are used:
74 *
75 * The name is converted to lower case.
76 *
77 * A file <i>name</i>.xml is searched in the
78 * <code>org.jcoderz.phoenix.report.ftf</code> package. If
79 * this is not found the file is searched in the <code>ftf</code>
80 * directory. The directory must be available through the classpath.
81 *
82 *
83 * @author Andreas Mandel
84 *
85 */
86public final class GenericReportReader implements ReportReader
87{
88    private static final int MAX_DEBUG_TEXT_CHARS = 100;
89    private static final String CLASSNAME
90        = GenericReportReader.class.getName();
91    private static final Logger logger = Logger.getLogger(CLASSNAME);
92
93    private static final Pattern CODE_LINE_PATTERN
94        = Pattern.compile("^.*$", Pattern.MULTILINE);
95
96    private static final Pattern CARET_LINE_PATTERN
97        = Pattern.compile("^\\s*\\^$", Pattern.MULTILINE);
98
99    private static final Map<Origin, GenericReportReader> GENERIC_REPORT_TYPES
100        = new HashMap<Origin, GenericReportReader>();
101
102
103    private final List<GenericFindingType> mFindingTypes
104        = new ArrayList<GenericFindingType>();
105
106    private Map<ResourceInfo, List<Item>> mItems;
107
108    private SourceFile mSourceFile;
109
110    private final Pattern mMessagePattern;
111    private final FindingTypeFormat mFindingTypeFormatDescription;
112
113    private final int mTextPos;
114    private final Origin mOrigin;
115    private final int mFilePos;
116    private final int mLineStart;
117    private final Severity mDefaultSeverity;
118
119    private Matcher mRootMatcher = null;
120
121    private GenericReportReader (Origin type)
122        throws JAXBException
123    {
124        mOrigin = type;
125        mFindingTypeFormatDescription = loadFormatDescription(type);
126        initializeFindingTypes();
127        final FindingDescription root
128            = mFindingTypeFormatDescription.getRootType();
129        mMessagePattern
130            = Pattern.compile(root.getPattern(),
131                Pattern.MULTILINE);
132        mTextPos =  Integer.parseInt(root.getTextPos());
133        mFilePos =  Integer.parseInt(root.getFilenamePos());
134        mLineStart = root.isSetLineStartPos()
135            ? Integer.parseInt(root.getLineStartPos()) : -1;
136        mDefaultSeverity = root.isSetSeverity()
137            ? root.getSeverity() : Severity.CODE_STYLE;
138    }
139
140    /**
141     * Initializes the selected finding type.
142     * Might return <code>null</code> if the initialization fails.
143     * CHECKME: Should return a null object?
144     * @param findingType the type to load.
145     * @return the loaded finding type.
146     */
147    public static GenericReportReader initialize (Origin findingType)
148    {
149        GenericReportReader result = null;
150        synchronized (GENERIC_REPORT_TYPES)
151        {
152            if (!GENERIC_REPORT_TYPES.containsKey(findingType))
153            {
154                try
155                {
156                    result = new GenericReportReader(findingType);
157                }
158                catch (Exception ex)
159                {
160                    // TODO: collect this an add it to the findings map later!
161                    logger.log(Level.WARNING,
162                        "Could not load finding type for '" + findingType
163                        + "' failed with " + ex.getMessage() + ".", ex);
164                }
165                GENERIC_REPORT_TYPES.put(findingType, result);
166            }
167            result = GENERIC_REPORT_TYPES.get(findingType);
168        }
169        return result;
170    }
171
172    private static FindingTypeFormat loadFormatDescription (Origin type)
173        throws JAXBException
174    {
175        FindingTypeFormat findingTypeFormatDescription = null;
176        InputStream in = null;
177        try
178        {
179            final String filename
180                = type.toString().toLowerCase(Constants.SYSTEM_LOCALE)
181                    + ".xml";
182            in = GenericReportReader.class.getResourceAsStream(
183                "ftf/" + filename);
184            if (in == null)
185            {
186                in = GenericReportReader.class.getResourceAsStream(
187                    "/ftf/" + filename);
188            }
189            if (in == null)
190            {
191                try
192                {
193                    in = new FileInputStream(filename);
194                }
195                catch (FileNotFoundException ex)
196                {
197                    // in = null;
198                }
199            }
200            Assert.notNull(in, "report type description " + type);
201            final UnmarshalResult unmarshal
202                = JaxbUtil.unmarshal(new InputSource(in),
203                    "org.jcoderz.phoenix.report.ftf.jaxb");
204
205            findingTypeFormatDescription
206                = (FindingTypeFormat) unmarshal.getParsedData();
207        }
208        finally
209        {
210            IoUtil.close(in);
211        }
212        return findingTypeFormatDescription;
213    }
214
215    /** {@inheritDoc} */
216    public void parse (File f)
217        throws JAXBException
218    {
219        try
220        {
221            mSourceFile = new SourceFile(f);
222            mRootMatcher = mMessagePattern.matcher(mSourceFile.getContent());
223        }
224        catch (IOException ex)
225        {
226            throw new JAXBException("Failed to read '" + f + "'.", ex);
227        }
228    }
229
230    /** {@inheritDoc} */
231    public void merge (Map<ResourceInfo, List<Item>> items)
232        throws JAXBException
233    {
234        mItems = items;
235        while (!mSourceFile.readFully())
236        {
237            parseNext();
238        }
239    }
240
241    /**
242     * Reads the given message and tries to find a matching finding type.
243     * @param message the message to read.
244     * @return the finding type matching to the message, or null if no such
245     *   type was found.
246     * @throws JAXBException if item creation fails.
247     */
248    public Item detectFindingTypeForMessage (String message)
249        throws JAXBException
250    {
251       Item result = null;
252       for (final GenericFindingType type : mFindingTypes)
253       {
254           result = type.createItem(mSourceFile, message);
255           if (result != null)
256           {
257              if (type.isSourceColumnByCaret())
258              {
259                  addPositionByCaret(result);
260              }
261              break;
262           }
263       }
264       if (logger.isLoggable(Level.FINE))
265       {
266           logger.fine("For text: '"
267               + StringUtil.trimLength(message, MAX_DEBUG_TEXT_CHARS)
268               + "' matched finding: "
269               + (result == null ? "null" : result.getFindingType()
270               + "'. End at " + mSourceFile.getPos()));
271       }
272       return result;
273    }
274
275    private void addPositionByCaret (final Item i)
276    {
277        final String text
278            = mSourceFile.getContent().substring(mSourceFile.getPos());
279        final Matcher codeMat
280            = CODE_LINE_PATTERN.matcher(text);
281        if (codeMat.lookingAt())
282        {
283            final String textAfterCode
284                = mSourceFile.getContent().substring(
285                    mSourceFile.getPos() + codeMat.end() + 1);
286            final Matcher caretMat
287                = CARET_LINE_PATTERN.matcher(textAfterCode);
288            if (caretMat.lookingAt())
289            {
290                i.setColumn(caretMat.end());
291                mSourceFile.setPos(
292                    mSourceFile.getPos()
293                    + codeMat.end() + 1
294                    + caretMat.end() + 1);
295            }
296            else
297            {
298                logger.fine("Caret defined but not found for '"
299                    + i.getFindingType()
300                    + "' Code Line: '" + codeMat + "' caretLine: '"
301                    + caretMat + "'. text: '"
302                    + StringUtil.trimLength(
303                        textAfterCode, MAX_DEBUG_TEXT_CHARS) + "'.");
304            }
305        }
306        else
307        {
308            logger.fine("Caret defined but not found for '"
309                + i.getFindingType()
310                + "' Code Line: '" + codeMat + "'. text: '"
311                + StringUtil.trimLength(
312                    text, MAX_DEBUG_TEXT_CHARS) + "'.");
313        }
314    }
315
316    private void parseNext ()
317        throws JAXBException
318    {
319        if (mRootMatcher.find())
320        {
321            final String text = mRootMatcher.group(mTextPos);
322            if (logger.isLoggable(Level.FINE))
323            {
324                logger.fine("Main pattern matched for: '"
325                    + StringUtil.trimLength(text, MAX_DEBUG_TEXT_CHARS)
326                    + "'. End at " + mRootMatcher.end());
327            }
328            mSourceFile.setPos(mRootMatcher.start(mTextPos));
329            final Item item = detectFindingTypeForMessage(text);
330            if (item == null)
331            {
332                final int pos
333                    = mSourceFile.getContent().indexOf(
334                        '\n', mSourceFile.getPos());
335                if (pos != -1)
336                {
337                    mSourceFile.setPos(pos + 1);
338                }
339                else
340                {
341                    mSourceFile.setPos(mSourceFile.getContent().length());
342                }
343            }
344            else
345            {
346                item.setOrigin(mOrigin);
347                if (!item.isSetSeverity())
348                {
349                    item.setSeverity(mDefaultSeverity);
350                }
351                if (!item.isSetLine() && mLineStart != -1
352                    && mRootMatcher.group(mLineStart) != null)
353                {
354                    item.setLine(
355                        Integer.parseInt(mRootMatcher.group(mLineStart)));
356                }
357                if (!item.isSetFindingType())
358                {
359                    item.setFindingType(mOrigin.toString());
360                }
361                if (!item.isSetMessage())
362                {
363                    item.setMessage(mRootMatcher.group(mTextPos));
364                }
365                if (mFindingTypeFormatDescription.getRootType().isGlobal())
366                {
367                    item.setGlobal(true);
368                }
369                addItemToResource(mRootMatcher.group(mFilePos), item);
370            }
371            mRootMatcher.region(
372                mSourceFile.getPos(), mSourceFile.getContent().length());
373        }
374        else
375        {
376            if (logger.isLoggable(Level.FINE))
377            {
378                logger.fine("No match after " + mSourceFile.getPos()
379                    + " '" + StringUtil.trimLength(
380                        mSourceFile.getContent().substring(
381                            mSourceFile.getPos()),
382                            MAX_DEBUG_TEXT_CHARS));
383            }
384            // set pos to end of file
385            mSourceFile.setPos(mSourceFile.getContent().length());
386        }
387    }
388
389    private void addItemToResource (String resourceFilename, Item item)
390    {
391        final ResourceInfo info = ResourceInfo.lookup(resourceFilename);
392        if (info != null || item.isGlobal())
393        {
394            final List<Item> l;
395            if (mItems.containsKey(info))
396            {
397                l = mItems.get(info);
398            }
399            else
400            {
401                l = new ArrayList<Item>();
402                mItems.put(info, l);
403            }
404            // sometimes javadoc reports the same thing twice...
405            final Iterator<Item> i = l.iterator();
406            while (i.hasNext())
407            {
408                final Item it = i.next();
409                if (it.getLine() == item.getLine()
410                    && it.getColumn() == item.getColumn()
411                    && it.getOrigin() == item.getOrigin()
412                    && ObjectUtil.equals(it.getMessage(), item.getMessage()))
413                {
414                    i.remove();
415                    break;
416                }
417            }
418            l.add(item);
419        }
420        else
421        {
422            logger.finer("Ignore findings for resource '"
423                + resourceFilename + "' type was "
424                + item.getFindingType() + ".");
425        }
426    }
427
428    private void initializeFindingTypes ()
429    {
430        final FindingDescription root
431            = mFindingTypeFormatDescription.getRootType();
432        final List<FindingDescription> findingTypes
433            = mFindingTypeFormatDescription.getFindingType();
434        for (FindingDescription findingDesc : findingTypes)
435        {
436            final GenericFindingType gft
437                = new GenericFindingType(root, findingDesc);
438            mFindingTypes.add(gft);
439        }
440        Collections.sort(
441            mFindingTypes, new GenericFindingType.OrderByPriority());
442    }
443
444
445    static final class SourceFile
446    {
447        private final File mFile;
448        private final String mContent;
449        private int mPos;
450
451        public SourceFile (File file)
452            throws IOException
453        {
454            mFile = file;
455            Reader in = null;
456            Reader buffered = null;
457            try
458            {
459                in = new FileReader(file);
460                buffered = new BufferedReader(in);
461                mContent = IoUtil.readFullyNormalizeNewLine(buffered);
462            }
463            finally
464            {
465                IoUtil.close(buffered);
466                IoUtil.close(in);
467            }
468            mPos = 0;
469        }
470
471        /**
472         * @return the pos
473         */
474        public int getPos ()
475        {
476            return mPos;
477        }
478
479        /**
480         * @param pos the pos to set
481         */
482        public void setPos (int pos)
483        {
484            mPos = pos;
485        }
486
487        /**
488         * @return the file
489         */
490        public File getFile ()
491        {
492            return mFile;
493        }
494
495        /**
496         * @return the content
497         */
498        public String getContent ()
499        {
500            return mContent;
501        }
502
503        public boolean readFully ()
504        {
505            return mPos >= mContent.length();
506        }
507    }
508}
Note: See TracBrowser for help on using the browser.