001package biweekly.io.text;
002
003import java.io.IOException;
004import 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 */
035public 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}