001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2014 Oliver Burn 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//////////////////////////////////////////////////////////////////////////////// 019package com.puppycrawl.tools.checkstyle; 020 021import java.io.OutputStream; 022import java.io.OutputStreamWriter; 023import java.io.PrintWriter; 024import java.io.StringWriter; 025import java.io.UnsupportedEncodingException; 026import java.util.ResourceBundle; 027 028import com.puppycrawl.tools.checkstyle.api.AuditEvent; 029import com.puppycrawl.tools.checkstyle.api.AuditListener; 030import com.puppycrawl.tools.checkstyle.api.AutomaticBean; 031import com.puppycrawl.tools.checkstyle.api.SeverityLevel; 032 033/** 034 * Simple XML logger. 035 * It outputs everything in UTF-8 (default XML encoding is UTF-8) in case 036 * we want to localize error messages or simply that filenames are 037 * localized and takes care about escaping as well. 038 039 * @author <a href="mailto:stephane.bailliez@wanadoo.fr">Stephane Bailliez</a> 040 */ 041public class XMLLogger 042 extends AutomaticBean 043 implements AuditListener 044{ 045 /** decimal radix */ 046 private static final int BASE_10 = 10; 047 048 /** hex radix */ 049 private static final int BASE_16 = 16; 050 051 /** close output stream in auditFinished */ 052 private boolean mCloseStream; 053 054 /** helper writer that allows easy encoding and printing */ 055 private PrintWriter mWriter; 056 057 /** some known entities to detect */ 058 private static final String[] ENTITIES = {"gt", "amp", "lt", "apos", 059 "quot", }; 060 061 /** 062 * Creates a new <code>XMLLogger</code> instance. 063 * Sets the output to a defined stream. 064 * @param aOS the stream to write logs to. 065 * @param aCloseStream close aOS in auditFinished 066 */ 067 public XMLLogger(OutputStream aOS, boolean aCloseStream) 068 { 069 setOutputStream(aOS); 070 mCloseStream = aCloseStream; 071 } 072 073 /** 074 * sets the OutputStream 075 * @param aOS the OutputStream to use 076 **/ 077 private void setOutputStream(OutputStream aOS) 078 { 079 try { 080 final OutputStreamWriter osw = new OutputStreamWriter(aOS, "UTF-8"); 081 mWriter = new PrintWriter(osw); 082 } 083 catch (final UnsupportedEncodingException e) { 084 // unlikely to happen... 085 throw new ExceptionInInitializerError(e); 086 } 087 } 088 089 /** {@inheritDoc} */ 090 public void auditStarted(AuditEvent aEvt) 091 { 092 mWriter.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); 093 094 final ResourceBundle compilationProperties = 095 ResourceBundle.getBundle("checkstylecompilation"); 096 final String version = 097 compilationProperties.getString("checkstyle.compile.version"); 098 099 mWriter.println("<checkstyle version=\"" + version + "\">"); 100 } 101 102 /** {@inheritDoc} */ 103 public void auditFinished(AuditEvent aEvt) 104 { 105 mWriter.println("</checkstyle>"); 106 if (mCloseStream) { 107 mWriter.close(); 108 } 109 else { 110 mWriter.flush(); 111 } 112 } 113 114 /** {@inheritDoc} */ 115 public void fileStarted(AuditEvent aEvt) 116 { 117 mWriter.println("<file name=\"" + encode(aEvt.getFileName()) + "\">"); 118 } 119 120 /** {@inheritDoc} */ 121 public void fileFinished(AuditEvent aEvt) 122 { 123 mWriter.println("</file>"); 124 } 125 126 /** {@inheritDoc} */ 127 public void addError(AuditEvent aEvt) 128 { 129 if (!SeverityLevel.IGNORE.equals(aEvt.getSeverityLevel())) { 130 mWriter.print("<error" + " line=\"" + aEvt.getLine() + "\""); 131 if (aEvt.getColumn() > 0) { 132 mWriter.print(" column=\"" + aEvt.getColumn() + "\""); 133 } 134 mWriter.print(" severity=\"" 135 + aEvt.getSeverityLevel().getName() 136 + "\""); 137 mWriter.print(" message=\"" 138 + encode(aEvt.getMessage()) 139 + "\""); 140 mWriter.println(" source=\"" 141 + encode(aEvt.getSourceName()) 142 + "\"/>"); 143 } 144 } 145 146 /** {@inheritDoc} */ 147 public void addException(AuditEvent aEvt, Throwable aThrowable) 148 { 149 final StringWriter sw = new StringWriter(); 150 final PrintWriter pw = new PrintWriter(sw); 151 pw.println("<exception>"); 152 pw.println("<![CDATA["); 153 aThrowable.printStackTrace(pw); 154 pw.println("]]>"); 155 pw.println("</exception>"); 156 pw.flush(); 157 mWriter.println(encode(sw.toString())); 158 } 159 160 /** 161 * Escape <, > & ' and " as their entities. 162 * @param aValue the value to escape. 163 * @return the escaped value if necessary. 164 */ 165 public String encode(String aValue) 166 { 167 final StringBuffer sb = new StringBuffer(); 168 for (int i = 0; i < aValue.length(); i++) { 169 final char c = aValue.charAt(i); 170 switch (c) { 171 case '<': 172 sb.append("<"); 173 break; 174 case '>': 175 sb.append(">"); 176 break; 177 case '\'': 178 sb.append("'"); 179 break; 180 case '\"': 181 sb.append("""); 182 break; 183 case '&': 184 final int nextSemi = aValue.indexOf(";", i); 185 if ((nextSemi < 0) 186 || !isReference(aValue.substring(i, nextSemi + 1))) 187 { 188 sb.append("&"); 189 } 190 else { 191 sb.append('&'); 192 } 193 break; 194 default: 195 sb.append(c); 196 break; 197 } 198 } 199 return sb.toString(); 200 } 201 202 /** 203 * @return whether the given argument a character or entity reference 204 * @param aEnt the possible entity to look for. 205 */ 206 public boolean isReference(String aEnt) 207 { 208 if (!(aEnt.charAt(0) == '&') || !aEnt.endsWith(";")) { 209 return false; 210 } 211 212 if (aEnt.charAt(1) == '#') { 213 int prefixLength = 2; // "&#" 214 int radix = BASE_10; 215 if (aEnt.charAt(2) == 'x') { 216 prefixLength++; 217 radix = BASE_16; 218 } 219 try { 220 Integer.parseInt( 221 aEnt.substring(prefixLength, aEnt.length() - 1), radix); 222 return true; 223 } 224 catch (final NumberFormatException nfe) { 225 return false; 226 } 227 } 228 229 final String name = aEnt.substring(1, aEnt.length() - 1); 230 for (String element : ENTITIES) { 231 if (name.equals(element)) { 232 return true; 233 } 234 } 235 return false; 236 } 237}