001    package biweekly.io.text;
002    
003    import java.io.IOException;
004    import java.io.Writer;
005    
006    /*
007     Copyright (c) 2013, Michael Angstadt
008     All rights reserved.
009    
010     Redistribution and use in source and binary forms, with or without
011     modification, are permitted provided that the following conditions are met: 
012    
013     1. Redistributions of source code must retain the above copyright notice, this
014     list of conditions and the following disclaimer. 
015     2. Redistributions in binary form must reproduce the above copyright notice,
016     this list of conditions and the following disclaimer in the documentation
017     and/or other materials provided with the distribution. 
018    
019     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
020     ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
021     WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
022     DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
023     ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
024     (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025     LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
026     ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027     (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
028     SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029     */
030    
031    /**
032     * Automatically folds lines as they are written.
033     * @author Michael Angstadt
034     */
035    public class FoldedLineWriter extends Writer {
036            private int curLineLength = 0;
037            private int lineLength;
038            private String indent;
039            private String newline;
040            private final Writer writer;
041    
042            /**
043             * @param writer the writer object to wrap
044             * @param lineLength the maximum length a line can be before it is folded
045             * (excluding the newline)
046             * @param indent the string to prepend to each folded line (e.g. a single
047             * space character)
048             * @param newline the newline sequence to use (e.g. "\r\n")
049             * @throws IllegalArgumentException if the line length is less than or equal
050             * to zero
051             * @throws IllegalArgumentException if the length of the indent string is
052             * greater than the max line length
053             */
054            public FoldedLineWriter(Writer writer, int lineLength, String indent, String newline) {
055                    setLineLength(lineLength);
056                    setIndent(indent);
057                    this.writer = writer;
058                    this.newline = newline;
059            }
060    
061            /**
062             * Writes a string of text, followed by a newline.
063             * @param str the text to write
064             * @throws IOException if there's a problem writing to the output stream
065             */
066            public void writeln(String str) throws IOException {
067                    write(str);
068                    write(newline);
069            }
070    
071            @Override
072            public void write(char buf[], int start, int end) throws IOException {
073                    write(buf, start, end, lineLength, indent);
074            }
075    
076            /**
077             * Writes a portion of an array of characters.
078             * @param buf the array of characters
079             * @param start the offset from which to start writing characters
080             * @param end the number of characters to write
081             * @param lineLength the maximum length a line can be before it is folded
082             * (excluding the newline)
083             * @param indent the indent string to use (e.g. a single space character)
084             * @throws IOException if there's a problem writing to the output stream
085             */
086            public void write(char buf[], int start, int end, int lineLength, String indent) throws IOException {
087                    for (int i = start; i < end; i++) {
088                            char c = buf[i];
089                            if (c == '\n') {
090                                    writer.write(buf, start, i - start + 1);
091                                    curLineLength = 0;
092                                    start = i + 1;
093                            } else if (c == '\r') {
094                                    if (i == end - 1 || buf[i + 1] != '\n') {
095                                            writer.write(buf, start, i - start + 1);
096                                            curLineLength = 0;
097                                            start = i + 1;
098                                    } else {
099                                            curLineLength++;
100                                    }
101                            } else if (curLineLength >= lineLength) {
102                                    //if the last characters on the line are whitespace, then exceed the max line length in order to include the whitespace on the same line
103                                    //otherwise it will be lost because it will merge with the padding on the next line
104                                    if (Character.isWhitespace(c)) {
105                                            while (Character.isWhitespace(c) && i < end - 1) {
106                                                    i++;
107                                                    c = buf[i];
108                                            }
109                                            if (i == end - 1) {
110                                                    //the rest of the char array is whitespace, so leave the loop
111                                                    break;
112                                            }
113                                    }
114    
115                                    writer.write(buf, start, i - start);
116                                    String s = newline + indent;
117                                    writer.write(s.toCharArray(), 0, s.length());
118                                    start = i;
119                                    curLineLength = indent.length() + 1;
120                            } else {
121                                    curLineLength++;
122                            }
123                    }
124                    writer.write(buf, start, end - start);
125            }
126    
127            @Override
128            public void close() throws IOException {
129                    writer.close();
130            }
131    
132            @Override
133            public void flush() throws IOException {
134                    writer.flush();
135            }
136    
137            /**
138             * Gets the maximum length a line can be before it is folded (excluding the
139             * newline).
140             * @return the line length
141             */
142            public int getLineLength() {
143                    return lineLength;
144            }
145    
146            /**
147             * Sets the maximum length a line can be before it is folded (excluding the
148             * newline).
149             * @param lineLength the line length
150             * @throws IllegalArgumentException if the line length is less than or equal
151             * to zero
152             */
153            public void setLineLength(int lineLength) {
154                    if (lineLength <= 0) {
155                            throw new IllegalArgumentException("Line length must be greater than 0.");
156                    }
157                    this.lineLength = lineLength;
158            }
159    
160            /**
161             * Gets the string that is prepended to each folded line.
162             * @return the indent string
163             */
164            public String getIndent() {
165                    return indent;
166            }
167    
168            /**
169             * Sets the string that is prepended to each folded line.
170             * @param indent the indent string (e.g. a single space character)
171             * @throws IllegalArgumentException if the length of the indent string is
172             * greater than the max line length
173             */
174            public void setIndent(String indent) {
175                    if (indent.length() >= lineLength) {
176                            throw new IllegalArgumentException("The length of the indent string must be less than the max line length.");
177                    }
178                    this.indent = indent;
179            }
180    
181            /**
182             * Gets the newline sequence that is used to separate lines.
183             * @return the newline sequence
184             */
185            public String getNewline() {
186                    return newline;
187            }
188    
189            /**
190             * Sets the newline sequence that is used to separate lines
191             * @param newline the newline sequence
192             */
193            public void setNewline(String newline) {
194                    this.newline = newline;
195            }
196    }