001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2025 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018/////////////////////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle; 021 022import java.io.OutputStream; 023import java.io.OutputStreamWriter; 024import java.io.PrintWriter; 025import java.nio.charset.StandardCharsets; 026import java.nio.file.Path; 027 028import com.puppycrawl.tools.checkstyle.api.AuditEvent; 029import com.puppycrawl.tools.checkstyle.api.AuditListener; 030 031/** 032 * Generates <b>suppressions.xml</b> file, based on violations occurred. 033 * See issue <a href="https://github.com/checkstyle/checkstyle/issues/102">#102</a> 034 */ 035public final class XpathFileGeneratorAuditListener 036 extends AbstractAutomaticBean 037 implements AuditListener { 038 039 /** The " quote character. */ 040 private static final String QUOTE_CHAR = "\""; 041 042 /** 043 * Helper writer that allows easy encoding and printing. 044 */ 045 private final PrintWriter writer; 046 047 /** Close output stream in auditFinished. */ 048 private final boolean closeStream; 049 050 /** Determines if xml header is printed. */ 051 private boolean isXmlHeaderPrinted; 052 053 /** 054 * Creates a new {@code SuppressionFileGenerator} instance. 055 * Sets the output to a defined stream. 056 * 057 * @param out the output stream 058 * @param outputStreamOptions if {@code CLOSE} stream should be closed in auditFinished() 059 * @throws IllegalArgumentException if outputStreamOptions is null 060 */ 061 public XpathFileGeneratorAuditListener(OutputStream out, 062 OutputStreamOptions outputStreamOptions) { 063 if (outputStreamOptions == null) { 064 throw new IllegalArgumentException("Parameter outputStreamOptions can not be null"); 065 } 066 067 writer = new PrintWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8)); 068 closeStream = outputStreamOptions == OutputStreamOptions.CLOSE; 069 } 070 071 @Override 072 public void auditStarted(AuditEvent event) { 073 // No code by default 074 } 075 076 @Override 077 public void auditFinished(AuditEvent event) { 078 if (isXmlHeaderPrinted) { 079 writer.println("</suppressions>"); 080 } 081 082 writer.flush(); 083 if (closeStream) { 084 writer.close(); 085 } 086 } 087 088 @Override 089 public void fileStarted(AuditEvent event) { 090 // No code by default 091 } 092 093 @Override 094 public void fileFinished(AuditEvent event) { 095 // No code by default 096 } 097 098 @Override 099 public void addError(AuditEvent event) { 100 final String xpathQuery = XpathFileGeneratorAstFilter.findCorrespondingXpathQuery(event); 101 if (xpathQuery != null) { 102 printXmlHeader(); 103 104 final Path path = Path.of(event.getFileName()); 105 106 writer.println(" <suppress-xpath"); 107 writer.print(" files=\""); 108 writer.print(path.getFileName()); 109 writer.println(QUOTE_CHAR); 110 111 if (event.getModuleId() == null) { 112 final String checkName = 113 PackageObjectFactory.getShortFromFullModuleNames(event.getSourceName()); 114 writer.print(" checks=\""); 115 writer.print(checkName); 116 } 117 else { 118 writer.print(" id=\""); 119 writer.print(event.getModuleId()); 120 } 121 writer.println(QUOTE_CHAR); 122 123 writer.print(" query=\""); 124 writer.print(xpathQuery); 125 126 writer.println("\"/>"); 127 } 128 } 129 130 @Override 131 public void addException(AuditEvent event, Throwable throwable) { 132 throw new UnsupportedOperationException("Operation is not supported"); 133 } 134 135 /** 136 * Prints XML header if only it was not printed before. 137 */ 138 private void printXmlHeader() { 139 if (!isXmlHeaderPrinted) { 140 writer.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); 141 writer.println("<!DOCTYPE suppressions PUBLIC"); 142 writer.println(" \"-//Checkstyle//DTD SuppressionXpathFilter Experimental " 143 + "Configuration 1.2//EN\""); 144 writer.println(" \"https://checkstyle.org/dtds/" 145 + "suppressions_1_2_xpath_experimental.dtd\">"); 146 writer.println("<suppressions>"); 147 isXmlHeaderPrinted = true; 148 } 149 } 150 151 @Override 152 protected void finishLocalSetup() { 153 // No code by default 154 } 155}