001 /* 002 * Copyright (c) 2007 Mozilla Foundation 003 * 004 * Permission is hereby granted, free of charge, to any person obtaining a 005 * copy of this software and associated documentation files (the "Software"), 006 * to deal in the Software without restriction, including without limitation 007 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 008 * and/or sell copies of the Software, and to permit persons to whom the 009 * Software is furnished to do so, subject to the following conditions: 010 * 011 * The above copyright notice and this permission notice shall be included in 012 * all copies or substantial portions of the Software. 013 * 014 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 015 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 016 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 017 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 018 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 019 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 020 * DEALINGS IN THE SOFTWARE. 021 */ 022 023 package nu.validator.messages; 024 025 import java.io.IOException; 026 import java.io.OutputStream; 027 import java.io.OutputStreamWriter; 028 import java.io.Writer; 029 import java.nio.charset.Charset; 030 import java.nio.charset.CharsetEncoder; 031 import java.nio.charset.CodingErrorAction; 032 033 import nu.validator.messages.types.MessageType; 034 import nu.validator.xml.XhtmlSaxEmitter; 035 036 import org.xml.sax.ContentHandler; 037 import org.xml.sax.SAXException; 038 039 public class TextMessageEmitter extends MessageEmitter { 040 041 private static final char[] COLON_SPACE = { ':', ' ' }; 042 043 private static final char[] PERIOD = { '.' }; 044 045 private static final char[] ON_LINE = "On line ".toCharArray(); 046 047 private static final char[] AT_LINE = "At line ".toCharArray(); 048 049 private static final char[] FROM_LINE = "From line ".toCharArray(); 050 051 private static final char[] TO_LINE = "; to line ".toCharArray(); 052 053 private static final char[] COLUMN = ", column ".toCharArray(); 054 055 private static final char[] IN_RESOURCE = " in resource ".toCharArray(); 056 057 private final Writer writer; 058 059 private final TextMessageTextHandler messageTextHandler; 060 061 private String systemId; 062 063 private int oneBasedFirstLine; 064 065 private int oneBasedFirstColumn; 066 067 private int oneBasedLastLine; 068 069 private int oneBasedLastColumn; 070 071 private boolean exact; 072 073 private boolean textEmitted; 074 075 private static Writer newOutputStreamWriter(OutputStream out) { 076 CharsetEncoder enc = Charset.forName("UTF-8").newEncoder(); 077 enc.onMalformedInput(CodingErrorAction.REPLACE); 078 enc.onUnmappableCharacter(CodingErrorAction.REPLACE); 079 return new OutputStreamWriter(out, enc); 080 } 081 082 public TextMessageEmitter(OutputStream out) { 083 this.writer = newOutputStreamWriter(out); 084 this.messageTextHandler = new TextMessageTextHandler(writer); 085 } 086 087 private void emitErrorLevel(char[] level) throws IOException { 088 writer.write(level, 0, level.length); 089 } 090 091 private void maybeEmitLocation() throws IOException { 092 if (oneBasedLastLine == -1 && systemId == null) { 093 return; 094 } 095 if (oneBasedLastLine == -1) { 096 emitSystemId(); 097 } else if (oneBasedLastColumn == -1) { 098 emitLineLocation(); 099 } else if (oneBasedFirstLine == -1 100 || (oneBasedFirstLine == oneBasedLastLine && oneBasedFirstColumn == oneBasedLastColumn)) { 101 emitSingleLocation(); 102 } else { 103 emitRangeLocation(); 104 } 105 writer.write('\n'); 106 } 107 108 /** 109 * @throws SAXException 110 */ 111 private void maybeEmitInResource() throws IOException { 112 if (systemId != null) { 113 this.writer.write(IN_RESOURCE); 114 emitSystemId(); 115 } 116 } 117 118 /** 119 * @throws SAXException 120 */ 121 private void emitSystemId() throws IOException { 122 this.writer.write(systemId); 123 } 124 125 private void emitRangeLocation() throws IOException { 126 this.writer.write(FROM_LINE); 127 this.writer.write(Integer.toString(oneBasedFirstLine)); 128 this.writer.write(COLUMN); 129 this.writer.write(Integer.toString(oneBasedFirstColumn)); 130 this.writer.write(TO_LINE); 131 this.writer.write(Integer.toString(oneBasedLastLine)); 132 this.writer.write(COLUMN); 133 this.writer.write(Integer.toString(oneBasedLastColumn)); 134 maybeEmitInResource(); 135 } 136 137 private void emitSingleLocation() throws IOException { 138 this.writer.write(AT_LINE); 139 this.writer.write(Integer.toString(oneBasedLastLine)); 140 this.writer.write(COLUMN); 141 this.writer.write(Integer.toString(oneBasedLastColumn)); 142 maybeEmitInResource(); 143 } 144 145 private void emitLineLocation() throws IOException { 146 this.writer.write(ON_LINE); 147 this.writer.write(Integer.toString(oneBasedLastLine)); 148 maybeEmitInResource(); 149 } 150 151 @Override 152 public void startMessage(MessageType type, String systemId, 153 int oneBasedFirstLine, int oneBasedFirstColumn, 154 int oneBasedLastLine, int oneBasedLastColumn, boolean exact) 155 throws SAXException { 156 this.systemId = systemId; 157 this.oneBasedFirstLine = oneBasedFirstLine; 158 this.oneBasedFirstColumn = oneBasedFirstColumn; 159 this.oneBasedLastLine = oneBasedLastLine; 160 this.oneBasedLastColumn = oneBasedLastColumn; 161 this.exact = exact; 162 try { 163 emitErrorLevel(type.getPresentationName()); 164 } catch (IOException e) { 165 throw new SAXException(e.getLocalizedMessage(), e); 166 } 167 this.textEmitted = false; 168 } 169 170 /** 171 * @see nu.validator.messages.MessageEmitter#endMessages() 172 */ 173 @Override 174 public void endMessages() throws SAXException { 175 try { 176 writer.flush(); 177 writer.close(); 178 } catch (IOException e) { 179 throw new SAXException(e.getLocalizedMessage(), e); 180 } 181 } 182 183 /** 184 * @see nu.validator.messages.MessageEmitter#endText() 185 */ 186 @Override 187 public void endText() throws SAXException { 188 try { 189 this.writer.write('\n'); 190 this.textEmitted = true; 191 } catch (IOException e) { 192 throw new SAXException(e.getLocalizedMessage(), e); 193 } 194 195 } 196 197 /** 198 * @see nu.validator.messages.MessageEmitter#startMessages(java.lang.String) 199 */ 200 @Override 201 public void startMessages(String documentUri, boolean willShowSource) throws SAXException { 202 } 203 204 /** 205 * @see nu.validator.messages.MessageEmitter#startText() 206 */ 207 @Override 208 public MessageTextHandler startText() throws SAXException { 209 try { 210 this.writer.write(COLON_SPACE); 211 return messageTextHandler; 212 } catch (IOException e) { 213 throw new SAXException(e.getLocalizedMessage(), e); 214 } 215 } 216 217 @Override 218 public void endMessage() throws SAXException { 219 try { 220 if (!textEmitted) { 221 writer.write(PERIOD); 222 writer.write('\n'); 223 } 224 maybeEmitLocation(); 225 writer.write('\n'); 226 } catch (IOException e) { 227 throw new SAXException(e.getLocalizedMessage(), e); 228 } 229 } 230 231 /** 232 * @see nu.validator.messages.MessageEmitter#startResult() 233 */ 234 @Override 235 public ResultHandler startResult() throws SAXException { 236 return new TextResultHandler(writer); 237 } 238 239 }