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 }