001    package ezvcard.io;
002    
003    import java.io.IOException;
004    import java.io.Writer;
005    
006    /*
007     Copyright (c) 2012, 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     The views and conclusions contained in the software and documentation are those
031     of the authors and should not be interpreted as representing official policies, 
032     either expressed or implied, of the FreeBSD Project.
033     */
034    
035    /**
036     * Automatically folds lines as they are written.
037     * @author Michael Angstadt
038     */
039    public class FoldedLineWriter extends Writer {
040            private int curLineLength = 0;
041            private int lineLength;
042            private String indent;
043            private String newline;
044            private final Writer writer;
045    
046            /**
047             * @param writer the writer object to wrap
048             * @param lineLength the maximum length a line can be before it is folded
049             * (excluding the newline)
050             * @param indent the string to prepend to each folded line (e.g. a single
051             * space character)
052             * @param newline the newline sequence to use (e.g. "\r\n")
053             * @throws IllegalArgumentException if the line length is less than or equal
054             * to zero
055             * @throws IllegalArgumentException if the length of the indent string is
056             * greater than the max line length
057             */
058            public FoldedLineWriter(Writer writer, int lineLength, String indent, String newline) {
059                    setLineLength(lineLength);
060                    setIndent(indent);
061                    this.writer = writer;
062                    this.newline = newline;
063            }
064    
065            /**
066             * Writes a string of text, followed by a newline.
067             * @param str the text to write
068             * @throws IOException if there's a problem writing to the output stream
069             */
070            public void writeln(String str) throws IOException {
071                    write(str);
072                    write(newline);
073            }
074    
075            @Override
076            public void write(char buf[], int start, int end) throws IOException {
077                    write(buf, start, end, lineLength, indent);
078            }
079    
080            /**
081             * Writes a portion of an array of characters.
082             * @param buf the array of characters
083             * @param start the offset from which to start writing characters
084             * @param end the number of characters to write
085             * @param lineLength the maximum length a line can be before it is folded
086             * (excluding the newline)
087             * @param indent the indent string to use (e.g. a single space character)
088             * @throws IOException if there's a problem writing to the output stream
089             */
090            public void write(char buf[], int start, int end, int lineLength, String indent) throws IOException {
091                    for (int i = start; i < end; i++) {
092                            char c = buf[i];
093                            if (c == '\n') {
094                                    writer.write(buf, start, i - start + 1);
095                                    curLineLength = 0;
096                                    start = i + 1;
097                            } else if (c == '\r') {
098                                    if (i == end - 1 || buf[i + 1] != '\n') {
099                                            writer.write(buf, start, i - start + 1);
100                                            curLineLength = 0;
101                                            start = i + 1;
102                                    } else {
103                                            curLineLength++;
104                                    }
105                            } else if (curLineLength >= lineLength) {
106                                    //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
107                                    //otherwise it will be lost because it will merge with the padding on the next line
108                                    if (Character.isWhitespace(c)) {
109                                            while (Character.isWhitespace(c) && i < end - 1) {
110                                                    i++;
111                                                    c = buf[i];
112                                            }
113                                            if (i == end - 1) {
114                                                    //the rest of the char array is whitespace, so leave the loop
115                                                    break;
116                                            }
117                                    }
118    
119                                    writer.write(buf, start, i - start);
120                                    String s = newline + indent;
121                                    writer.write(s.toCharArray(), 0, s.length());
122                                    start = i;
123                                    curLineLength = indent.length() + 1;
124                            } else {
125                                    curLineLength++;
126                            }
127                    }
128                    writer.write(buf, start, end - start);
129            }
130    
131            @Override
132            public void close() throws IOException {
133                    writer.close();
134            }
135    
136            @Override
137            public void flush() throws IOException {
138                    writer.flush();
139            }
140    
141            /**
142             * Gets the maximum length a line can be before it is folded (excluding the
143             * newline).
144             * @return the line length
145             */
146            public int getLineLength() {
147                    return lineLength;
148            }
149    
150            /**
151             * Sets the maximum length a line can be before it is folded (excluding the
152             * newline).
153             * @param lineLength the line length
154             * @throws IllegalArgumentException if the line length is less than or equal
155             * to zero
156             */
157            public void setLineLength(int lineLength) {
158                    if (lineLength <= 0) {
159                            throw new IllegalArgumentException("Line length must be greater than 0.");
160                    }
161                    this.lineLength = lineLength;
162            }
163    
164            /**
165             * Gets the string that is prepended to each folded line.
166             * @return the indent string
167             */
168            public String getIndent() {
169                    return indent;
170            }
171    
172            /**
173             * Sets the string that is prepended to each folded line.
174             * @param indent the indent string (e.g. a single space character)
175             * @throws IllegalArgumentException if the length of the indent string is
176             * greater than the max line length
177             */
178            public void setIndent(String indent) {
179                    if (indent.length() >= lineLength) {
180                            throw new IllegalArgumentException("The length of the indent string must be less than the max line length.");
181                    }
182                    this.indent = indent;
183            }
184    
185            /**
186             * Gets the newline sequence that is used to separate lines.
187             * @return the newline sequence
188             */
189            public String getNewline() {
190                    return newline;
191            }
192    
193            /**
194             * Sets the newline sequence that is used to separate lines
195             * @param newline the newline sequence
196             */
197            public void setNewline(String newline) {
198                    this.newline = newline;
199            }
200    }