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    }