PageRenderTime 39ms CodeModel.GetById 12ms RepoModel.GetById 1ms app.codeStats 0ms

/src/main/java/biweekly/io/text/FoldedLineWriter.java

https://gitlab.com/benjamin.beau/biweekly
Java | 366 lines | 166 code | 37 blank | 163 comment | 40 complexity | 06c9b49d10205937951e6c335f7d7997 MD5 | raw file
  1. package biweekly.io.text;
  2. import java.io.IOException;
  3. import java.io.OutputStreamWriter;
  4. import java.io.Writer;
  5. import java.nio.charset.Charset;
  6. import biweekly.Messages;
  7. import biweekly.util.org.apache.commons.codec.EncoderException;
  8. import biweekly.util.org.apache.commons.codec.net.QuotedPrintableCodec;
  9. /*
  10. Copyright (c) 2013-2016, Michael Angstadt
  11. All rights reserved.
  12. Redistribution and use in source and binary forms, with or without
  13. modification, are permitted provided that the following conditions are met:
  14. 1. Redistributions of source code must retain the above copyright notice, this
  15. list of conditions and the following disclaimer.
  16. 2. Redistributions in binary form must reproduce the above copyright notice,
  17. this list of conditions and the following disclaimer in the documentation
  18. and/or other materials provided with the distribution.
  19. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  20. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  21. WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  22. DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
  23. ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  24. (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  25. LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  26. ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  27. (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  28. SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  29. */
  30. /**
  31. * Automatically folds lines as they are written.
  32. * @author Michael Angstadt
  33. */
  34. public class FoldedLineWriter extends Writer {
  35. private final Writer writer;
  36. private int curLineLength = 0;
  37. private Integer lineLength = 75;
  38. private String indent = " ";
  39. private String newline = "\r\n";
  40. /**
  41. * Creates a folded line writer.
  42. * @param writer the writer object to wrap
  43. */
  44. public FoldedLineWriter(Writer writer) {
  45. this.writer = writer;
  46. }
  47. /**
  48. * Writes a string, followed by a newline.
  49. * @param str the text to write
  50. * @throws IOException if there's a problem writing to the output stream
  51. */
  52. public void writeln(String str) throws IOException {
  53. write(str);
  54. write(newline);
  55. }
  56. /**
  57. * Writes a string.
  58. * @param str the string to write
  59. * @param quotedPrintable true to encode the string in quoted-printable
  60. * encoding, false not to
  61. * @param charset the character set to use when encoding into
  62. * quoted-printable, or null to use the writer's character encoding (only
  63. * applicable if "quotedPrintable" is set to true)
  64. * @return this
  65. * @throws IOException if there's a problem writing to the output stream
  66. */
  67. public FoldedLineWriter append(CharSequence str, boolean quotedPrintable, Charset charset) throws IOException {
  68. write(str, quotedPrintable, charset);
  69. return this;
  70. }
  71. /**
  72. * Writes a string.
  73. * @param str the string to write
  74. * @param quotedPrintable true to encode the string in quoted-printable
  75. * encoding, false not to
  76. * @param charset the character set to use when encoding into
  77. * quoted-printable, or null to use the writer's character encoding (only
  78. * applicable if "quotedPrintable" is set to true)
  79. * @throws IOException if there's a problem writing to the output stream
  80. */
  81. public void write(CharSequence str, boolean quotedPrintable, Charset charset) throws IOException {
  82. write(str.toString().toCharArray(), 0, str.length(), quotedPrintable, charset);
  83. }
  84. @Override
  85. public void write(char[] cbuf, int off, int len) throws IOException {
  86. write(cbuf, off, len, false, null);
  87. }
  88. /**
  89. * Writes a portion of an array of characters.
  90. * @param cbuf the array of characters
  91. * @param off the offset from which to start writing characters
  92. * @param len the number of characters to write
  93. * @param quotedPrintable true to encode the string in quoted-printable
  94. * encoding, false not to
  95. * @param charset the character set to use when encoding into
  96. * quoted-printable, or null to use the writer's character encoding (only
  97. * applicable if "quotedPrintable" is set to true)
  98. * @throws IOException if there's a problem writing to the output stream
  99. */
  100. public void write(char[] cbuf, int off, int len, boolean quotedPrintable, Charset charset) throws IOException {
  101. if (quotedPrintable) {
  102. if (charset == null) {
  103. charset = Charset.forName("UTF-8");
  104. }
  105. QuotedPrintableCodec codec = new QuotedPrintableCodec(charset.name());
  106. try {
  107. String str = new String(cbuf, off, len);
  108. String encoded = codec.encode(str);
  109. cbuf = encoded.toCharArray();
  110. off = 0;
  111. len = cbuf.length;
  112. } catch (EncoderException e) {
  113. /*
  114. * Thrown if an unsupported charset is passed into the codec.
  115. * This should never be thrown though, because we already know
  116. * the charset is valid (a Charset object is passed into the
  117. * method).
  118. */
  119. throw new RuntimeException(e);
  120. }
  121. }
  122. if (lineLength == null) {
  123. /*
  124. * If line folding is disabled, then write directly to the Writer.
  125. */
  126. writer.write(cbuf, off, len);
  127. return;
  128. }
  129. int effectiveLineLength = lineLength;
  130. if (quotedPrintable) {
  131. /*
  132. * Account for the "=" character that must be appended onto each
  133. * line.
  134. */
  135. effectiveLineLength -= 1;
  136. }
  137. int encodedCharPos = -1;
  138. int start = off;
  139. int end = off + len;
  140. for (int i = start; i < end; i++) {
  141. char c = cbuf[i];
  142. /*
  143. * Keep track of the quoted-printable characters to prevent them
  144. * from being cut in two at a folding boundary.
  145. */
  146. if (encodedCharPos >= 0) {
  147. encodedCharPos++;
  148. if (encodedCharPos == 3) {
  149. encodedCharPos = -1;
  150. }
  151. }
  152. if (c == '\n') {
  153. writer.write(cbuf, start, i - start + 1);
  154. curLineLength = 0;
  155. start = i + 1;
  156. continue;
  157. }
  158. if (c == '\r') {
  159. if (i == end - 1 || cbuf[i + 1] != '\n') {
  160. writer.write(cbuf, start, i - start + 1);
  161. curLineLength = 0;
  162. start = i + 1;
  163. } else {
  164. curLineLength++;
  165. }
  166. continue;
  167. }
  168. if (c == '=' && quotedPrintable) {
  169. encodedCharPos = 0;
  170. }
  171. if (curLineLength >= effectiveLineLength) {
  172. /*
  173. * If the last characters on the line are whitespace, then
  174. * exceed the max line length in order to include the whitespace
  175. * on the same line. Otherwise, the whitespace will be lost
  176. * because it will merge with the padding on the next, folded
  177. * line.
  178. */
  179. if (Character.isWhitespace(c)) {
  180. while (Character.isWhitespace(c) && i < end - 1) {
  181. i++;
  182. c = cbuf[i];
  183. }
  184. if (i >= end - 1) {
  185. /*
  186. * The rest of the char array is whitespace, so leave
  187. * the loop.
  188. */
  189. break;
  190. }
  191. }
  192. /*
  193. * If we are in the middle of a quoted-printable encoded
  194. * character, then exceed the max line length so the sequence
  195. * doesn't get split up across multiple lines.
  196. */
  197. if (encodedCharPos > 0) {
  198. i += 3 - encodedCharPos;
  199. if (i >= end - 1) {
  200. /*
  201. * The rest of the char array was a quoted-printable
  202. * encoded char, so leave the loop.
  203. */
  204. break;
  205. }
  206. }
  207. /*
  208. * If the last char is the low (second) char in a surrogate
  209. * pair, don't split the pair across two lines.
  210. */
  211. if (Character.isLowSurrogate(c)) {
  212. i++;
  213. if (i >= end - 1) {
  214. /*
  215. * Surrogate pair finishes the char array, so leave the
  216. * loop.
  217. */
  218. break;
  219. }
  220. }
  221. writer.write(cbuf, start, i - start);
  222. if (quotedPrintable) {
  223. writer.write('=');
  224. }
  225. writer.write(newline);
  226. writer.write(indent);
  227. curLineLength = indent.length() + 1;
  228. start = i;
  229. continue;
  230. }
  231. curLineLength++;
  232. }
  233. writer.write(cbuf, start, end - start);
  234. }
  235. /**
  236. * Closes the writer.
  237. */
  238. @Override
  239. public void close() throws IOException {
  240. writer.close();
  241. }
  242. /**
  243. * Flushes the writer.
  244. */
  245. @Override
  246. public void flush() throws IOException {
  247. writer.flush();
  248. }
  249. /**
  250. * Gets the maximum length a line can be before it is folded (excluding the
  251. * newline, defaults to 75).
  252. * @return the line length or null if folding is disabled
  253. */
  254. public Integer getLineLength() {
  255. return lineLength;
  256. }
  257. /**
  258. * Sets the maximum length a line can be before it is folded (excluding the
  259. * newline, defaults to 75).
  260. * @param lineLength the line length or null to disable folding
  261. * @throws IllegalArgumentException if the line length is less than or equal
  262. * to zero
  263. */
  264. public void setLineLength(Integer lineLength) {
  265. if (lineLength != null && lineLength <= 0) {
  266. throw Messages.INSTANCE.getIllegalArgumentException(3);
  267. }
  268. this.lineLength = lineLength;
  269. }
  270. /**
  271. * Gets the string that is prepended to each folded line (defaults to a
  272. * single space character).
  273. * @return the indent string
  274. */
  275. public String getIndent() {
  276. return indent;
  277. }
  278. /**
  279. * Sets the string that is prepended to each folded line (defaults to a
  280. * single space character).
  281. * @param indent the indent string
  282. * @throws IllegalArgumentException if the length of the indent string is
  283. * greater than the max line length
  284. */
  285. public void setIndent(String indent) {
  286. if (lineLength != null && indent.length() >= lineLength) {
  287. throw Messages.INSTANCE.getIllegalArgumentException(5);
  288. }
  289. this.indent = indent;
  290. }
  291. /**
  292. * Gets the newline sequence that is used to separate lines (defaults to
  293. * CRLF).
  294. * @return the newline sequence
  295. */
  296. public String getNewline() {
  297. return newline;
  298. }
  299. /**
  300. * Sets the newline sequence that is used to separate lines (defaults to
  301. * CRLF).
  302. * @param newline the newline sequence
  303. */
  304. public void setNewline(String newline) {
  305. this.newline = newline;
  306. }
  307. /**
  308. * Gets the wrapped {@link Writer} object.
  309. * @return the wrapped writer
  310. */
  311. public Writer getWriter() {
  312. return writer;
  313. }
  314. /**
  315. * Gets the writer's character encoding.
  316. * @return the writer's character encoding or null if undefined
  317. */
  318. public Charset getEncoding() {
  319. if (!(writer instanceof OutputStreamWriter)) {
  320. return null;
  321. }
  322. OutputStreamWriter osw = (OutputStreamWriter) writer;
  323. String charsetStr = osw.getEncoding();
  324. return (charsetStr == null) ? null : Charset.forName(charsetStr);
  325. }
  326. }