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 }