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 }