1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package net.fortuna.ical4j.data;
35
36 import java.io.IOException;
37 import java.io.InputStream;
38 import java.io.InputStreamReader;
39 import java.io.Reader;
40 import java.io.StreamTokenizer;
41 import java.net.URISyntaxException;
42 import java.text.ParseException;
43
44 import net.fortuna.ical4j.model.Calendar;
45 import net.fortuna.ical4j.model.Component;
46 import net.fortuna.ical4j.model.ComponentFactory;
47 import net.fortuna.ical4j.model.ComponentList;
48 import net.fortuna.ical4j.model.Parameter;
49 import net.fortuna.ical4j.model.ParameterFactory;
50 import net.fortuna.ical4j.model.ParameterList;
51 import net.fortuna.ical4j.model.Property;
52 import net.fortuna.ical4j.model.PropertyFactory;
53 import net.fortuna.ical4j.model.PropertyList;
54
55 import org.apache.commons.logging.Log;
56 import org.apache.commons.logging.LogFactory;
57
58 /***
59 * Parses and builds an iCalendar model from an input stream.
60 *
61 * @author benf
62 */
63 public class CalendarBuilder {
64
65 private static final int WORD_CHAR_START = 32;
66
67 private static final int WORD_CHAR_END = 126;
68
69 private static final int WHITESPACE_CHAR_START = 0;
70
71 private static final int WHITESPACE_CHAR_END = 20;
72
73 private static Log log = LogFactory.getLog(CalendarBuilder.class);
74
75 /***
76 * Builds an iCalendar model from the specified input stream.
77 *
78 * @param in
79 * @return a calendar
80 * @throws IOException
81 * @throws ParseException
82 * @throws URISyntaxException
83 * @throws BuilderException
84 */
85 public final Calendar build(final InputStream in) throws IOException,
86 BuilderException {
87
88 return build(new InputStreamReader(in));
89 }
90
91 /***
92 * Builds an iCalendar model from the specified reader.
93 * An <code>UnfoldingReader</code> is applied to the specified
94 * reader to ensure the data stream is correctly unfolded where
95 * appropriate.
96 *
97 * @param in
98 * @return a calendar
99 * @throws IOException
100 * @throws ParseException
101 * @throws URISyntaxException
102 * @throws BuilderException
103 */
104 public final Calendar build(final Reader in) throws IOException,
105 BuilderException {
106
107 try {
108 UnfoldingReader uin = new UnfoldingReader(in);
109
110 StreamTokenizer tokeniser = new StreamTokenizer(uin);
111 tokeniser.resetSyntax();
112 tokeniser.wordChars(WORD_CHAR_START, WORD_CHAR_END);
113 tokeniser.whitespaceChars(WHITESPACE_CHAR_START,
114 WHITESPACE_CHAR_END);
115 tokeniser.ordinaryChar(':');
116 tokeniser.ordinaryChar(';');
117 tokeniser.ordinaryChar('=');
118 tokeniser.eolIsSignificant(true);
119 tokeniser.whitespaceChars(0, 0);
120 tokeniser.quoteChar('"');
121
122 PropertyList properties = null;
123 ComponentList components = null;
124
125
126 assertToken(tokeniser, Calendar.BEGIN);
127
128 assertToken(tokeniser, ':');
129
130 assertToken(tokeniser, Calendar.VCALENDAR);
131
132 assertToken(tokeniser, StreamTokenizer.TT_EOL);
133
134
135 properties = buildPropertyList(tokeniser);
136
137
138 components = buildComponentList(tokeniser);
139
140
141
142
143 assertToken(tokeniser, ':');
144
145 assertToken(tokeniser, Calendar.VCALENDAR);
146
147
148 return new Calendar(properties, components);
149 }
150 catch (Exception e) {
151
152 if (e instanceof IOException) { throw (IOException) e; }
153 if (e instanceof BuilderException) {
154 throw (BuilderException) e;
155 }
156 else {
157 throw new BuilderException("An error ocurred during parsing", e);
158 }
159 }
160 }
161
162 /***
163 * Builds am iCalendar property list from the specified stream tokeniser.
164 *
165 * @param tokeniser
166 * @return a property list
167 * @throws IOException
168 * @throws ParseException
169 * @throws URISyntaxException
170 * @throws URISyntaxException
171 * @throws BuilderException
172 */
173 private PropertyList buildPropertyList(final StreamTokenizer tokeniser)
174 throws IOException, ParseException, URISyntaxException,
175 BuilderException {
176
177 PropertyList list = new PropertyList();
178
179 assertToken(tokeniser, StreamTokenizer.TT_WORD);
180
181 while (!Component.BEGIN.equals(tokeniser.sval)
182 && !Component.END.equals(tokeniser.sval)) {
183
184 list.add(buildProperty(tokeniser));
185
186 assertToken(tokeniser, StreamTokenizer.TT_WORD);
187 }
188
189 return list;
190 }
191
192 /***
193 * Builds an iCalendar property from the specified stream tokeniser.
194 *
195 * @param tokeniser
196 * @return a property
197 * @throws IOException
198 * @throws BuilderException
199 * @throws URISyntaxException
200 * @throws ParseException
201 */
202 private Property buildProperty(final StreamTokenizer tokeniser)
203 throws IOException, BuilderException, URISyntaxException,
204 ParseException {
205
206 String name = tokeniser.sval;
207
208
209 log.debug("Property [" + name + "]");
210
211 ParameterList parameters = buildParameterList(tokeniser);
212
213
214
215
216
217
218
219
220 StringBuffer value = new StringBuffer();
221
222
223
224 while (tokeniser.nextToken() != StreamTokenizer.TT_EOL) {
225
226 if (tokeniser.ttype == StreamTokenizer.TT_WORD) {
227 value.append(tokeniser.sval);
228 }
229 else {
230 value.append((char) tokeniser.ttype);
231 }
232 }
233
234 return PropertyFactory.getInstance().createProperty(name, parameters,
235 value.toString());
236 }
237
238 /***
239 * Build a list of iCalendar parameters by parsing the specified stream
240 * tokeniser.
241 *
242 * @param tokeniser
243 * @return @throws
244 * IOException
245 * @throws BuilderException
246 * @throws URISyntaxException
247 * @throws ParseException
248 */
249 private ParameterList buildParameterList(final StreamTokenizer tokeniser)
250 throws IOException, BuilderException, URISyntaxException,
251 ParseException {
252
253 ParameterList parameters = new ParameterList();
254
255 while (tokeniser.nextToken() == ';') {
256 parameters.add(buildParameter(tokeniser));
257 }
258
259 return parameters;
260 }
261
262 private Parameter buildParameter(final StreamTokenizer tokeniser)
263 throws IOException, BuilderException, URISyntaxException,
264 ParseException {
265
266 assertToken(tokeniser, StreamTokenizer.TT_WORD);
267
268 String paramName = tokeniser.sval;
269
270
271 log.debug("Parameter [" + paramName + "]");
272
273 assertToken(tokeniser, '=');
274
275 StringBuffer paramValue = new StringBuffer();
276
277
278 if (tokeniser.nextToken() == '"') {
279 paramValue.append('"');
280 paramValue.append(tokeniser.sval);
281 paramValue.append('"');
282 }
283 else {
284 paramValue.append(tokeniser.sval);
285 }
286
287 return ParameterFactory.getInstance().createParameter(paramName,
288 paramValue.toString());
289 }
290
291 /***
292 * Builds an iCalendar component list from the specified stream tokeniser.
293 *
294 * @param tokeniser
295 * @return a component list
296 * @throws IOException
297 * @throws ParseException
298 * @throws URISyntaxException
299 * @throws BuilderException
300 */
301 private ComponentList buildComponentList(final StreamTokenizer tokeniser)
302 throws IOException, ParseException, URISyntaxException,
303 BuilderException {
304
305 ComponentList list = new ComponentList();
306
307 while (Component.BEGIN.equals(tokeniser.sval)) {
308
309 list.add(buildComponent(tokeniser));
310
311 assertToken(tokeniser, StreamTokenizer.TT_WORD);
312 }
313
314 return list;
315 }
316
317 /***
318 * Builds an iCalendar component from the specified stream tokeniser.
319 *
320 * @param tokeniser
321 * @return a component
322 * @throws IOException
323 * @throws ParseException
324 * @throws URISyntaxException
325 * @throws BuilderException
326 */
327 private Component buildComponent(final StreamTokenizer tokeniser)
328 throws IOException, ParseException, URISyntaxException,
329 BuilderException {
330
331 assertToken(tokeniser, ':');
332
333 assertToken(tokeniser, StreamTokenizer.TT_WORD);
334
335 String name = tokeniser.sval;
336
337 assertToken(tokeniser, StreamTokenizer.TT_EOL);
338
339 PropertyList properties = buildPropertyList(tokeniser);
340
341 ComponentList subComponents = null;
342
343
344
345 if (Component.VTIMEZONE.equals(name)) {
346
347 subComponents = buildComponentList(tokeniser);
348 }
349
350
351 else if (Component.VEVENT.equals(name)
352 && Component.BEGIN.equals(tokeniser.sval)) {
353
354 subComponents = buildComponentList(tokeniser);
355 }
356
357 assertToken(tokeniser, ':');
358
359 assertToken(tokeniser, name);
360
361 assertToken(tokeniser, StreamTokenizer.TT_EOL);
362
363 return ComponentFactory.getInstance().createComponent(name, properties,
364 subComponents);
365 }
366
367 /***
368 * Asserts that the next token in the stream matches the specified token.
369 *
370 * @param tokeniser
371 * stream tokeniser to perform assertion on
372 * @param token
373 * expected token
374 * @throws IOException
375 * when unable to read from stream
376 * @throws ParseException
377 * when next token in the stream does not match the expected
378 * token
379 */
380 private void assertToken(final StreamTokenizer tokeniser, final int token)
381 throws IOException, BuilderException, ParseException {
382
383 if (tokeniser.nextToken() != token) {
384
385 throw new BuilderException("Expected [" + token + "], read ["
386 + tokeniser.ttype + "] at line " + tokeniser.lineno()); }
387
388 log.debug("[" + token + "]");
389 }
390
391 /***
392 * Asserts that the next token in the stream matches the specified token.
393 *
394 * @param tokeniser
395 * stream tokeniser to perform assertion on
396 * @param token
397 * expected token
398 * @throws IOException
399 * when unable to read from stream
400 * @throws ParseException
401 * when next token in the stream does not match the expected
402 * token
403 */
404 private void assertToken(final StreamTokenizer tokeniser, final String token)
405 throws IOException, ParseException, BuilderException {
406
407
408 assertToken(tokeniser, StreamTokenizer.TT_WORD);
409
410 if (!token.equals(tokeniser.sval)) {
411
412 throw new BuilderException("Expected [" + token + "], read ["
413 + tokeniser.sval + "] at line " + tokeniser.lineno()); }
414
415 log.debug("[" + token + "]");
416 }
417 }