src/net/fortuna/ical4j/data/CalendarParserImpl.java

Tue, 10 Feb 2015 18:12:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 10 Feb 2015 18:12:00 +0100
changeset 0
fb9019fb1bf7
child 3
73bdfa70b04e
permissions
-rw-r--r--

Import initial revisions of existing project AndroidCaldavSyncAdapater,
forked from upstream repository at 27e8a0f8495c92e0780d450bdf0c7cec77a03a55.

michael@0 1 /**
michael@0 2 * Copyright (c) 2012, Ben Fortuna
michael@0 3 * All rights reserved.
michael@0 4 *
michael@0 5 * Redistribution and use in source and binary forms, with or without
michael@0 6 * modification, are permitted provided that the following conditions
michael@0 7 * are met:
michael@0 8 *
michael@0 9 * o Redistributions of source code must retain the above copyright
michael@0 10 * notice, this list of conditions and the following disclaimer.
michael@0 11 *
michael@0 12 * o Redistributions in binary form must reproduce the above copyright
michael@0 13 * notice, this list of conditions and the following disclaimer in the
michael@0 14 * documentation and/or other materials provided with the distribution.
michael@0 15 *
michael@0 16 * o Neither the name of Ben Fortuna nor the names of any other contributors
michael@0 17 * may be used to endorse or promote products derived from this software
michael@0 18 * without specific prior written permission.
michael@0 19 *
michael@0 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
michael@0 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
michael@0 22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
michael@0 23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
michael@0 24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
michael@0 25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
michael@0 26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
michael@0 27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
michael@0 28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
michael@0 29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
michael@0 30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
michael@0 31 */
michael@0 32 package net.fortuna.ical4j.data;
michael@0 33
michael@0 34 import java.io.IOException;
michael@0 35 import java.io.InputStream;
michael@0 36 import java.io.InputStreamReader;
michael@0 37 import java.io.Reader;
michael@0 38 import java.io.StreamTokenizer;
michael@0 39 import java.net.URISyntaxException;
michael@0 40 import java.text.MessageFormat;
michael@0 41 import java.text.ParseException;
michael@0 42
michael@0 43 import net.fortuna.ical4j.model.Calendar;
michael@0 44 import net.fortuna.ical4j.model.Component;
michael@0 45
michael@0 46 import org.apache.commons.logging.Log;
michael@0 47 import org.apache.commons.logging.LogFactory;
michael@0 48
michael@0 49 /**
michael@0 50 * <pre>
michael@0 51 * $Id$
michael@0 52 *
michael@0 53 * Created [Nov 5, 2004]
michael@0 54 * </pre>
michael@0 55 *
michael@0 56 * The default implementation of a calendar parser.
michael@0 57 * @author Ben Fortuna
michael@0 58 */
michael@0 59 public class CalendarParserImpl implements CalendarParser {
michael@0 60
michael@0 61 private static final int WORD_CHAR_START = 32;
michael@0 62
michael@0 63 private static final int WORD_CHAR_END = 255;
michael@0 64
michael@0 65 private static final int WHITESPACE_CHAR_START = 0;
michael@0 66
michael@0 67 private static final int WHITESPACE_CHAR_END = 20;
michael@0 68
michael@0 69 private static final String UNEXPECTED_TOKEN_MESSAGE = "Expected [{0}], read [{1}]";
michael@0 70
michael@0 71 private Log log = LogFactory.getLog(CalendarParserImpl.class);
michael@0 72
michael@0 73 private final ComponentListParser componentListParser = new ComponentListParser();
michael@0 74
michael@0 75 private final ComponentParser componentParser = new ComponentParser();
michael@0 76
michael@0 77 private final PropertyListParser propertyListParser = new PropertyListParser();
michael@0 78
michael@0 79 private final PropertyParser propertyParser = new PropertyParser();
michael@0 80
michael@0 81 private final ParameterListParser paramListParser = new ParameterListParser();
michael@0 82
michael@0 83 private final ParameterParser paramParser = new ParameterParser();
michael@0 84
michael@0 85 /**
michael@0 86 * {@inheritDoc}
michael@0 87 */
michael@0 88 public final void parse(final InputStream in, final ContentHandler handler)
michael@0 89 throws IOException, ParserException {
michael@0 90 parse(new InputStreamReader(in), handler);
michael@0 91 }
michael@0 92
michael@0 93 /**
michael@0 94 * {@inheritDoc}
michael@0 95 */
michael@0 96 public final void parse(final Reader in, final ContentHandler handler)
michael@0 97 throws IOException, ParserException {
michael@0 98
michael@0 99 final StreamTokenizer tokeniser = new StreamTokenizer(in);
michael@0 100 try {
michael@0 101 tokeniser.resetSyntax();
michael@0 102 tokeniser.wordChars(WORD_CHAR_START, WORD_CHAR_END);
michael@0 103 tokeniser.whitespaceChars(WHITESPACE_CHAR_START,
michael@0 104 WHITESPACE_CHAR_END);
michael@0 105 tokeniser.ordinaryChar(':');
michael@0 106 tokeniser.ordinaryChar(';');
michael@0 107 tokeniser.ordinaryChar('=');
michael@0 108 tokeniser.ordinaryChar('\t');
michael@0 109 tokeniser.eolIsSignificant(true);
michael@0 110 tokeniser.whitespaceChars(0, 0);
michael@0 111 tokeniser.quoteChar('"');
michael@0 112
michael@0 113 // BEGIN:VCALENDAR
michael@0 114 assertToken(tokeniser, in, Calendar.BEGIN);
michael@0 115
michael@0 116 assertToken(tokeniser, in, ':');
michael@0 117
michael@0 118 assertToken(tokeniser, in, Calendar.VCALENDAR, true);
michael@0 119
michael@0 120 assertToken(tokeniser, in, StreamTokenizer.TT_EOL);
michael@0 121
michael@0 122 handler.startCalendar();
michael@0 123
michael@0 124 // parse calendar properties..
michael@0 125 propertyListParser.parse(tokeniser, in, handler);
michael@0 126
michael@0 127 // parse components..
michael@0 128 componentListParser.parse(tokeniser, in, handler);
michael@0 129
michael@0 130 // END:VCALENDAR
michael@0 131 // assertToken(tokeniser,Calendar.END);
michael@0 132
michael@0 133 assertToken(tokeniser, in, ':');
michael@0 134
michael@0 135 assertToken(tokeniser, in, Calendar.VCALENDAR, true);
michael@0 136
michael@0 137 handler.endCalendar();
michael@0 138 }
michael@0 139 catch (Exception e) {
michael@0 140
michael@0 141 if (e instanceof IOException) {
michael@0 142 throw (IOException) e;
michael@0 143 }
michael@0 144 if (e instanceof ParserException) {
michael@0 145 throw (ParserException) e;
michael@0 146 }
michael@0 147 else {
michael@0 148 throw new ParserException(e.getMessage(), getLineNumber(tokeniser, in), e);
michael@0 149 }
michael@0 150 }
michael@0 151 }
michael@0 152
michael@0 153 /**
michael@0 154 * Parses an iCalendar property list from the specified stream tokeniser.
michael@0 155 * @param tokeniser
michael@0 156 * @throws IOException
michael@0 157 * @throws ParseException
michael@0 158 * @throws URISyntaxException
michael@0 159 * @throws URISyntaxException
michael@0 160 * @throws ParserException
michael@0 161 */
michael@0 162 private class PropertyListParser {
michael@0 163
michael@0 164 public void parse(final StreamTokenizer tokeniser, Reader in,
michael@0 165 final ContentHandler handler) throws IOException, ParseException,
michael@0 166 URISyntaxException, ParserException {
michael@0 167
michael@0 168 assertToken(tokeniser, in, StreamTokenizer.TT_WORD);
michael@0 169
michael@0 170 while (/*
michael@0 171 * !Component.BEGIN.equals(tokeniser.sval) &&
michael@0 172 */!Component.END.equals(tokeniser.sval)) {
michael@0 173 // check for timezones observances or vevent/vtodo alarms..
michael@0 174 if (Component.BEGIN.equals(tokeniser.sval)) {
michael@0 175 componentParser.parse(tokeniser, in, handler);
michael@0 176 }
michael@0 177 else {
michael@0 178 propertyParser.parse(tokeniser, in, handler);
michael@0 179 }
michael@0 180 absorbWhitespace(tokeniser);
michael@0 181 // assertToken(tokeniser, StreamTokenizer.TT_WORD);
michael@0 182 }
michael@0 183 }
michael@0 184 }
michael@0 185
michael@0 186 /**
michael@0 187 * Parses an iCalendar property from the specified stream tokeniser.
michael@0 188 * @param tokeniser
michael@0 189 * @throws IOException
michael@0 190 * @throws ParserException
michael@0 191 * @throws URISyntaxException
michael@0 192 * @throws ParseException
michael@0 193 */
michael@0 194 private class PropertyParser {
michael@0 195
michael@0 196 private static final String PARSE_DEBUG_MESSAGE = "Property [{0}]";
michael@0 197
michael@0 198 private static final String PARSE_EXCEPTION_MESSAGE = "Property [{0}]";
michael@0 199
michael@0 200 private void parse(final StreamTokenizer tokeniser, Reader in,
michael@0 201 final ContentHandler handler) throws IOException, ParserException,
michael@0 202 URISyntaxException, ParseException {
michael@0 203
michael@0 204 final String name = tokeniser.sval;
michael@0 205
michael@0 206 // debugging..
michael@0 207 if (log.isDebugEnabled()) {
michael@0 208 log.debug(MessageFormat.format(PARSE_DEBUG_MESSAGE, new Object[] {name}));
michael@0 209 }
michael@0 210
michael@0 211 handler.startProperty(name);
michael@0 212
michael@0 213 paramListParser.parse(tokeniser, in, handler);
michael@0 214
michael@0 215 // it appears that control tokens (ie. ':') are allowed
michael@0 216 // after the first instance on a line is used.. as such
michael@0 217 // we must continue appending to value until EOL is
michael@0 218 // reached..
michael@0 219 // assertToken(tokeniser, StreamTokenizer.TT_WORD);
michael@0 220
michael@0 221 // String value = tokeniser.sval;
michael@0 222 final StringBuffer value = new StringBuffer();
michael@0 223
michael@0 224 // assertToken(tokeniser,StreamTokenizer.TT_EOL);
michael@0 225
michael@0 226 // DQUOTE is ordinary char for property value
michael@0 227 // From sec 4.3.11 of rfc-2445:
michael@0 228 // text = *(TSAFE-CHAR / ":" / DQUOTE / ESCAPED-CHAR)
michael@0 229 //
michael@0 230 tokeniser.ordinaryChar('"');
michael@0 231 int nextToken = tokeniser.nextToken();
michael@0 232
michael@0 233 while (nextToken != StreamTokenizer.TT_EOL
michael@0 234 && nextToken != StreamTokenizer.TT_EOF) {
michael@0 235
michael@0 236 if (tokeniser.ttype == StreamTokenizer.TT_WORD) {
michael@0 237 value.append(tokeniser.sval);
michael@0 238 }
michael@0 239 else {
michael@0 240 value.append((char) tokeniser.ttype);
michael@0 241 }
michael@0 242
michael@0 243 nextToken = tokeniser.nextToken();
michael@0 244 }
michael@0 245
michael@0 246 // reset DQUOTE to be quote char
michael@0 247 tokeniser.quoteChar('"');
michael@0 248
michael@0 249 if (nextToken == StreamTokenizer.TT_EOF) {
michael@0 250 throw new ParserException("Unexpected end of file",
michael@0 251 getLineNumber(tokeniser, in));
michael@0 252 }
michael@0 253
michael@0 254 try {
michael@0 255 handler.propertyValue(value.toString());
michael@0 256 }
michael@0 257 catch (ParseException e) {
michael@0 258 final ParseException eNew = new ParseException("[" + name + "] "
michael@0 259 + e.getMessage(), e.getErrorOffset());
michael@0 260 eNew.initCause(e);
michael@0 261 throw eNew;
michael@0 262 }
michael@0 263
michael@0 264 handler.endProperty(name);
michael@0 265
michael@0 266 }
michael@0 267 }
michael@0 268
michael@0 269 /**
michael@0 270 * Parses a list of iCalendar parameters by parsing the specified stream tokeniser.
michael@0 271 * @param tokeniser
michael@0 272 * @throws IOException
michael@0 273 * @throws ParserException
michael@0 274 * @throws URISyntaxException
michael@0 275 */
michael@0 276 private class ParameterListParser {
michael@0 277
michael@0 278 public void parse(final StreamTokenizer tokeniser, Reader in,
michael@0 279 final ContentHandler handler) throws IOException, ParserException,
michael@0 280 URISyntaxException {
michael@0 281
michael@0 282 while (tokeniser.nextToken() == ';') {
michael@0 283 paramParser.parse(tokeniser, in, handler);
michael@0 284 }
michael@0 285 }
michael@0 286 }
michael@0 287
michael@0 288 /**
michael@0 289 * @param tokeniser
michael@0 290 * @param handler
michael@0 291 * @throws IOException
michael@0 292 * @throws ParserException
michael@0 293 * @throws URISyntaxException
michael@0 294 */
michael@0 295 private class ParameterParser {
michael@0 296
michael@0 297 private void parse(final StreamTokenizer tokeniser, Reader in,
michael@0 298 final ContentHandler handler) throws IOException, ParserException,
michael@0 299 URISyntaxException {
michael@0 300
michael@0 301 assertToken(tokeniser, in, StreamTokenizer.TT_WORD);
michael@0 302
michael@0 303 final String paramName = tokeniser.sval;
michael@0 304
michael@0 305 // debugging..
michael@0 306 if (log.isDebugEnabled()) {
michael@0 307 log.debug("Parameter [" + paramName + "]");
michael@0 308 }
michael@0 309
michael@0 310 assertToken(tokeniser, in, '=');
michael@0 311
michael@0 312 final StringBuffer paramValue = new StringBuffer();
michael@0 313
michael@0 314 // preserve quote chars..
michael@0 315 if (tokeniser.nextToken() == '"') {
michael@0 316 paramValue.append('"');
michael@0 317 paramValue.append(tokeniser.sval);
michael@0 318 paramValue.append('"');
michael@0 319 }
michael@0 320 else if (tokeniser.sval != null) {
michael@0 321 paramValue.append(tokeniser.sval);
michael@0 322 // check for additional words to account for equals (=) in param-value
michael@0 323 int nextToken = tokeniser.nextToken();
michael@0 324
michael@0 325 while (nextToken != ';' && nextToken != ':' && nextToken != ',') {
michael@0 326
michael@0 327 if (tokeniser.ttype == StreamTokenizer.TT_WORD) {
michael@0 328 paramValue.append(tokeniser.sval);
michael@0 329 }
michael@0 330 else {
michael@0 331 paramValue.append((char) tokeniser.ttype);
michael@0 332 }
michael@0 333
michael@0 334 nextToken = tokeniser.nextToken();
michael@0 335 }
michael@0 336 tokeniser.pushBack();
michael@0 337 } else if(tokeniser.sval == null) {
michael@0 338 tokeniser.pushBack();
michael@0 339 }
michael@0 340
michael@0 341 try {
michael@0 342 handler.parameter(paramName, paramValue.toString());
michael@0 343 }
michael@0 344 catch (ClassCastException cce) {
michael@0 345 throw new ParserException("Error parsing parameter", getLineNumber(tokeniser, in), cce);
michael@0 346 }
michael@0 347 }
michael@0 348 }
michael@0 349
michael@0 350 /**
michael@0 351 * Parses an iCalendar component list from the specified stream tokeniser.
michael@0 352 * @param tokeniser
michael@0 353 * @throws IOException
michael@0 354 * @throws ParseException
michael@0 355 * @throws URISyntaxException
michael@0 356 * @throws ParserException
michael@0 357 */
michael@0 358 private class ComponentListParser {
michael@0 359
michael@0 360 private void parse(final StreamTokenizer tokeniser, Reader in,
michael@0 361 final ContentHandler handler) throws IOException, ParseException,
michael@0 362 URISyntaxException, ParserException {
michael@0 363
michael@0 364 while (Component.BEGIN.equals(tokeniser.sval)) {
michael@0 365 componentParser.parse(tokeniser, in, handler);
michael@0 366 absorbWhitespace(tokeniser);
michael@0 367 // assertToken(tokeniser, StreamTokenizer.TT_WORD);
michael@0 368 }
michael@0 369 }
michael@0 370 }
michael@0 371
michael@0 372 /**
michael@0 373 * Parses an iCalendar component from the specified stream tokeniser.
michael@0 374 * @param tokeniser
michael@0 375 * @throws IOException
michael@0 376 * @throws ParseException
michael@0 377 * @throws URISyntaxException
michael@0 378 * @throws ParserException
michael@0 379 */
michael@0 380 private class ComponentParser {
michael@0 381
michael@0 382 private void parse(final StreamTokenizer tokeniser, Reader in,
michael@0 383 final ContentHandler handler) throws IOException, ParseException,
michael@0 384 URISyntaxException, ParserException {
michael@0 385
michael@0 386 assertToken(tokeniser, in, ':');
michael@0 387
michael@0 388 assertToken(tokeniser, in, StreamTokenizer.TT_WORD);
michael@0 389
michael@0 390 final String name = tokeniser.sval;
michael@0 391
michael@0 392 handler.startComponent(name);
michael@0 393
michael@0 394 assertToken(tokeniser, in, StreamTokenizer.TT_EOL);
michael@0 395
michael@0 396 propertyListParser.parse(tokeniser, in, handler);
michael@0 397
michael@0 398 /*
michael@0 399 * // a special case for VTIMEZONE component which contains
michael@0 400 * // sub-components..
michael@0 401 * if (Component.VTIMEZONE.equals(name)) {
michael@0 402 * parseComponentList(tokeniser, handler);
michael@0 403 * }
michael@0 404 * // VEVENT/VTODO components may optionally have embedded VALARM
michael@0 405 * // components..
michael@0 406 * else if ((Component.VEVENT.equals(name) || Component.VTODO.equals(name))
michael@0 407 * &amp;&amp; Component.BEGIN.equals(tokeniser.sval)) {
michael@0 408 * parseComponentList(tokeniser, handler);
michael@0 409 * }
michael@0 410 */
michael@0 411
michael@0 412 assertToken(tokeniser, in, ':');
michael@0 413
michael@0 414 assertToken(tokeniser, in, name);
michael@0 415
michael@0 416 assertToken(tokeniser, in, StreamTokenizer.TT_EOL);
michael@0 417
michael@0 418 handler.endComponent(name);
michael@0 419 }
michael@0 420 }
michael@0 421
michael@0 422 /**
michael@0 423 * Asserts that the next token in the stream matches the specified token.
michael@0 424 * @param tokeniser stream tokeniser to perform assertion on
michael@0 425 * @param token expected token
michael@0 426 * @throws IOException when unable to read from stream
michael@0 427 * @throws ParserException when next token in the stream does not match the expected token
michael@0 428 */
michael@0 429 private void assertToken(final StreamTokenizer tokeniser, Reader in, final int token)
michael@0 430 throws IOException, ParserException {
michael@0 431
michael@0 432 if (tokeniser.nextToken() != token) {
michael@0 433 throw new ParserException(MessageFormat.format(UNEXPECTED_TOKEN_MESSAGE, new Object[] {
michael@0 434 new Integer(token), new Integer(tokeniser.ttype),
michael@0 435 }), getLineNumber(tokeniser, in));
michael@0 436 }
michael@0 437
michael@0 438 if (log.isDebugEnabled()) {
michael@0 439 log.debug("[" + token + "]");
michael@0 440 }
michael@0 441 }
michael@0 442
michael@0 443 /**
michael@0 444 * Asserts that the next token in the stream matches the specified token. This method is case-sensitive.
michael@0 445 * @param tokeniser
michael@0 446 * @param token
michael@0 447 * @throws IOException
michael@0 448 * @throws ParserException
michael@0 449 */
michael@0 450 private void assertToken(final StreamTokenizer tokeniser, Reader in, final String token)
michael@0 451 throws IOException, ParserException {
michael@0 452 assertToken(tokeniser, in, token, false);
michael@0 453 }
michael@0 454
michael@0 455 /**
michael@0 456 * Asserts that the next token in the stream matches the specified token.
michael@0 457 * @param tokeniser stream tokeniser to perform assertion on
michael@0 458 * @param token expected token
michael@0 459 * @throws IOException when unable to read from stream
michael@0 460 * @throws ParserException when next token in the stream does not match the expected token
michael@0 461 */
michael@0 462 private void assertToken(final StreamTokenizer tokeniser, Reader in,
michael@0 463 final String token, final boolean ignoreCase) throws IOException,
michael@0 464 ParserException {
michael@0 465
michael@0 466 // ensure next token is a word token..
michael@0 467 assertToken(tokeniser, in, StreamTokenizer.TT_WORD);
michael@0 468
michael@0 469 if (ignoreCase) {
michael@0 470 if (!token.equalsIgnoreCase(tokeniser.sval)) {
michael@0 471 throw new ParserException(MessageFormat.format(UNEXPECTED_TOKEN_MESSAGE, new Object[] {
michael@0 472 token, tokeniser.sval,
michael@0 473 }), getLineNumber(tokeniser, in));
michael@0 474 }
michael@0 475 }
michael@0 476 else if (!token.equals(tokeniser.sval)) {
michael@0 477 throw new ParserException(MessageFormat.format(UNEXPECTED_TOKEN_MESSAGE, new Object[] {
michael@0 478 token, tokeniser.sval,
michael@0 479 }), getLineNumber(tokeniser, in));
michael@0 480 }
michael@0 481
michael@0 482 if (log.isDebugEnabled()) {
michael@0 483 log.debug("[" + token + "]");
michael@0 484 }
michael@0 485 }
michael@0 486
michael@0 487 /**
michael@0 488 * Absorbs extraneous newlines.
michael@0 489 * @param tokeniser
michael@0 490 * @throws IOException
michael@0 491 */
michael@0 492 private void absorbWhitespace(final StreamTokenizer tokeniser) throws IOException {
michael@0 493 // HACK: absorb extraneous whitespace between components (KOrganizer)..
michael@0 494 while (tokeniser.nextToken() == StreamTokenizer.TT_EOL) {
michael@0 495 if (log.isTraceEnabled()) {
michael@0 496 log.trace("Absorbing extra whitespace..");
michael@0 497 }
michael@0 498 }
michael@0 499 if (log.isTraceEnabled()) {
michael@0 500 log.trace("Aborting: absorbing extra whitespace complete");
michael@0 501 }
michael@0 502 }
michael@0 503
michael@0 504 /**
michael@0 505 * @param tokeniser
michael@0 506 * @param in
michael@0 507 * @return
michael@0 508 */
michael@0 509 private int getLineNumber(StreamTokenizer tokeniser, Reader in) {
michael@0 510 int line = tokeniser.lineno();
michael@0 511 if (tokeniser.ttype == StreamTokenizer.TT_EOL) {
michael@0 512 line -= 1;
michael@0 513 }
michael@0 514 if (in instanceof UnfoldingReader) {
michael@0 515 // need to take unfolded lines into account
michael@0 516 final int unfolded = ((UnfoldingReader) in).getLinesUnfolded();
michael@0 517 line += unfolded;
michael@0 518 }
michael@0 519 return line;
michael@0 520 }
michael@0 521 }

mercurial