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