001 package biweekly; 002 003 import java.io.File; 004 import java.io.IOException; 005 import java.io.InputStream; 006 import java.io.OutputStream; 007 import java.io.Reader; 008 import java.io.StringWriter; 009 import java.io.Writer; 010 import java.util.ArrayList; 011 import java.util.Arrays; 012 import java.util.Collection; 013 import java.util.HashMap; 014 import java.util.List; 015 import java.util.Map; 016 import java.util.Properties; 017 018 import javax.xml.transform.TransformerException; 019 020 import org.w3c.dom.Document; 021 import org.xml.sax.SAXException; 022 023 import biweekly.component.ICalComponent; 024 import biweekly.component.marshaller.ICalComponentMarshaller; 025 import biweekly.io.ICalMarshallerRegistrar; 026 import biweekly.io.json.JCalParseException; 027 import biweekly.io.json.JCalReader; 028 import biweekly.io.json.JCalWriter; 029 import biweekly.io.text.ICalRawReader; 030 import biweekly.io.text.ICalRawWriter; 031 import biweekly.io.text.ICalReader; 032 import biweekly.io.text.ICalWriter; 033 import biweekly.io.xml.XCalDocument; 034 import biweekly.property.ICalProperty; 035 import biweekly.property.marshaller.ICalPropertyMarshaller; 036 import biweekly.util.IOUtils; 037 038 import com.fasterxml.jackson.core.JsonParseException; 039 040 /* 041 Copyright (c) 2013, Michael Angstadt 042 All rights reserved. 043 044 Redistribution and use in source and binary forms, with or without 045 modification, are permitted provided that the following conditions are met: 046 047 1. Redistributions of source code must retain the above copyright notice, this 048 list of conditions and the following disclaimer. 049 2. Redistributions in binary form must reproduce the above copyright notice, 050 this list of conditions and the following disclaimer in the documentation 051 and/or other materials provided with the distribution. 052 053 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 054 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 055 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 056 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 057 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 058 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 059 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 060 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 061 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 062 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 063 */ 064 065 /** 066 * <p> 067 * Contains static chaining factory methods for reading/writing iCalendar 068 * objects. 069 * </p> 070 * 071 * <p> 072 * <b>Writing an iCalendar object</b> 073 * 074 * <pre class="brush:java"> 075 * ICalendar ical = new ICalendar(); 076 * 077 * //string 078 * String icalString = Biweekly.write(ical).go(); 079 * 080 * //file 081 * File file = new File("meeting.ics"); 082 * Biweekly.write(ical).go(file); 083 * 084 * //output stream 085 * OutputStream out = ... 086 * Biweekly.write(ical).go(out); 087 * out.close(); 088 * 089 * //writer (should be configured to use UTF-8 encoding) 090 * Writer writer = ... 091 * Biweekly.write(ical).go(writer); 092 * writer.close(); 093 * </pre> 094 * 095 * </p> 096 * 097 * <p> 098 * <b>Writing multiple iCalendar objects</b> 099 * 100 * <pre class="brush:java"> 101 * ICalendar ical1 = new ICalendar(); 102 * ICalendar ical2 = new ICalendar(); 103 * 104 * String icalString = Biweekly.write(ical1, ical2).go(); 105 * </pre> 106 * 107 * </p> 108 * 109 * <p> 110 * <b>Writing an XML-encoded iCalendar object (xCal)</b><br> 111 * 112 * <pre class="brush:java"> 113 * //Call writeXml() instead of write() 114 * ICalendar ical = new ICalendar(); 115 * String xml = Biweekly.writeXml(ical).indent(2).go(); 116 * </pre> 117 * 118 * </p> 119 * 120 * <p> 121 * <b>Writing a JSON-encoded iCalendar object (jCal)</b><br> 122 * 123 * <pre class="brush:java"> 124 * //Call writeJson() instead of write() 125 * ICalendar ical = new ICalendar(); 126 * String json = Biweekly.writeJson(ical).go(); 127 * </pre> 128 * 129 * </p> 130 * 131 * <p> 132 * <b>Reading an iCalendar object</b> 133 * 134 * <pre class="brush:java"> 135 * ICalendar ical; 136 * 137 * //string 138 * String icalStr = ... 139 * ical = Biweekly.parse(icalStr).first(); 140 * 141 * //file 142 * File file = new File("meeting.ics"); 143 * ical = Biweekly.parse(file).first(); 144 * 145 * //input stream 146 * InputStream in = ... 147 * ical = Biweekly.parse(in).first(); 148 * in.close(); 149 * 150 * //reader (should be configured to read UTF-8) 151 * Reader reader = ... 152 * ical = Biweekly.parse(reader).first(); 153 * reader.close(); 154 * </pre> 155 * 156 * </p> 157 * 158 * <p> 159 * <b>Reading multiple iCalendar objects</b> 160 * 161 * <pre class="brush:java"> 162 * String icalStr = ... 163 * List<ICalendar> icals = Biweekly.parse(icalStr).all(); 164 * </pre> 165 * 166 * </p> 167 * 168 * <p> 169 * <b>Reading an XML-encoded iCalendar object (xCal)</b><br> 170 * 171 * <pre class="brush:java"> 172 * //Call parseXml() instead of parse() 173 * String xml = ... 174 * ICalendar ical = Biweekly.parseXml(xml).first(); 175 * </pre> 176 * 177 * </p> 178 * 179 * <p> 180 * <b>Reading a JSON-encoded iCalendar object (Cal)</b><br> 181 * 182 * <pre class="brush:java"> 183 * //Call parseJson() instead of parse() 184 * String json = ... 185 * ICalendar ical = Biweekly.parseJson(json).first(); 186 * </pre> 187 * 188 * </p> 189 * 190 * <p> 191 * <b>Retrieving parser warnings</b> 192 * 193 * <pre class="brush:java"> 194 * String icalStr = ... 195 * List<List<String>> warnings = new ArrayList<List<String>>(); 196 * List<ICalendar> icals = Biweekly.parse(icalStr).warnings(warnings).all(); 197 * int i = 0; 198 * for (List<String> icalWarnings : warnings){ 199 * System.out.println("iCal #" + (i++) + " warnings:"); 200 * for (String warning : icalWarnings){ 201 * System.out.println(warning); 202 * } 203 * } 204 * </pre> 205 * 206 * </p> 207 * 208 * <p> 209 * The methods in this class make use of the following classes. These classes 210 * can be used if greater control over the read/write operation is required: 211 * </p> 212 * 213 * <style> table.t td, table.t th {border:1px solid #000;} </style> 214 * <table class="t" cellpadding="5" style="border-collapse:collapse;"> 215 * <tr> 216 * <th></th> 217 * <th>Classes</th> 218 * <th>Supports<br> 219 * streaming?</th> 220 * </tr> 221 * <tr> 222 * <th>Text</th> 223 * <td>{@link ICalReader} / {@link ICalWriter}</td> 224 * <td>yes</td> 225 * </tr> 226 * <tr> 227 * <th>XML</th> 228 * <td>{@link XCalDocument}</td> 229 * <td>no</td> 230 * </tr> 231 * <tr> 232 * <th>JSON</th> 233 * <td>{@link JCalReader} / {@link JCalWriter}</td> 234 * <td>yes</td> 235 * </tr> 236 * </table> 237 * @author Michael Angstadt 238 */ 239 public class Biweekly { 240 /** 241 * The version of the library. 242 */ 243 public static final String VERSION; 244 245 /** 246 * The project webpage. 247 */ 248 public static final String URL; 249 250 static { 251 InputStream in = null; 252 try { 253 in = Biweekly.class.getResourceAsStream("/biweekly.properties"); 254 Properties props = new Properties(); 255 props.load(in); 256 257 VERSION = props.getProperty("version"); 258 URL = props.getProperty("url"); 259 } catch (IOException e) { 260 throw new RuntimeException(e); 261 } finally { 262 IOUtils.closeQuietly(in); 263 } 264 } 265 266 /** 267 * Parses an iCalendar object string. 268 * @param ical the iCalendar data 269 * @return chainer object for completing the parse operation 270 */ 271 public static ParserChainTextString parse(String ical) { 272 return new ParserChainTextString(ical); 273 } 274 275 /** 276 * Parses an iCalendar file. 277 * @param file the iCalendar file 278 * @return chainer object for completing the parse operation 279 */ 280 public static ParserChainTextReader parse(File file) { 281 return new ParserChainTextReader(file); 282 } 283 284 /** 285 * Parses an iCalendar data stream. 286 * @param in the input stream 287 * @return chainer object for completing the parse operation 288 */ 289 public static ParserChainTextReader parse(InputStream in) { 290 return new ParserChainTextReader(in); 291 } 292 293 /** 294 * Parses an iCalendar data stream. 295 * @param reader the reader 296 * @return chainer object for completing the parse operation 297 */ 298 public static ParserChainTextReader parse(Reader reader) { 299 return new ParserChainTextReader(reader); 300 } 301 302 /** 303 * Writes multiple iCalendar objects to a data stream. 304 * @param icals the iCalendar objects to write 305 * @return chainer object for completing the write operation 306 */ 307 public static WriterChainText write(ICalendar... icals) { 308 return write(Arrays.asList(icals)); 309 } 310 311 /** 312 * Writes multiple iCalendar objects to a data stream. 313 * @param icals the iCalendar objects to write 314 * @return chainer object for completing the write operation 315 */ 316 public static WriterChainText write(Collection<ICalendar> icals) { 317 return new WriterChainText(icals); 318 } 319 320 /** 321 * Parses an xCal document (XML-encoded iCalendar objects) from a string. 322 * @param xml the XML string 323 * @return chainer object for completing the parse operation 324 */ 325 public static ParserChainXmlString parseXml(String xml) { 326 return new ParserChainXmlString(xml); 327 } 328 329 /** 330 * Parses an xCal document (XML-encoded iCalendar objects) from a file. 331 * @param file the XML file 332 * @return chainer object for completing the parse operation 333 */ 334 public static ParserChainXmlReader parseXml(File file) { 335 return new ParserChainXmlReader(file); 336 } 337 338 /** 339 * Parses an xCal document (XML-encoded iCalendar objects) from an input 340 * stream. 341 * @param in the input stream 342 * @return chainer object for completing the parse operation 343 */ 344 public static ParserChainXmlReader parseXml(InputStream in) { 345 return new ParserChainXmlReader(in); 346 } 347 348 /** 349 * <p> 350 * Parses an xCal document (XML-encoded iCalendar objects) from a reader. 351 * </p> 352 * <p> 353 * Note that use of this method is discouraged. It ignores the character 354 * encoding that is defined within the XML document itself, and should only 355 * be used if the encoding is undefined or if the encoding needs to be 356 * ignored for whatever reason. The {@link #parseXml(InputStream)} method 357 * should be used instead, since it takes the XML document's character 358 * encoding into account when parsing. 359 * </p> 360 * @param reader the reader 361 * @return chainer object for completing the parse operation 362 */ 363 public static ParserChainXmlReader parseXml(Reader reader) { 364 return new ParserChainXmlReader(reader); 365 } 366 367 /** 368 * Parses an xCal document (XML-encoded iCalendar objects). 369 * @param document the XML document 370 * @return chainer object for completing the parse operation 371 */ 372 public static ParserChainXmlDocument parseXml(Document document) { 373 return new ParserChainXmlDocument(document); 374 } 375 376 /** 377 * Writes an xCal document (XML-encoded iCalendar objects). 378 * @param icals the iCalendar object(s) to write 379 * @return chainer object for completing the write operation 380 */ 381 public static WriterChainXml writeXml(ICalendar... icals) { 382 return writeXml(Arrays.asList(icals)); 383 } 384 385 /** 386 * Writes an xCal document (XML-encoded iCalendar objects). 387 * @param icals the iCalendar objects to write 388 * @return chainer object for completing the write operation 389 */ 390 public static WriterChainXml writeXml(Collection<ICalendar> icals) { 391 return new WriterChainXml(icals); 392 } 393 394 /** 395 * Parses a jCal data stream (JSON-encoded iCalendar objects). 396 * @param json the JSON data 397 * @return chainer object for completing the parse operation 398 */ 399 public static ParserChainJsonString parseJson(String json) { 400 return new ParserChainJsonString(json); 401 } 402 403 /** 404 * Parses a jCal data stream (JSON-encoded iCalendar objects). 405 * @param file the JSON file 406 * @return chainer object for completing the parse operation 407 */ 408 public static ParserChainJsonReader parseJson(File file) { 409 return new ParserChainJsonReader(file); 410 } 411 412 /** 413 * Parses a jCal data stream (JSON-encoded iCalendar objects). 414 * @param in the input stream 415 * @return chainer object for completing the parse operation 416 */ 417 public static ParserChainJsonReader parseJson(InputStream in) { 418 return new ParserChainJsonReader(in); 419 } 420 421 /** 422 * Parses a jCal data stream (JSON-encoded iCalendar objects). 423 * @param reader the reader 424 * @return chainer object for completing the parse operation 425 */ 426 public static ParserChainJsonReader parseJson(Reader reader) { 427 return new ParserChainJsonReader(reader); 428 } 429 430 /** 431 * Writes an xCal document (XML-encoded iCalendar objects). 432 * @param icals the iCalendar object(s) to write 433 * @return chainer object for completing the write operation 434 */ 435 public static WriterChainJson writeJson(ICalendar... icals) { 436 return writeJson(Arrays.asList(icals)); 437 } 438 439 /** 440 * Writes an xCal document (XML-encoded iCalendar objects). 441 * @param icals the iCalendar objects to write 442 * @return chainer object for completing the write operation 443 */ 444 public static WriterChainJson writeJson(Collection<ICalendar> icals) { 445 return new WriterChainJson(icals); 446 } 447 448 static abstract class ParserChain<T> { 449 //Note: "package" level is used so various fields/methods don't show up in the Javadocs, but are still visible to child classes 450 final ICalMarshallerRegistrar registrar = new ICalMarshallerRegistrar(); 451 452 @SuppressWarnings("unchecked") 453 final T this_ = (T) this; 454 455 List<List<String>> warnings; 456 457 /** 458 * Registers a property marshaller. 459 * @param marshaller the marshaller 460 * @return this 461 */ 462 public T register(ICalPropertyMarshaller<? extends ICalProperty> marshaller) { 463 registrar.register(marshaller); 464 return this_; 465 } 466 467 /** 468 * Registers a component marshaller. 469 * @param marshaller the marshaller 470 * @return this 471 */ 472 public T register(ICalComponentMarshaller<? extends ICalComponent> marshaller) { 473 registrar.register(marshaller); 474 return this_; 475 } 476 477 /** 478 * Provides a list for putting the parser warnings into. 479 * @param warnings the list object to populate (it is a 480 * "list of lists"--each parsed {@link ICalendar} object has its own 481 * warnings list) 482 * @return this 483 */ 484 public T warnings(List<List<String>> warnings) { 485 this.warnings = warnings; 486 return this_; 487 } 488 489 /** 490 * Reads the first iCalendar object from the data stream. 491 * @return the first iCalendar object or null if there are none 492 * @throws IOException if there a problem reading from the data stream 493 * @throws SAXException if there's a problem parsing the XML 494 */ 495 public abstract ICalendar first() throws IOException, SAXException; 496 497 /** 498 * Reads all iCalendar objects from the data stream. 499 * @return the parsed iCalendar objects 500 * @throws IOException if there's a problem reading from the data stream 501 * @throws SAXException if there's a problem parsing the XML 502 */ 503 public abstract List<ICalendar> all() throws IOException, SAXException; 504 } 505 506 /////////////////////////////////////////////////////// 507 // plain-text 508 /////////////////////////////////////////////////////// 509 510 static abstract class ParserChainText<T> extends ParserChain<T> { 511 boolean caretDecoding = true; 512 final boolean closeWhenDone; 513 514 private ParserChainText(boolean closeWhenDone) { 515 this.closeWhenDone = closeWhenDone; 516 } 517 518 /** 519 * Sets whether the reader will decode parameter values that use 520 * circumflex accent encoding (enabled by default). This escaping 521 * mechanism allows newlines and double quotes to be included in 522 * parameter values. 523 * @param enable true to use circumflex accent decoding, false not to 524 * @return this 525 * @see ICalRawReader#setCaretDecodingEnabled(boolean) 526 */ 527 public T caretDecoding(boolean enable) { 528 caretDecoding = enable; 529 return this_; 530 } 531 532 @Override 533 public ICalendar first() throws IOException { 534 ICalReader parser = constructReader(); 535 536 try { 537 ICalendar ical = parser.readNext(); 538 if (warnings != null) { 539 warnings.add(parser.getWarnings()); 540 } 541 return ical; 542 } finally { 543 if (closeWhenDone) { 544 IOUtils.closeQuietly(parser); 545 } 546 } 547 } 548 549 @Override 550 public List<ICalendar> all() throws IOException { 551 ICalReader parser = constructReader(); 552 553 try { 554 List<ICalendar> icals = new ArrayList<ICalendar>(); 555 ICalendar ical; 556 while ((ical = parser.readNext()) != null) { 557 if (warnings != null) { 558 warnings.add(parser.getWarnings()); 559 } 560 icals.add(ical); 561 } 562 return icals; 563 } finally { 564 if (closeWhenDone) { 565 IOUtils.closeQuietly(parser); 566 } 567 } 568 } 569 570 private ICalReader constructReader() throws IOException { 571 ICalReader parser = _constructReader(); 572 parser.setRegistrar(registrar); 573 parser.setCaretDecodingEnabled(caretDecoding); 574 return parser; 575 } 576 577 abstract ICalReader _constructReader() throws IOException; 578 } 579 580 /** 581 * Chainer class for parsing plain text iCalendar data streams. 582 * @see Biweekly#parse(InputStream) 583 * @see Biweekly#parse(File) 584 * @see Biweekly#parse(Reader) 585 */ 586 public static class ParserChainTextReader extends ParserChainText<ParserChainTextReader> { 587 private final InputStream in; 588 private final File file; 589 private final Reader reader; 590 591 private ParserChainTextReader(InputStream in) { 592 super(false); 593 this.in = in; 594 this.reader = null; 595 this.file = null; 596 } 597 598 private ParserChainTextReader(File file) { 599 super(true); 600 this.in = null; 601 this.reader = null; 602 this.file = file; 603 } 604 605 private ParserChainTextReader(Reader reader) { 606 super(false); 607 this.in = null; 608 this.reader = reader; 609 this.file = null; 610 } 611 612 @Override 613 public ParserChainTextReader register(ICalPropertyMarshaller<? extends ICalProperty> marshaller) { 614 return super.register(marshaller); 615 } 616 617 @Override 618 public ParserChainTextReader register(ICalComponentMarshaller<? extends ICalComponent> marshaller) { 619 return super.register(marshaller); 620 } 621 622 @Override 623 public ParserChainTextReader warnings(List<List<String>> warnings) { 624 return super.warnings(warnings); 625 } 626 627 @Override 628 public ParserChainTextReader caretDecoding(boolean enable) { 629 return super.caretDecoding(enable); 630 } 631 632 @Override 633 ICalReader _constructReader() throws IOException { 634 if (in != null) { 635 return new ICalReader(in); 636 } 637 if (file != null) { 638 return new ICalReader(file); 639 } 640 return new ICalReader(reader); 641 } 642 } 643 644 /** 645 * Chainer class for parsing plain text iCalendar strings. 646 * @see Biweekly#parse(String) 647 */ 648 public static class ParserChainTextString extends ParserChainText<ParserChainTextString> { 649 private final String text; 650 651 private ParserChainTextString(String text) { 652 super(false); 653 this.text = text; 654 } 655 656 @Override 657 public ParserChainTextString register(ICalPropertyMarshaller<? extends ICalProperty> marshaller) { 658 return super.register(marshaller); 659 } 660 661 @Override 662 public ParserChainTextString register(ICalComponentMarshaller<? extends ICalComponent> marshaller) { 663 return super.register(marshaller); 664 } 665 666 @Override 667 public ParserChainTextString warnings(List<List<String>> warnings) { 668 return super.warnings(warnings); 669 } 670 671 @Override 672 public ParserChainTextString caretDecoding(boolean enable) { 673 return super.caretDecoding(enable); 674 } 675 676 @Override 677 ICalReader _constructReader() { 678 return new ICalReader(text); 679 } 680 681 @Override 682 public ICalendar first() { 683 try { 684 return super.first(); 685 } catch (IOException e) { 686 //should never been thrown because we're reading from a string 687 throw new RuntimeException(e); 688 } 689 } 690 691 @Override 692 public List<ICalendar> all() { 693 try { 694 return super.all(); 695 } catch (IOException e) { 696 //should never been thrown because we're reading from a string 697 throw new RuntimeException(e); 698 } 699 } 700 } 701 702 /////////////////////////////////////////////////////// 703 // XML 704 /////////////////////////////////////////////////////// 705 706 static abstract class ParserChainXml<T> extends ParserChain<T> { 707 @Override 708 public ICalendar first() throws IOException, SAXException { 709 XCalDocument document = constructDocument(); 710 ICalendar ical = document.parseFirst(); 711 if (warnings != null) { 712 warnings.addAll(document.getParseWarnings()); 713 } 714 return ical; 715 } 716 717 @Override 718 public List<ICalendar> all() throws IOException, SAXException { 719 XCalDocument document = constructDocument(); 720 List<ICalendar> icals = document.parseAll(); 721 if (warnings != null) { 722 warnings.addAll(document.getParseWarnings()); 723 } 724 return icals; 725 } 726 727 private XCalDocument constructDocument() throws SAXException, IOException { 728 XCalDocument parser = _constructDocument(); 729 parser.setRegistrar(registrar); 730 return parser; 731 } 732 733 abstract XCalDocument _constructDocument() throws IOException, SAXException; 734 } 735 736 /** 737 * Chainer class for parsing XML-encoded iCalendar objects (xCal). 738 * @see Biweekly#parseXml(String) 739 */ 740 public static class ParserChainXmlString extends ParserChainXml<ParserChainXmlString> { 741 private final String xml; 742 743 private ParserChainXmlString(String xml) { 744 this.xml = xml; 745 } 746 747 @Override 748 public ParserChainXmlString register(ICalPropertyMarshaller<? extends ICalProperty> marshaller) { 749 return super.register(marshaller); 750 } 751 752 @Override 753 public ParserChainXmlString register(ICalComponentMarshaller<? extends ICalComponent> marshaller) { 754 return super.register(marshaller); 755 } 756 757 @Override 758 public ParserChainXmlString warnings(List<List<String>> warnings) { 759 return super.warnings(warnings); 760 } 761 762 @Override 763 XCalDocument _constructDocument() throws SAXException { 764 return new XCalDocument(xml); 765 } 766 767 @Override 768 public ICalendar first() throws SAXException { 769 try { 770 return super.first(); 771 } catch (IOException e) { 772 //should never been thrown because we're reading from a string 773 throw new RuntimeException(e); 774 } 775 } 776 777 @Override 778 public List<ICalendar> all() throws SAXException { 779 try { 780 return super.all(); 781 } catch (IOException e) { 782 //should never been thrown because we're reading from a string 783 throw new RuntimeException(e); 784 } 785 } 786 } 787 788 /** 789 * Chainer class for parsing XML-encoded iCalendar objects (xCal). 790 * @see Biweekly#parseXml(InputStream) 791 * @see Biweekly#parseXml(File) 792 * @see Biweekly#parseXml(Reader) 793 */ 794 public static class ParserChainXmlReader extends ParserChainXml<ParserChainXmlReader> { 795 private final InputStream in; 796 private final File file; 797 private final Reader reader; 798 799 private ParserChainXmlReader(InputStream in) { 800 this.in = in; 801 this.reader = null; 802 this.file = null; 803 } 804 805 private ParserChainXmlReader(File file) { 806 this.in = null; 807 this.reader = null; 808 this.file = file; 809 } 810 811 private ParserChainXmlReader(Reader reader) { 812 this.in = null; 813 this.reader = reader; 814 this.file = null; 815 } 816 817 @Override 818 public ParserChainXmlReader register(ICalPropertyMarshaller<? extends ICalProperty> marshaller) { 819 return super.register(marshaller); 820 } 821 822 @Override 823 public ParserChainXmlReader register(ICalComponentMarshaller<? extends ICalComponent> marshaller) { 824 return super.register(marshaller); 825 } 826 827 @Override 828 public ParserChainXmlReader warnings(List<List<String>> warnings) { 829 return super.warnings(warnings); 830 } 831 832 @Override 833 XCalDocument _constructDocument() throws IOException, SAXException { 834 if (in != null) { 835 return new XCalDocument(in); 836 } 837 if (file != null) { 838 return new XCalDocument(file); 839 } 840 return new XCalDocument(reader); 841 } 842 } 843 844 /** 845 * Chainer class for parsing XML-encoded iCalendar objects (xCal). 846 * @see Biweekly#parseXml(Document) 847 */ 848 public static class ParserChainXmlDocument extends ParserChainXml<ParserChainXmlDocument> { 849 private final Document document; 850 851 private ParserChainXmlDocument(Document document) { 852 this.document = document; 853 } 854 855 @Override 856 public ParserChainXmlDocument register(ICalPropertyMarshaller<? extends ICalProperty> marshaller) { 857 return super.register(marshaller); 858 } 859 860 @Override 861 public ParserChainXmlDocument register(ICalComponentMarshaller<? extends ICalComponent> marshaller) { 862 return super.register(marshaller); 863 } 864 865 @Override 866 public ParserChainXmlDocument warnings(List<List<String>> warnings) { 867 return super.warnings(warnings); 868 } 869 870 @Override 871 XCalDocument _constructDocument() { 872 return new XCalDocument(document); 873 } 874 875 @Override 876 public ICalendar first() { 877 try { 878 return super.first(); 879 } catch (IOException e) { 880 //should never been thrown because we're reading from a DOM 881 throw new RuntimeException(e); 882 } catch (SAXException e) { 883 //should never been thrown because we're reading from a DOM 884 throw new RuntimeException(e); 885 } 886 } 887 888 @Override 889 public List<ICalendar> all() { 890 try { 891 return super.all(); 892 } catch (IOException e) { 893 //should never been thrown because we're reading from a DOM 894 throw new RuntimeException(e); 895 } catch (SAXException e) { 896 //should never been thrown because we're reading from a DOM 897 throw new RuntimeException(e); 898 } 899 } 900 } 901 902 /////////////////////////////////////////////////////// 903 // JSON 904 /////////////////////////////////////////////////////// 905 906 static abstract class ParserChainJson<T> extends ParserChain<T> { 907 final boolean closeWhenDone; 908 909 private ParserChainJson(boolean closeWhenDone) { 910 this.closeWhenDone = closeWhenDone; 911 } 912 913 /** 914 * @throws JCalParseException if the jCal syntax is incorrect (the JSON 915 * syntax may be valid, but it is not in the correct jCal format). 916 * @throws JsonParseException if the JSON syntax is incorrect 917 */ 918 @Override 919 public ICalendar first() throws IOException { 920 JCalReader parser = constructReader(); 921 922 try { 923 ICalendar ical = parser.readNext(); 924 if (warnings != null) { 925 warnings.add(parser.getWarnings()); 926 } 927 return ical; 928 } finally { 929 if (closeWhenDone) { 930 IOUtils.closeQuietly(parser); 931 } 932 } 933 } 934 935 /** 936 * @throws JCalParseException if the jCal syntax is incorrect (the JSON 937 * syntax may be valid, but it is not in the correct jCal format). 938 * @throws JsonParseException if the JSON syntax is incorrect 939 */ 940 @Override 941 public List<ICalendar> all() throws IOException { 942 JCalReader parser = constructReader(); 943 944 try { 945 List<ICalendar> icals = new ArrayList<ICalendar>(); 946 ICalendar ical; 947 while ((ical = parser.readNext()) != null) { 948 if (warnings != null) { 949 warnings.add(parser.getWarnings()); 950 } 951 icals.add(ical); 952 } 953 return icals; 954 } finally { 955 if (closeWhenDone) { 956 IOUtils.closeQuietly(parser); 957 } 958 } 959 } 960 961 private JCalReader constructReader() throws IOException { 962 JCalReader parser = _constructReader(); 963 parser.setRegistrar(registrar); 964 return parser; 965 } 966 967 abstract JCalReader _constructReader() throws IOException; 968 } 969 970 /** 971 * Chainer class for parsing JSON-encoded iCalendar data streams (jCal). 972 * @see Biweekly#parseJson(InputStream) 973 * @see Biweekly#parseJson(File) 974 * @see Biweekly#parseJson(Reader) 975 */ 976 public static class ParserChainJsonReader extends ParserChainJson<ParserChainJsonReader> { 977 private final InputStream in; 978 private final File file; 979 private final Reader reader; 980 981 private ParserChainJsonReader(InputStream in) { 982 super(false); 983 this.in = in; 984 this.reader = null; 985 this.file = null; 986 } 987 988 private ParserChainJsonReader(File file) { 989 super(true); 990 this.in = null; 991 this.reader = null; 992 this.file = file; 993 } 994 995 private ParserChainJsonReader(Reader reader) { 996 super(false); 997 this.in = null; 998 this.reader = reader; 999 this.file = null; 1000 } 1001 1002 @Override 1003 public ParserChainJsonReader register(ICalPropertyMarshaller<? extends ICalProperty> marshaller) { 1004 return super.register(marshaller); 1005 } 1006 1007 @Override 1008 public ParserChainJsonReader register(ICalComponentMarshaller<? extends ICalComponent> marshaller) { 1009 return super.register(marshaller); 1010 } 1011 1012 @Override 1013 public ParserChainJsonReader warnings(List<List<String>> warnings) { 1014 return super.warnings(warnings); 1015 } 1016 1017 @Override 1018 JCalReader _constructReader() throws IOException { 1019 if (in != null) { 1020 return new JCalReader(in); 1021 } 1022 if (file != null) { 1023 return new JCalReader(file); 1024 } 1025 return new JCalReader(reader); 1026 } 1027 } 1028 1029 /** 1030 * Chainer class for parsing JSON-encoded iCalendar strings (jCal). 1031 * @see Biweekly#parseJson(String) 1032 */ 1033 public static class ParserChainJsonString extends ParserChainJson<ParserChainJsonString> { 1034 private final String text; 1035 1036 private ParserChainJsonString(String text) { 1037 super(false); 1038 this.text = text; 1039 } 1040 1041 @Override 1042 public ParserChainJsonString register(ICalPropertyMarshaller<? extends ICalProperty> marshaller) { 1043 return super.register(marshaller); 1044 } 1045 1046 @Override 1047 public ParserChainJsonString register(ICalComponentMarshaller<? extends ICalComponent> marshaller) { 1048 return super.register(marshaller); 1049 } 1050 1051 @Override 1052 public ParserChainJsonString warnings(List<List<String>> warnings) { 1053 return super.warnings(warnings); 1054 } 1055 1056 @Override 1057 JCalReader _constructReader() { 1058 return new JCalReader(text); 1059 } 1060 1061 @Override 1062 public ICalendar first() { 1063 try { 1064 return super.first(); 1065 } catch (IOException e) { 1066 //should never been thrown because we're reading from a string 1067 throw new RuntimeException(e); 1068 } 1069 } 1070 1071 @Override 1072 public List<ICalendar> all() { 1073 try { 1074 return super.all(); 1075 } catch (IOException e) { 1076 //should never been thrown because we're reading from a string 1077 throw new RuntimeException(e); 1078 } 1079 } 1080 } 1081 1082 static abstract class WriterChain<T> { 1083 final Collection<ICalendar> icals; 1084 final ICalMarshallerRegistrar registrar = new ICalMarshallerRegistrar(); 1085 1086 @SuppressWarnings("unchecked") 1087 final T this_ = (T) this; 1088 1089 WriterChain(Collection<ICalendar> icals) { 1090 this.icals = icals; 1091 } 1092 1093 /** 1094 * Registers a property marshaller. 1095 * @param marshaller the marshaller 1096 * @return this 1097 */ 1098 public T register(ICalPropertyMarshaller<? extends ICalProperty> marshaller) { 1099 registrar.register(marshaller); 1100 return this_; 1101 } 1102 1103 /** 1104 * Registers a component marshaller. 1105 * @param marshaller the marshaller 1106 * @return this 1107 */ 1108 public T register(ICalComponentMarshaller<? extends ICalComponent> marshaller) { 1109 registrar.register(marshaller); 1110 return this_; 1111 } 1112 } 1113 1114 /////////////////////////////////////////////////////// 1115 // plain-text 1116 /////////////////////////////////////////////////////// 1117 1118 /** 1119 * Chainer class for writing to plain text iCalendar data streams. 1120 * @see Biweekly#write(Collection) 1121 * @see Biweekly#write(ICalendar...) 1122 */ 1123 public static class WriterChainText extends WriterChain<WriterChainText> { 1124 boolean caretEncoding = false; 1125 1126 private WriterChainText(Collection<ICalendar> icals) { 1127 super(icals); 1128 } 1129 1130 /** 1131 * <p> 1132 * Sets whether the writer will apply circumflex accent encoding on 1133 * parameter values (disabled by default). This escaping mechanism 1134 * allows for newlines and double quotes to be included in parameter 1135 * values. 1136 * </p> 1137 * 1138 * <p> 1139 * When disabled, the writer will replace newlines with spaces and 1140 * double quotes with single quotes. 1141 * </p> 1142 * @param enable true to use circumflex accent encoding, false not to 1143 * @return this 1144 * @see ICalRawWriter#setCaretEncodingEnabled(boolean) 1145 */ 1146 public WriterChainText caretEncoding(boolean enable) { 1147 this.caretEncoding = enable; 1148 return this_; 1149 } 1150 1151 /** 1152 * Writes the iCalendar objects to a string. 1153 * @return the iCalendar string 1154 * @throws IllegalArgumentException if the marshaller class for a 1155 * component or property object cannot be found (only happens when an 1156 * experimental property/component marshaller is not registered with the 1157 * {@code register} method.) 1158 */ 1159 public String go() { 1160 StringWriter sw = new StringWriter(); 1161 try { 1162 go(sw); 1163 } catch (IOException e) { 1164 //writing to a string 1165 } 1166 return sw.toString(); 1167 } 1168 1169 /** 1170 * Writes the iCalendar objects to a data stream. 1171 * @param out the output stream to write to 1172 * @throws IllegalArgumentException if the marshaller class for a 1173 * component or property object cannot be found (only happens when an 1174 * experimental property/component marshaller is not registered with the 1175 * {@code register} method.) 1176 * @throws IOException if there's a problem writing to the output stream 1177 */ 1178 public void go(OutputStream out) throws IOException { 1179 go(new ICalWriter(out)); 1180 } 1181 1182 /** 1183 * Writes the iCalendar objects to a file. 1184 * @param file the file to write to 1185 * @throws IllegalArgumentException if the marshaller class for a 1186 * component or property object cannot be found (only happens when an 1187 * experimental property/component marshaller is not registered with the 1188 * {@code register} method.) 1189 * @throws IOException if there's a problem writing to the file 1190 */ 1191 public void go(File file) throws IOException { 1192 go(file, false); 1193 } 1194 1195 /** 1196 * Writes the iCalendar objects to a file. 1197 * @param file the file to write to 1198 * @param append true to append to the end of the file, false to 1199 * overwrite it 1200 * @throws IllegalArgumentException if the marshaller class for a 1201 * component or property object cannot be found (only happens when an 1202 * experimental property/component marshaller is not registered with the 1203 * {@code register} method.) 1204 * @throws IOException if there's a problem writing to the file 1205 */ 1206 public void go(File file, boolean append) throws IOException { 1207 ICalWriter icalWriter = new ICalWriter(file, append); 1208 try { 1209 go(icalWriter); 1210 } finally { 1211 IOUtils.closeQuietly(icalWriter); 1212 } 1213 } 1214 1215 /** 1216 * Writes the iCalendar objects to a data stream. 1217 * @param writer the writer to write to 1218 * @throws IllegalArgumentException if the marshaller class for a 1219 * component or property object cannot be found (only happens when an 1220 * experimental property/component marshaller is not registered with the 1221 * {@code register} method.) 1222 * @throws IOException if there's a problem writing to the writer 1223 */ 1224 public void go(Writer writer) throws IOException { 1225 go(new ICalWriter(writer)); 1226 } 1227 1228 private void go(ICalWriter icalWriter) throws IOException { 1229 icalWriter.setRegistrar(registrar); 1230 icalWriter.setCaretEncodingEnabled(caretEncoding); 1231 1232 for (ICalendar ical : icals) { 1233 icalWriter.write(ical); 1234 } 1235 } 1236 } 1237 1238 /////////////////////////////////////////////////////// 1239 // XML 1240 /////////////////////////////////////////////////////// 1241 1242 /** 1243 * Chainer class for writing xCal documents (XML-encoded iCalendar objects). 1244 * @see Biweekly#writeXml(Collection) 1245 * @see Biweekly#writeXml(ICalendar...) 1246 */ 1247 public static class WriterChainXml extends WriterChain<WriterChainXml> { 1248 int indent = -1; 1249 final Map<String, ICalDataType> parameterDataTypes = new HashMap<String, ICalDataType>(0); 1250 1251 WriterChainXml(Collection<ICalendar> icals) { 1252 super(icals); 1253 } 1254 1255 @Override 1256 public WriterChainXml register(ICalPropertyMarshaller<? extends ICalProperty> marshaller) { 1257 return super.register(marshaller); 1258 } 1259 1260 @Override 1261 public WriterChainXml register(ICalComponentMarshaller<? extends ICalComponent> marshaller) { 1262 return super.register(marshaller); 1263 } 1264 1265 /** 1266 * Registers the data type of an experimental parameter. Experimental 1267 * parameters use the "unknown" xCal data type by default. 1268 * @param parameterName the parameter name (e.g. "x-foo") 1269 * @param dataType the data type 1270 * @return this 1271 */ 1272 public WriterChainXml register(String parameterName, ICalDataType dataType) { 1273 parameterDataTypes.put(parameterName, dataType); 1274 return this_; 1275 } 1276 1277 /** 1278 * Sets the number of indent spaces to use for pretty-printing. If not 1279 * set, then the XML will not be pretty-printed. 1280 * @param indent the number of spaces 1281 * @return this 1282 */ 1283 public WriterChainXml indent(int indent) { 1284 this.indent = indent; 1285 return this_; 1286 } 1287 1288 /** 1289 * Writes the xCal document to a string. 1290 * @return the XML string 1291 * @throws IllegalArgumentException if the marshaller class for a 1292 * component or property object cannot be found (only happens when an 1293 * experimental property/component marshaller is not registered with the 1294 * {@code register} method.) 1295 */ 1296 public String go() { 1297 StringWriter sw = new StringWriter(); 1298 try { 1299 go(sw); 1300 } catch (TransformerException e) { 1301 //writing to a string 1302 } 1303 return sw.toString(); 1304 } 1305 1306 /** 1307 * Writes the xCal document to an output stream. 1308 * @param out the output stream to write to 1309 * @throws IllegalArgumentException if the marshaller class for a 1310 * component or property object cannot be found (only happens when an 1311 * experimental property/component marshaller is not registered with the 1312 * {@code register} method.) 1313 * @throws TransformerException if there's a problem writing the XML 1314 */ 1315 public void go(OutputStream out) throws TransformerException { 1316 XCalDocument document = constructDocument(); 1317 document.write(out, indent); 1318 } 1319 1320 /** 1321 * Writes the xCal document to a file. 1322 * @param file the file to write to 1323 * @throws IllegalArgumentException if the marshaller class for a 1324 * component or property object cannot be found (only happens when an 1325 * experimental property/component marshaller is not registered with the 1326 * {@code register} method.) 1327 * @throws TransformerException if there's a problem writing the XML 1328 * @throws IOException if there's a problem writing to the file 1329 */ 1330 public void go(File file) throws TransformerException, IOException { 1331 XCalDocument document = constructDocument(); 1332 document.write(file, indent); 1333 } 1334 1335 /** 1336 * Writes the xCal document to a writer. 1337 * @param writer the writer to write to 1338 * @throws IllegalArgumentException if the marshaller class for a 1339 * component or property object cannot be found (only happens when an 1340 * experimental property/component marshaller is not registered with the 1341 * {@code register} method.) 1342 * @throws TransformerException if there's a problem writing the XML 1343 */ 1344 public void go(Writer writer) throws TransformerException { 1345 XCalDocument document = constructDocument(); 1346 document.write(writer, indent); 1347 } 1348 1349 /** 1350 * Writes the xCal document to an XML DOM. 1351 * @return the XML DOM 1352 */ 1353 public Document dom() { 1354 XCalDocument document = constructDocument(); 1355 return document.getDocument(); 1356 } 1357 1358 private XCalDocument constructDocument() { 1359 XCalDocument document = new XCalDocument(); 1360 document.setRegistrar(registrar); 1361 for (Map.Entry<String, ICalDataType> entry : parameterDataTypes.entrySet()) { 1362 document.registerParameterDataType(entry.getKey(), entry.getValue()); 1363 } 1364 1365 for (ICalendar ical : icals) { 1366 document.add(ical); 1367 } 1368 1369 return document; 1370 } 1371 } 1372 1373 /////////////////////////////////////////////////////// 1374 // JSON 1375 /////////////////////////////////////////////////////// 1376 1377 /** 1378 * Chainer class for writing to JSON-encoded iCalendar data streams (jCal). 1379 * @see Biweekly#writeJson(Collection) 1380 * @see Biweekly#writeJson(ICalendar...) 1381 */ 1382 public static class WriterChainJson extends WriterChain<WriterChainJson> { 1383 private boolean indent = false; 1384 1385 private WriterChainJson(Collection<ICalendar> icals) { 1386 super(icals); 1387 } 1388 1389 /** 1390 * Sets whether or not to pretty-print the JSON. 1391 * @param indent true to pretty-print it, false not to (defaults to 1392 * false) 1393 * @return this 1394 */ 1395 public WriterChainJson indent(boolean indent) { 1396 this.indent = indent; 1397 return this_; 1398 } 1399 1400 /** 1401 * Writes the iCalendar objects to a string. 1402 * @return the iCalendar string 1403 * @throws IllegalArgumentException if the marshaller class for a 1404 * component or property object cannot be found (only happens when an 1405 * experimental property/component marshaller is not registered with the 1406 * {@code register} method.) 1407 */ 1408 public String go() { 1409 StringWriter sw = new StringWriter(); 1410 try { 1411 go(sw); 1412 } catch (IOException e) { 1413 //writing to a string 1414 } 1415 return sw.toString(); 1416 } 1417 1418 /** 1419 * Writes the iCalendar objects to a data stream. 1420 * @param out the output stream to write to 1421 * @throws IllegalArgumentException if the marshaller class for a 1422 * component or property object cannot be found (only happens when an 1423 * experimental property/component marshaller is not registered with the 1424 * {@code register} method.) 1425 * @throws IOException if there's a problem writing to the output stream 1426 */ 1427 public void go(OutputStream out) throws IOException { 1428 go(new JCalWriter(out, icals.size() > 1)); 1429 } 1430 1431 /** 1432 * Writes the iCalendar objects to a file. 1433 * @param file the file to write to 1434 * @throws IllegalArgumentException if the marshaller class for a 1435 * component or property object cannot be found (only happens when an 1436 * experimental property/component marshaller is not registered with the 1437 * {@code register} method.) 1438 * @throws IOException if there's a problem writing to the file 1439 */ 1440 public void go(File file) throws IOException { 1441 JCalWriter jcalWriter = new JCalWriter(file, icals.size() > 1); 1442 try { 1443 go(jcalWriter); 1444 } finally { 1445 IOUtils.closeQuietly(jcalWriter); 1446 } 1447 } 1448 1449 /** 1450 * Writes the iCalendar objects to a data stream. 1451 * @param writer the writer to write to 1452 * @throws IllegalArgumentException if the marshaller class for a 1453 * component or property object cannot be found (only happens when an 1454 * experimental property/component marshaller is not registered with the 1455 * {@code register} method.) 1456 * @throws IOException if there's a problem writing to the writer 1457 */ 1458 public void go(Writer writer) throws IOException { 1459 go(new JCalWriter(writer, icals.size() > 1)); 1460 } 1461 1462 private void go(JCalWriter jcalWriter) throws IOException { 1463 jcalWriter.setRegistrar(registrar); 1464 jcalWriter.setIndent(indent); 1465 1466 for (ICalendar ical : icals) { 1467 jcalWriter.write(ical); 1468 } 1469 jcalWriter.closeJsonStream(); 1470 } 1471 } 1472 1473 private Biweekly() { 1474 //hide 1475 } 1476 }