1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2019 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle;
21  
22  import java.io.File;
23  import java.io.OutputStream;
24  import java.io.OutputStreamWriter;
25  import java.io.PrintWriter;
26  import java.nio.charset.StandardCharsets;
27  
28  import com.puppycrawl.tools.checkstyle.api.AuditEvent;
29  import com.puppycrawl.tools.checkstyle.api.AuditListener;
30  import com.puppycrawl.tools.checkstyle.api.AutomaticBean;
31  
32  /**
33   * Generates <b>suppressions.xml</b> file, based on violations occurred.
34   * See issue #102 https://github.com/checkstyle/checkstyle/issues/102
35   */
36  public class XpathFileGeneratorAuditListener extends AutomaticBean implements AuditListener {
37  
38      /** The " quote character. */
39      private static final String QUOTE_CHAR = "\"";
40  
41      /**
42       * Helper writer that allows easy encoding and printing.
43       */
44      private final PrintWriter writer;
45  
46      /** Close output stream in auditFinished. */
47      private final boolean closeStream;
48  
49      /** Determines if xml header is printed. */
50      private boolean isXmlHeaderPrinted;
51  
52      /**
53       * Creates a new {@code SuppressionFileGenerator} instance.
54       * Sets the output to a defined stream.
55       * @param out the output stream
56       * @param outputStreamOptions if {@code CLOSE} stream should be closed in auditFinished()
57       */
58      public XpathFileGeneratorAuditListener(OutputStream out,
59                                             OutputStreamOptions outputStreamOptions) {
60          writer = new PrintWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8));
61          closeStream = outputStreamOptions == OutputStreamOptions.CLOSE;
62      }
63  
64      @Override
65      public void auditStarted(AuditEvent event) {
66          // No code by default
67      }
68  
69      @Override
70      public void auditFinished(AuditEvent event) {
71          if (isXmlHeaderPrinted) {
72              writer.println("</suppressions>");
73          }
74  
75          writer.flush();
76          if (closeStream) {
77              writer.close();
78          }
79      }
80  
81      @Override
82      public void fileStarted(AuditEvent event) {
83          // No code by default
84      }
85  
86      @Override
87      public void fileFinished(AuditEvent event) {
88          // No code by default
89      }
90  
91      @Override
92      public void addError(AuditEvent event) {
93          final String xpathQuery = XpathFileGeneratorAstFilter.findCorrespondingXpathQuery(event);
94          if (xpathQuery != null) {
95              printXmlHeader();
96  
97              final File file = new File(event.getFileName());
98  
99              writer.println("<suppress-xpath");
100             writer.print("       files=\"");
101             writer.print(file.getName());
102             writer.println(QUOTE_CHAR);
103 
104             if (event.getModuleId() == null) {
105                 final String checkName =
106                         PackageObjectFactory.getShortFromFullModuleNames(event.getSourceName());
107                 writer.print("       checks=\"");
108                 writer.print(checkName);
109             }
110             else {
111                 writer.print("       id=\"");
112                 writer.print(event.getModuleId());
113             }
114             writer.println(QUOTE_CHAR);
115 
116             writer.print("       query=\"");
117             writer.print(xpathQuery);
118 
119             writer.println("\"/>");
120         }
121     }
122 
123     @Override
124     public void addException(AuditEvent event, Throwable throwable) {
125         throw new UnsupportedOperationException("Operation is not supported");
126     }
127 
128     /**
129      * Prints XML header if only it was not printed before.
130      */
131     private void printXmlHeader() {
132         if (!isXmlHeaderPrinted) {
133             writer.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
134             writer.println("<!DOCTYPE suppressions PUBLIC");
135             writer.println("    \"-//Checkstyle//DTD SuppressionXpathFilter Experimental "
136                     + "Configuration 1.2//EN\"");
137             writer.println("    \"https://checkstyle.org/dtds/"
138                     + "suppressions_1_2_xpath_experimental.dtd\">");
139             writer.println("<suppressions>");
140             isXmlHeaderPrinted = true;
141         }
142     }
143 
144     @Override
145     protected void finishLocalSetup() {
146         // No code by default
147     }
148 }