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 closeStream; 053 054 /** helper writer that allows easy encoding and printing */ 055 private PrintWriter writer; 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 os the stream to write logs to. 065 * @param closeStream close oS in auditFinished 066 */ 067 public XMLLogger(OutputStream os, boolean closeStream) 068 { 069 setOutputStream(os); 070 this.closeStream = closeStream; 071 } 072 073 /** 074 * sets the OutputStream 075 * @param oS the OutputStream to use 076 **/ 077 private void setOutputStream(OutputStream oS) 078 { 079 try { 080 final OutputStreamWriter osw = new OutputStreamWriter(oS, "UTF-8"); 081 writer = new PrintWriter(osw); 082 } 083 catch (final UnsupportedEncodingException e) { 084 // unlikely to happen... 085 throw new ExceptionInInitializerError(e); 086 } 087 } 088 089 /** {@inheritDoc} */ 090 @Override 091 public void auditStarted(AuditEvent evt) 092 { 093 writer.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); 094 095 final ResourceBundle compilationProperties = 096 ResourceBundle.getBundle("checkstylecompilation"); 097 final String version = 098 compilationProperties.getString("checkstyle.compile.version"); 099 100 writer.println("<checkstyle version=\"" + version + "\">"); 101 } 102 103 /** {@inheritDoc} */ 104 @Override 105 public void auditFinished(AuditEvent evt) 106 { 107 writer.println("</checkstyle>"); 108 if (closeStream) { 109 writer.close(); 110 } 111 else { 112 writer.flush(); 113 } 114 } 115 116 /** {@inheritDoc} */ 117 @Override 118 public void fileStarted(AuditEvent evt) 119 { 120 writer.println("<file name=\"" + encode(evt.getFileName()) + "\">"); 121 } 122 123 /** {@inheritDoc} */ 124 @Override 125 public void fileFinished(AuditEvent evt) 126 { 127 writer.println("</file>"); 128 } 129 130 /** {@inheritDoc} */ 131 @Override 132 public void addError(AuditEvent evt) 133 { 134 if (!SeverityLevel.IGNORE.equals(evt.getSeverityLevel())) { 135 writer.print("<error" + " line=\"" + evt.getLine() + "\""); 136 if (evt.getColumn() > 0) { 137 writer.print(" column=\"" + evt.getColumn() + "\""); 138 } 139 writer.print(" severity=\"" 140 + evt.getSeverityLevel().getName() 141 + "\""); 142 writer.print(" message=\"" 143 + encode(evt.getMessage()) 144 + "\""); 145 writer.println(" source=\"" 146 + encode(evt.getSourceName()) 147 + "\"/>"); 148 } 149 } 150 151 /** {@inheritDoc} */ 152 @Override 153 public void addException(AuditEvent evt, Throwable throwable) 154 { 155 final StringWriter sw = new StringWriter(); 156 final PrintWriter pw = new PrintWriter(sw); 157 pw.println("<exception>"); 158 pw.println("<![CDATA["); 159 throwable.printStackTrace(pw); 160 pw.println("]]>"); 161 pw.println("</exception>"); 162 pw.flush(); 163 writer.println(encode(sw.toString())); 164 } 165 166 /** 167 * Escape <, > & ' and " as their entities. 168 * @param value the value to escape. 169 * @return the escaped value if necessary. 170 */ 171 public String encode(String value) 172 { 173 final StringBuffer sb = new StringBuffer(); 174 for (int i = 0; i < value.length(); i++) { 175 final char c = value.charAt(i); 176 switch (c) { 177 case '<': 178 sb.append("<"); 179 break; 180 case '>': 181 sb.append(">"); 182 break; 183 case '\'': 184 sb.append("'"); 185 break; 186 case '\"': 187 sb.append("""); 188 break; 189 case '&': 190 final int nextSemi = value.indexOf(";", i); 191 if ((nextSemi < 0) 192 || !isReference(value.substring(i, nextSemi + 1))) 193 { 194 sb.append("&"); 195 } 196 else { 197 sb.append('&'); 198 } 199 break; 200 default: 201 sb.append(c); 202 break; 203 } 204 } 205 return sb.toString(); 206 } 207 208 /** 209 * @return whether the given argument a character or entity reference 210 * @param ent the possible entity to look for. 211 */ 212 public boolean isReference(String ent) 213 { 214 if (!(ent.charAt(0) == '&') || !ent.endsWith(";")) { 215 return false; 216 } 217 218 if (ent.charAt(1) == '#') { 219 int prefixLength = 2; // "&#" 220 int radix = BASE_10; 221 if (ent.charAt(2) == 'x') { 222 prefixLength++; 223 radix = BASE_16; 224 } 225 try { 226 Integer.parseInt( 227 ent.substring(prefixLength, ent.length() - 1), radix); 228 return true; 229 } 230 catch (final NumberFormatException nfe) { 231 return false; 232 } 233 } 234 235 final String name = ent.substring(1, ent.length() - 1); 236 for (String element : ENTITIES) { 237 if (name.equals(element)) { 238 return true; 239 } 240 } 241 return false; 242 } 243}