|
1 /** |
|
2 * Copyright (c) 2012, Ben Fortuna |
|
3 * All rights reserved. |
|
4 * |
|
5 * Redistribution and use in source and binary forms, with or without |
|
6 * modification, are permitted provided that the following conditions |
|
7 * are met: |
|
8 * |
|
9 * o Redistributions of source code must retain the above copyright |
|
10 * notice, this list of conditions and the following disclaimer. |
|
11 * |
|
12 * o Redistributions in binary form must reproduce the above copyright |
|
13 * notice, this list of conditions and the following disclaimer in the |
|
14 * documentation and/or other materials provided with the distribution. |
|
15 * |
|
16 * o Neither the name of Ben Fortuna nor the names of any other contributors |
|
17 * may be used to endorse or promote products derived from this software |
|
18 * without specific prior written permission. |
|
19 * |
|
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
|
24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
|
25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
|
26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
|
27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
|
28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
|
29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
|
30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
31 */ |
|
32 package net.fortuna.ical4j.data; |
|
33 |
|
34 import java.io.IOException; |
|
35 import java.io.PushbackReader; |
|
36 import java.io.Reader; |
|
37 import java.util.Arrays; |
|
38 |
|
39 import net.fortuna.ical4j.util.CompatibilityHints; |
|
40 |
|
41 import org.apache.commons.logging.Log; |
|
42 import org.apache.commons.logging.LogFactory; |
|
43 |
|
44 /** |
|
45 * <pre> |
|
46 * $Id$ [06-Apr-2004] |
|
47 * </pre> |
|
48 * |
|
49 * A reader which performs iCalendar unfolding as it reads. Note that unfolding rules may be "relaxed" to allow |
|
50 * unfolding of non-conformant *.ics files. By specifying the system property "ical4j.unfolding.relaxed=true" iCalendar |
|
51 * files created with Mozilla Calendar/Sunbird may be correctly unfolded. |
|
52 * |
|
53 * To wrap this reader with a {@link java.io.BufferedReader} you must ensure you specify an identical buffer size |
|
54 * to that used in the {@link java.io.BufferedReader}. |
|
55 * |
|
56 * @author Ben Fortuna |
|
57 */ |
|
58 public class UnfoldingReader extends PushbackReader { |
|
59 |
|
60 private Log log = LogFactory.getLog(UnfoldingReader.class); |
|
61 |
|
62 /** |
|
63 * The pattern used to identify a fold in an iCalendar data stream. |
|
64 */ |
|
65 private static final char[] DEFAULT_FOLD_PATTERN_1 = { '\r', '\n', ' ' }; |
|
66 |
|
67 /** |
|
68 * The pattern used to identify a fold in Microsoft Outlook 2007. |
|
69 */ |
|
70 private static final char[] DEFAULT_FOLD_PATTERN_2 = { '\r', '\n', '\t' }; |
|
71 |
|
72 /** |
|
73 * The pattern used to identify a fold in Mozilla Calendar/Sunbird and KOrganizer. |
|
74 */ |
|
75 private static final char[] RELAXED_FOLD_PATTERN_1 = { '\n', ' ' }; |
|
76 |
|
77 /** |
|
78 * The pattern used to identify a fold in Microsoft Outlook 2007. |
|
79 */ |
|
80 private static final char[] RELAXED_FOLD_PATTERN_2 = { '\n', '\t' }; |
|
81 |
|
82 private char[][] patterns; |
|
83 |
|
84 private char[][] buffers; |
|
85 |
|
86 private int linesUnfolded; |
|
87 |
|
88 private int maxPatternLength = 0; |
|
89 |
|
90 /** |
|
91 * Creates a new unfolding reader instance. Relaxed unfolding flag is read from system property. |
|
92 * @param in the reader to unfold from |
|
93 */ |
|
94 public UnfoldingReader(final Reader in) { |
|
95 this(in, DEFAULT_FOLD_PATTERN_1.length, CompatibilityHints |
|
96 .isHintEnabled(CompatibilityHints.KEY_RELAXED_UNFOLDING)); |
|
97 } |
|
98 |
|
99 /** |
|
100 * @param in reader source for data |
|
101 * @param size the buffer size |
|
102 */ |
|
103 public UnfoldingReader(final Reader in, int size) { |
|
104 this(in, size, CompatibilityHints.isHintEnabled(CompatibilityHints.KEY_RELAXED_UNFOLDING)); |
|
105 } |
|
106 |
|
107 /** |
|
108 * @param in reader source for data |
|
109 * @param relaxed indicates whether relaxed unfolding is enabled |
|
110 */ |
|
111 public UnfoldingReader(final Reader in, boolean relaxed) { |
|
112 this(in, DEFAULT_FOLD_PATTERN_1.length, relaxed); |
|
113 } |
|
114 |
|
115 /** |
|
116 * Creates a new unfolding reader instance. |
|
117 * @param in a reader to read from |
|
118 * @param size the buffer size |
|
119 * @param relaxed specifies whether unfolding is relaxed |
|
120 */ |
|
121 public UnfoldingReader(final Reader in, int size, final boolean relaxed) { |
|
122 super(in, size); |
|
123 if (relaxed) { |
|
124 patterns = new char[4][]; |
|
125 patterns[0] = DEFAULT_FOLD_PATTERN_1; |
|
126 patterns[1] = DEFAULT_FOLD_PATTERN_2; |
|
127 patterns[2] = RELAXED_FOLD_PATTERN_1; |
|
128 patterns[3] = RELAXED_FOLD_PATTERN_2; |
|
129 } |
|
130 else { |
|
131 patterns = new char[2][]; |
|
132 patterns[0] = DEFAULT_FOLD_PATTERN_1; |
|
133 patterns[1] = DEFAULT_FOLD_PATTERN_2; |
|
134 } |
|
135 buffers = new char[patterns.length][]; |
|
136 for (int i = 0; i < patterns.length; i++) { |
|
137 buffers[i] = new char[patterns[i].length]; |
|
138 maxPatternLength = Math.max(maxPatternLength, patterns[i].length); |
|
139 } |
|
140 } |
|
141 |
|
142 /** |
|
143 * @return number of lines unfolded so far while reading |
|
144 */ |
|
145 public final int getLinesUnfolded() { |
|
146 return linesUnfolded; |
|
147 } |
|
148 |
|
149 /** |
|
150 * {@inheritDoc} |
|
151 */ |
|
152 public final int read() throws IOException { |
|
153 final int c = super.read(); |
|
154 boolean doUnfold = false; |
|
155 for (int i = 0; i < patterns.length; i++) { |
|
156 if (c == patterns[i][0]) { |
|
157 doUnfold = true; |
|
158 break; |
|
159 } |
|
160 } |
|
161 if (!doUnfold) { |
|
162 return c; |
|
163 } |
|
164 else { |
|
165 unread(c); |
|
166 } |
|
167 |
|
168 unfold(); |
|
169 |
|
170 return super.read(); |
|
171 } |
|
172 |
|
173 /** |
|
174 * {@inheritDoc} |
|
175 */ |
|
176 public int read(final char[] cbuf, final int off, final int len) throws IOException { |
|
177 final int read = super.read(cbuf, off, len); |
|
178 boolean doUnfold = false; |
|
179 for (int i = 0; i < patterns.length; i++) { |
|
180 if (read > 0 && cbuf[0] == patterns[i][0]) { |
|
181 doUnfold = true; |
|
182 break; |
|
183 } |
|
184 else { |
|
185 for (int j = 0; j < read; j++) { |
|
186 if (cbuf[j] == patterns[i][0]) { |
|
187 unread(cbuf, j, read - j); |
|
188 return j; |
|
189 } |
|
190 } |
|
191 } |
|
192 } |
|
193 if (!doUnfold) { |
|
194 return read; |
|
195 } |
|
196 else { |
|
197 unread(cbuf, off, read); |
|
198 } |
|
199 |
|
200 unfold(); |
|
201 |
|
202 return super.read(cbuf, off, maxPatternLength); |
|
203 } |
|
204 |
|
205 private void unfold() throws IOException { |
|
206 // need to loop since one line fold might be directly followed by another |
|
207 boolean didUnfold; |
|
208 do { |
|
209 didUnfold = false; |
|
210 |
|
211 for (int i = 0; i < buffers.length; i++) { |
|
212 int read = 0; |
|
213 while (read < buffers[i].length) { |
|
214 final int partialRead = super.read(buffers[i], read, buffers[i].length - read); |
|
215 if (partialRead < 0) { |
|
216 break; |
|
217 } |
|
218 read += partialRead; |
|
219 } |
|
220 if (read > 0) { |
|
221 if (!Arrays.equals(patterns[i], buffers[i])) { |
|
222 unread(buffers[i], 0, read); |
|
223 } |
|
224 else { |
|
225 if (log.isTraceEnabled()) { |
|
226 log.trace("Unfolding..."); |
|
227 } |
|
228 linesUnfolded++; |
|
229 didUnfold = true; |
|
230 } |
|
231 } |
|
232 // else { |
|
233 // return read; |
|
234 // } |
|
235 } |
|
236 } |
|
237 while (didUnfold); |
|
238 } |
|
239 } |