001 package biweekly; 002 003 import java.io.File; 004 import java.io.FileNotFoundException; 005 import java.io.FileReader; 006 import java.io.FileWriter; 007 import java.io.IOException; 008 import java.io.InputStream; 009 import java.io.InputStreamReader; 010 import java.io.OutputStream; 011 import java.io.OutputStreamWriter; 012 import java.io.Reader; 013 import java.io.StringWriter; 014 import java.io.Writer; 015 import java.util.ArrayList; 016 import java.util.Arrays; 017 import java.util.Collection; 018 import java.util.HashMap; 019 import java.util.List; 020 import java.util.Map; 021 import java.util.Properties; 022 023 import javax.xml.transform.TransformerException; 024 025 import org.w3c.dom.Document; 026 import org.xml.sax.SAXException; 027 028 import biweekly.component.ICalComponent; 029 import biweekly.component.marshaller.ICalComponentMarshaller; 030 import biweekly.io.text.ICalRawReader; 031 import biweekly.io.text.ICalRawWriter; 032 import biweekly.io.text.ICalReader; 033 import biweekly.io.text.ICalWriter; 034 import biweekly.io.xml.XCalDocument; 035 import biweekly.parameter.Value; 036 import biweekly.property.ICalProperty; 037 import biweekly.property.marshaller.ICalPropertyMarshaller; 038 import biweekly.util.IOUtils; 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> 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 * //writer 085 * Writer writer = ... 086 * Biweekly.write(ical).go(writer); 087 * writer.close(); 088 * </pre> 089 * 090 * </p> 091 * 092 * <p> 093 * <b>Writing multiple iCalendar objects</b> 094 * 095 * <pre> 096 * ICalendar ical1 = new ICalendar(); 097 * ICalendar ical2 = new ICalendar(); 098 * 099 * String icalString = Biweekly.write(ical1, ical2).go(); 100 * </pre> 101 * 102 * </p> 103 * 104 * <p> 105 * <b>Writing an XML-encoded iCalendar object (xCal)</b><br> 106 * 107 * <pre> 108 * //Call writeXml() instead of write() 109 * ICalendar ical = new ICalendar(); 110 * String xml = Biweekly.writeXml(ical).indent(2).go(); 111 * </pre> 112 * 113 * </p> 114 * 115 * <p> 116 * <b>Reading an iCalendar object</b> 117 * 118 * <pre> 119 * ICalendar ical; 120 * 121 * //string 122 * String icalStr = ... 123 * ical = Biweekly.parse(icalStr).first(); 124 * 125 * //file 126 * File file = new File("meeting.ics"); 127 * ical = Biweekly.parse(file).first(); 128 * 129 * //reader 130 * Reader reader = ... 131 * ical = Biweekly.parse(reader).first(); 132 * reader.close(); 133 * </pre> 134 * 135 * </p> 136 * 137 * <p> 138 * <b>Reading multiple iCalendar objects</b> 139 * 140 * <pre> 141 * String icalStr = ... 142 * List<ICalendar> icals = Biweekly.parse(icalStr).all(); 143 * </pre> 144 * 145 * </p> 146 * 147 * <p> 148 * <b>Reading an XML-encoded iCalendar object (xCal)</b><br> 149 * 150 * <pre> 151 * //Call parseXml() instead of parse() 152 * String xml = ... 153 * ICalendar ical = Biweekly.parseXml(xml).first(); 154 * </pre> 155 * 156 * </p> 157 * 158 * <p> 159 * <b>Retrieving parser warnings</b> 160 * 161 * <pre> 162 * String icalStr = ... 163 * List<List<String>> warnings = new ArrayList<List<String>>(); 164 * List<ICalendar> icals = Biweekly.parse(icalStr).warnings(warnings).all(); 165 * int i = 0; 166 * for (List<String> icalWarnings : warnings){ 167 * System.out.println("iCal #" + (i++) + " warnings:"); 168 * for (String warning : icalWarnings){ 169 * System.out.println(warning); 170 * } 171 * } 172 * </pre> 173 * 174 * </p> 175 * 176 * <p> 177 * The methods in this class make use of the following classes. These classes 178 * can be used if greater control over the read/write operation is required: 179 * </p> 180 * 181 * <style> table.t td, table.t th {border:1px solid #000;} </style> 182 * <table class="t" cellpadding="5" style="border-collapse:collapse;"> 183 * <tr> 184 * <th></th> 185 * <th>Classes</th> 186 * <th>Supports<br> 187 * streaming?</th> 188 * </tr> 189 * <tr> 190 * <th>Text</th> 191 * <td>{@link ICalReader} / {@link ICalWriter}</td> 192 * <td>yes</td> 193 * </tr> 194 * <tr> 195 * <th>XML</th> 196 * <td>{@link XCalDocument}</td> 197 * <td>no</td> 198 * </tr> 199 * </table> 200 * @author Michael Angstadt 201 */ 202 public class Biweekly { 203 /** 204 * The version of the library. 205 */ 206 public static final String VERSION; 207 208 /** 209 * The project webpage. 210 */ 211 public static final String URL; 212 213 static { 214 InputStream in = null; 215 try { 216 in = Biweekly.class.getResourceAsStream("/biweekly.properties"); 217 Properties props = new Properties(); 218 props.load(in); 219 220 VERSION = props.getProperty("version"); 221 URL = props.getProperty("url"); 222 } catch (IOException e) { 223 throw new RuntimeException(e); 224 } finally { 225 IOUtils.closeQuietly(in); 226 } 227 } 228 229 /** 230 * Parses an iCalendar object string. 231 * @param ical the iCalendar data 232 * @return chainer object for completing the parse operation 233 */ 234 public static ParserChainTextString parse(String ical) { 235 return new ParserChainTextString(ical); 236 } 237 238 /** 239 * Parses an iCalendar file. 240 * @return chainer object for completing the parse operation 241 * @throws FileNotFoundException if the file does not exist or cannot be 242 * accessed 243 */ 244 public static ParserChainTextReader parse(File file) throws FileNotFoundException { 245 return new ParserChainTextReader(new FileReader(file), true); //close the FileReader, since we created it 246 } 247 248 /** 249 * Parses an iCalendar data stream. 250 * @param in the input stream 251 * @return chainer object for completing the parse operation 252 */ 253 public static ParserChainTextReader parse(InputStream in) { 254 return parse(new InputStreamReader(in)); 255 } 256 257 /** 258 * Parses an iCalendar data stream. 259 * @param reader the reader 260 * @return chainer object for completing the parse operation 261 */ 262 public static ParserChainTextReader parse(Reader reader) { 263 return new ParserChainTextReader(reader, false); //do not close the Reader, since we didn't create it 264 } 265 266 /** 267 * Writes an iCalendar object to a data stream. 268 * @param ical the iCalendar object to write 269 * @return chainer object for completing the write operation 270 */ 271 public static WriterChainTextSingle write(ICalendar ical) { 272 return new WriterChainTextSingle(ical); 273 } 274 275 /** 276 * Writes multiple iCalendar objects to a data stream. 277 * @param icals the iCalendar objects to write 278 * @return chainer object for completing the write operation 279 */ 280 public static WriterChainTextMulti write(ICalendar... icals) { 281 return write(Arrays.asList(icals)); 282 } 283 284 /** 285 * Writes multiple iCalendar objects to a data stream. 286 * @param icals the iCalendar objects to write 287 * @return chainer object for completing the write operation 288 */ 289 public static WriterChainTextMulti write(Collection<ICalendar> icals) { 290 return new WriterChainTextMulti(icals); 291 } 292 293 /** 294 * Parses an xCal document (XML-encoded iCalendar objects). 295 * @param xml the XML string 296 * @return chainer object for completing the parse operation 297 */ 298 public static ParserChainXmlString parseXml(String xml) { 299 return new ParserChainXmlString(xml); 300 } 301 302 /** 303 * Parses an xCal document (XML-encoded iCalendar objects). 304 * @param file the XML file 305 * @return chainer object for completing the parse operation 306 * @throws FileNotFoundException if the file does not exist or cannot be 307 * accessed 308 */ 309 public static ParserChainXmlReader parseXml(File file) throws FileNotFoundException { 310 return new ParserChainXmlReader(file); //close the FileReader, since we created it 311 } 312 313 /** 314 * Parses an xCal document (XML-encoded iCalendar objects). 315 * @param in the input stream 316 * @return chainer object for completing the parse operation 317 */ 318 public static ParserChainXmlReader parseXml(InputStream in) { 319 return parseXml(new InputStreamReader(in)); 320 } 321 322 /** 323 * Parses an xCal document (XML-encoded iCalendar objects). 324 * @param reader the reader 325 * @return chainer object for completing the parse operation 326 */ 327 public static ParserChainXmlReader parseXml(Reader reader) { 328 return new ParserChainXmlReader(reader); 329 } 330 331 /** 332 * Parses an xCal document (XML-encoded iCalendar objects). 333 * @param document the XML document 334 * @return chainer object for completing the parse operation 335 */ 336 public static ParserChainXmlDocument parseXml(Document document) { 337 return new ParserChainXmlDocument(document); 338 } 339 340 /** 341 * Writes an xCal document (XML-encoded iCalendar objects). 342 * @param icals the iCalendar object(s) to write 343 * @return chainer object for completing the write operation 344 */ 345 public static WriterChainXml writeXml(ICalendar... icals) { 346 return writeXml(Arrays.asList(icals)); 347 } 348 349 /** 350 * Writes an xCal document (XML-encoded iCalendar objects). 351 * @param icals the iCalendar objects to write 352 * @return chainer object for completing the write operation 353 */ 354 public static WriterChainXml writeXml(Collection<ICalendar> icals) { 355 return new WriterChainXml(icals); 356 } 357 358 static abstract class ParserChain<T> { 359 //Note: "package" level is used so various fields/methods don't show up in the Javadocs, but are still visible to child classes 360 final List<ICalPropertyMarshaller<? extends ICalProperty>> propertyMarshallers = new ArrayList<ICalPropertyMarshaller<? extends ICalProperty>>(0); 361 final List<ICalComponentMarshaller<? extends ICalComponent>> componentMarshallers = new ArrayList<ICalComponentMarshaller<? extends ICalComponent>>(0); 362 363 @SuppressWarnings("unchecked") 364 final T this_ = (T) this; 365 366 List<List<String>> warnings; 367 368 /** 369 * Registers a property marshaller. 370 * @param marshaller the marshaller 371 * @return this 372 */ 373 public T register(ICalPropertyMarshaller<? extends ICalProperty> marshaller) { 374 propertyMarshallers.add(marshaller); 375 return this_; 376 } 377 378 /** 379 * Registers a component marshaller. 380 * @param marshaller the marshaller 381 * @return this 382 */ 383 public T register(ICalComponentMarshaller<? extends ICalComponent> marshaller) { 384 componentMarshallers.add(marshaller); 385 return this_; 386 } 387 388 /** 389 * Provides a list for putting the parser warnings into. 390 * @param warnings the list object to populate (it is a 391 * "list of lists"--each parsed {@link ICalendar} object has its own 392 * warnings list) 393 * @return this 394 */ 395 public T warnings(List<List<String>> warnings) { 396 this.warnings = warnings; 397 return this_; 398 } 399 400 /** 401 * Reads the first iCalendar object from the data stream. 402 * @return the first iCalendar object or null if there are none 403 * @throws IOException if there a problem reading from the data stream 404 * @throws SAXException if there's a problem parsing the XML 405 */ 406 public abstract ICalendar first() throws IOException, SAXException; 407 408 /** 409 * Reads all iCalendar objects from the data stream. 410 * @return the parsed iCalendar objects 411 * @throws IOException if there's a problem reading from the data stream 412 * @throws SAXException if there's a problem parsing the XML 413 */ 414 public abstract List<ICalendar> all() throws IOException, SAXException; 415 } 416 417 /////////////////////////////////////////////////////// 418 // plain-text 419 /////////////////////////////////////////////////////// 420 421 static abstract class ParserChainText<T> extends ParserChain<T> { 422 boolean caretDecoding = true; 423 final boolean closeWhenDone; 424 425 private ParserChainText(boolean closeWhenDone) { 426 this.closeWhenDone = closeWhenDone; 427 } 428 429 /** 430 * Sets whether the reader will decode parameter values that use 431 * circumflex accent encoding (enabled by default). This escaping 432 * mechanism allows newlines and double quotes to be included in 433 * parameter values. 434 * @param enable true to use circumflex accent decoding, false not to 435 * @see ICalRawReader#setCaretDecodingEnabled(boolean) 436 */ 437 public T caretDecoding(boolean enable) { 438 caretDecoding = enable; 439 return this_; 440 } 441 442 @Override 443 public ICalendar first() throws IOException { 444 ICalReader parser = constructReader(); 445 446 try { 447 ICalendar ical = parser.readNext(); 448 if (warnings != null) { 449 warnings.add(parser.getWarnings()); 450 } 451 return ical; 452 } finally { 453 if (closeWhenDone) { 454 IOUtils.closeQuietly(parser); 455 } 456 } 457 } 458 459 @Override 460 public List<ICalendar> all() throws IOException { 461 ICalReader parser = constructReader(); 462 463 try { 464 List<ICalendar> icals = new ArrayList<ICalendar>(); 465 ICalendar ical; 466 while ((ical = parser.readNext()) != null) { 467 if (warnings != null) { 468 warnings.add(parser.getWarnings()); 469 } 470 icals.add(ical); 471 } 472 return icals; 473 } finally { 474 if (closeWhenDone) { 475 IOUtils.closeQuietly(parser); 476 } 477 } 478 } 479 480 private ICalReader constructReader() { 481 ICalReader parser = _constructReader(); 482 for (ICalPropertyMarshaller<? extends ICalProperty> marshaller : propertyMarshallers) { 483 parser.registerMarshaller(marshaller); 484 } 485 for (ICalComponentMarshaller<? extends ICalComponent> marshaller : componentMarshallers) { 486 parser.registerMarshaller(marshaller); 487 } 488 parser.setCaretDecodingEnabled(caretDecoding); 489 return parser; 490 } 491 492 abstract ICalReader _constructReader(); 493 } 494 495 /** 496 * Chainer class for parsing plain text iCalendar data streams. 497 * @see Biweekly#parse(InputStream) 498 * @see Biweekly#parse(File) 499 * @see Biweekly#parse(Reader) 500 */ 501 public static class ParserChainTextReader extends ParserChainText<ParserChainTextReader> { 502 private final Reader reader; 503 504 private ParserChainTextReader(Reader reader, boolean closeWhenDone) { 505 super(closeWhenDone); 506 this.reader = reader; 507 } 508 509 @Override 510 public ParserChainTextReader register(ICalPropertyMarshaller<? extends ICalProperty> marshaller) { 511 return super.register(marshaller); 512 } 513 514 @Override 515 public ParserChainTextReader register(ICalComponentMarshaller<? extends ICalComponent> marshaller) { 516 return super.register(marshaller); 517 } 518 519 @Override 520 public ParserChainTextReader warnings(List<List<String>> warnings) { 521 return super.warnings(warnings); 522 } 523 524 @Override 525 public ParserChainTextReader caretDecoding(boolean enable) { 526 return super.caretDecoding(enable); 527 } 528 529 @Override 530 ICalReader _constructReader() { 531 return new ICalReader(reader); 532 } 533 } 534 535 /** 536 * Chainer class for parsing plain text iCalendar strings. 537 * @see Biweekly#parse(String) 538 */ 539 public static class ParserChainTextString extends ParserChainText<ParserChainTextString> { 540 private final String text; 541 542 private ParserChainTextString(String text) { 543 super(false); 544 this.text = text; 545 } 546 547 @Override 548 public ParserChainTextString register(ICalPropertyMarshaller<? extends ICalProperty> marshaller) { 549 return super.register(marshaller); 550 } 551 552 @Override 553 public ParserChainTextString register(ICalComponentMarshaller<? extends ICalComponent> marshaller) { 554 return super.register(marshaller); 555 } 556 557 @Override 558 public ParserChainTextString caretDecoding(boolean enable) { 559 return super.caretDecoding(enable); 560 } 561 562 @Override 563 ICalReader _constructReader() { 564 return new ICalReader(text); 565 } 566 567 @Override 568 public ICalendar first() { 569 try { 570 return super.first(); 571 } catch (IOException e) { 572 //reading from string 573 } 574 return null; 575 } 576 577 @Override 578 public List<ICalendar> all() { 579 try { 580 return super.all(); 581 } catch (IOException e) { 582 //reading from string 583 } 584 return null; 585 } 586 } 587 588 /////////////////////////////////////////////////////// 589 // XML 590 /////////////////////////////////////////////////////// 591 592 static abstract class ParserChainXml<T> extends ParserChain<T> { 593 @Override 594 public ICalendar first() throws IOException, SAXException { 595 XCalDocument document = constructDocument(); 596 ICalendar ical = document.parseFirst(); 597 if (warnings != null) { 598 warnings.addAll(document.getParseWarnings()); 599 } 600 return ical; 601 } 602 603 @Override 604 public List<ICalendar> all() throws IOException, SAXException { 605 XCalDocument document = constructDocument(); 606 List<ICalendar> icals = document.parseAll(); 607 if (warnings != null) { 608 warnings.addAll(document.getParseWarnings()); 609 } 610 return icals; 611 } 612 613 private XCalDocument constructDocument() throws SAXException, IOException { 614 XCalDocument parser = _constructDocument(); 615 for (ICalPropertyMarshaller<? extends ICalProperty> marshaller : propertyMarshallers) { 616 parser.registerMarshaller(marshaller); 617 } 618 for (ICalComponentMarshaller<? extends ICalComponent> marshaller : componentMarshallers) { 619 parser.registerMarshaller(marshaller); 620 } 621 return parser; 622 } 623 624 abstract XCalDocument _constructDocument() throws IOException, SAXException; 625 } 626 627 /** 628 * Chainer class for parsing XML-encoded iCalendar objects (xCal). 629 * @see Biweekly#parseXml(String) 630 */ 631 public static class ParserChainXmlString extends ParserChainXml<ParserChainXmlString> { 632 private final String xml; 633 634 public ParserChainXmlString(String xml) { 635 this.xml = xml; 636 } 637 638 @Override 639 public ParserChainXmlString register(ICalPropertyMarshaller<? extends ICalProperty> marshaller) { 640 return super.register(marshaller); 641 } 642 643 @Override 644 public ParserChainXmlString register(ICalComponentMarshaller<? extends ICalComponent> marshaller) { 645 return super.register(marshaller); 646 } 647 648 @Override 649 XCalDocument _constructDocument() throws SAXException { 650 return new XCalDocument(xml); 651 } 652 653 @Override 654 public ICalendar first() throws SAXException { 655 try { 656 return super.first(); 657 } catch (IOException e) { 658 //reading from string 659 } 660 return null; 661 } 662 663 @Override 664 public List<ICalendar> all() throws SAXException { 665 try { 666 return super.all(); 667 } catch (IOException e) { 668 //reading from string 669 } 670 return null; 671 } 672 } 673 674 /** 675 * Chainer class for parsing XML-encoded iCalendar objects (xCal). 676 * @see Biweekly#parseXml(InputStream) 677 * @see Biweekly#parseXml(File) 678 * @see Biweekly#parseXml(Reader) 679 */ 680 public static class ParserChainXmlReader extends ParserChainXml<ParserChainXmlReader> { 681 private final Reader reader; 682 private final File file; 683 684 public ParserChainXmlReader(Reader reader) { 685 this.reader = reader; 686 this.file = null; 687 } 688 689 public ParserChainXmlReader(File file) { 690 this.reader = null; 691 this.file = file; 692 } 693 694 @Override 695 public ParserChainXmlReader register(ICalPropertyMarshaller<? extends ICalProperty> marshaller) { 696 return super.register(marshaller); 697 } 698 699 @Override 700 public ParserChainXmlReader register(ICalComponentMarshaller<? extends ICalComponent> marshaller) { 701 return super.register(marshaller); 702 } 703 704 @Override 705 XCalDocument _constructDocument() throws IOException, SAXException { 706 return (reader == null) ? new XCalDocument(file) : new XCalDocument(reader); 707 } 708 } 709 710 /** 711 * Chainer class for parsing XML-encoded iCalendar objects (xCal). 712 * @see Biweekly#parseXml(Document) 713 */ 714 public static class ParserChainXmlDocument extends ParserChainXml<ParserChainXmlDocument> { 715 private final Document document; 716 717 public ParserChainXmlDocument(Document document) { 718 this.document = document; 719 } 720 721 @Override 722 public ParserChainXmlDocument register(ICalPropertyMarshaller<? extends ICalProperty> marshaller) { 723 return super.register(marshaller); 724 } 725 726 @Override 727 public ParserChainXmlDocument register(ICalComponentMarshaller<? extends ICalComponent> marshaller) { 728 return super.register(marshaller); 729 } 730 731 @Override 732 XCalDocument _constructDocument() { 733 return new XCalDocument(document); 734 } 735 736 @Override 737 public ICalendar first() { 738 try { 739 return super.first(); 740 } catch (IOException e) { 741 //reading from string 742 } catch (SAXException e) { 743 //reading from Document 744 } 745 return null; 746 } 747 748 @Override 749 public List<ICalendar> all() { 750 try { 751 return super.all(); 752 } catch (IOException e) { 753 //reading from string 754 } catch (SAXException e) { 755 //reading from Document 756 } 757 return null; 758 } 759 } 760 761 static abstract class WriterChain<T> { 762 final Collection<ICalendar> icals; 763 final List<ICalPropertyMarshaller<? extends ICalProperty>> propertyMarshallers = new ArrayList<ICalPropertyMarshaller<? extends ICalProperty>>(0); 764 final List<ICalComponentMarshaller<? extends ICalComponent>> componentMarshallers = new ArrayList<ICalComponentMarshaller<? extends ICalComponent>>(0); 765 766 @SuppressWarnings("unchecked") 767 final T this_ = (T) this; 768 769 WriterChain(Collection<ICalendar> icals) { 770 this.icals = icals; 771 } 772 773 /** 774 * Registers a property marshaller. 775 * @param marshaller the marshaller 776 * @return this 777 */ 778 public T register(ICalPropertyMarshaller<? extends ICalProperty> marshaller) { 779 propertyMarshallers.add(marshaller); 780 return this_; 781 } 782 783 /** 784 * Registers a component marshaller. 785 * @param marshaller the marshaller 786 * @return this 787 */ 788 public T register(ICalComponentMarshaller<? extends ICalComponent> marshaller) { 789 componentMarshallers.add(marshaller); 790 return this_; 791 } 792 } 793 794 /////////////////////////////////////////////////////// 795 // plain-text 796 /////////////////////////////////////////////////////// 797 798 static abstract class WriterChainText<T> extends WriterChain<T> { 799 boolean caretEncoding = false; 800 801 WriterChainText(Collection<ICalendar> icals) { 802 super(icals); 803 } 804 805 /** 806 * <p> 807 * Sets whether the writer will apply circumflex accent encoding on 808 * parameter values (disabled by default). This escaping mechanism 809 * allows for newlines and double quotes to be included in parameter 810 * values. 811 * </p> 812 * 813 * <p> 814 * When disabled, the writer will replace newlines with spaces and 815 * double quotes with single quotes. 816 * </p> 817 * @param enable true to use circumflex accent encoding, false not to 818 * @see ICalRawWriter#setCaretEncodingEnabled(boolean) 819 */ 820 public T caretEncoding(boolean enable) { 821 this.caretEncoding = enable; 822 return this_; 823 } 824 825 /** 826 * Writes the iCalendar objects to a string. 827 * @return the iCalendar string 828 */ 829 public String go() { 830 StringWriter sw = new StringWriter(); 831 try { 832 go(sw); 833 } catch (IOException e) { 834 //writing to a string 835 } 836 return sw.toString(); 837 } 838 839 /** 840 * Writes the iCalendar objects to a data stream. 841 * @param out the output stream to write to 842 * @throws IOException if there's a problem writing to the output stream 843 */ 844 public void go(OutputStream out) throws IOException { 845 go(new OutputStreamWriter(out)); 846 } 847 848 /** 849 * Writes the iCalendar objects to a file. 850 * @param file the file to write to 851 * @throws IOException if there's a problem writing to the file 852 */ 853 public void go(File file) throws IOException { 854 FileWriter writer = null; 855 try { 856 writer = new FileWriter(file); 857 go(writer); 858 } finally { 859 IOUtils.closeQuietly(writer); 860 } 861 } 862 863 /** 864 * Writes the iCalendar objects to a data stream. 865 * @param writer the writer to write to 866 * @throws IOException if there's a problem writing to the writer 867 */ 868 public void go(Writer writer) throws IOException { 869 ICalWriter icalWriter = new ICalWriter(writer); 870 for (ICalPropertyMarshaller<? extends ICalProperty> marshaller : propertyMarshallers) { 871 icalWriter.registerMarshaller(marshaller); 872 } 873 for (ICalComponentMarshaller<? extends ICalComponent> marshaller : componentMarshallers) { 874 icalWriter.registerMarshaller(marshaller); 875 } 876 icalWriter.setCaretEncodingEnabled(caretEncoding); 877 878 for (ICalendar ical : icals) { 879 icalWriter.write(ical); 880 addWarnings(icalWriter.getWarnings()); 881 } 882 } 883 884 abstract void addWarnings(List<String> warnings); 885 } 886 887 /** 888 * Chainer class for writing to plain text iCalendar data streams. 889 * @see Biweekly#write(Collection) 890 * @see Biweekly#write(ICalendar...) 891 */ 892 public static class WriterChainTextMulti extends WriterChainText<WriterChainTextMulti> { 893 private List<List<String>> warnings; 894 895 private WriterChainTextMulti(Collection<ICalendar> icals) { 896 super(icals); 897 } 898 899 @Override 900 public WriterChainTextMulti caretEncoding(boolean enable) { 901 return super.caretEncoding(enable); 902 } 903 904 /** 905 * Provides a list for putting the marshal warnings into. 906 * @param warnings the list object to populate (it is a 907 * "list of lists"--each {@link ICalendar} object has its own warnings 908 * list) 909 * @return this 910 */ 911 public WriterChainTextMulti warnings(List<List<String>> warnings) { 912 this.warnings = warnings; 913 return this; 914 } 915 916 @Override 917 public WriterChainTextMulti register(ICalPropertyMarshaller<? extends ICalProperty> marshaller) { 918 return super.register(marshaller); 919 } 920 921 @Override 922 public WriterChainTextMulti register(ICalComponentMarshaller<? extends ICalComponent> marshaller) { 923 return super.register(marshaller); 924 } 925 926 @Override 927 void addWarnings(List<String> warnings) { 928 if (this.warnings != null) { 929 this.warnings.add(warnings); 930 } 931 } 932 } 933 934 /** 935 * Chainer class for writing to plain text iCalendar data streams. 936 * @see Biweekly#write(ICalendar) 937 */ 938 public static class WriterChainTextSingle extends WriterChainText<WriterChainTextSingle> { 939 private List<String> warnings; 940 941 private WriterChainTextSingle(ICalendar ical) { 942 super(Arrays.asList(ical)); 943 } 944 945 @Override 946 public WriterChainTextSingle caretEncoding(boolean enable) { 947 return super.caretEncoding(enable); 948 } 949 950 /** 951 * Provides a list for putting the parser warnings into. 952 * @param warnings the list object to populate 953 * @return this 954 */ 955 public WriterChainTextSingle warnings(List<String> warnings) { 956 this.warnings = warnings; 957 return this; 958 } 959 960 @Override 961 public WriterChainTextSingle register(ICalPropertyMarshaller<? extends ICalProperty> marshaller) { 962 return super.register(marshaller); 963 } 964 965 @Override 966 public WriterChainTextSingle register(ICalComponentMarshaller<? extends ICalComponent> marshaller) { 967 return super.register(marshaller); 968 } 969 970 @Override 971 void addWarnings(List<String> warnings) { 972 if (this.warnings != null) { 973 this.warnings.addAll(warnings); 974 } 975 } 976 } 977 978 /////////////////////////////////////////////////////// 979 // XML 980 /////////////////////////////////////////////////////// 981 982 /** 983 * Chainer class for writing xCal documents (XML-encoded iCalendar objects). 984 * @see Biweekly#writeXml(Collection) 985 * @see Biweekly#writeXml(ICalendar...) 986 */ 987 public static class WriterChainXml extends WriterChain<WriterChainXml> { 988 int indent = -1; 989 final Map<String, Value> parameterDataTypes = new HashMap<String, Value>(0); 990 991 WriterChainXml(Collection<ICalendar> icals) { 992 super(icals); 993 } 994 995 @Override 996 public WriterChainXml register(ICalPropertyMarshaller<? extends ICalProperty> marshaller) { 997 return super.register(marshaller); 998 } 999 1000 @Override 1001 public WriterChainXml register(ICalComponentMarshaller<? extends ICalComponent> marshaller) { 1002 return super.register(marshaller); 1003 } 1004 1005 /** 1006 * Registers the data type of an experimental parameter. Experimental 1007 * parameters use the "unknown" xCal data type by default. 1008 * @param parameterName the parameter name (e.g. "x-foo") 1009 * @param dataType the data type 1010 */ 1011 public WriterChainXml register(String parameterName, Value dataType) { 1012 parameterDataTypes.put(parameterName, dataType); 1013 return this_; 1014 } 1015 1016 /** 1017 * Sets the number of indent spaces to use for pretty-printing. If not 1018 * set, then the XML will not be pretty-printed. 1019 * @param indent the number of spaces 1020 * @return this 1021 */ 1022 public WriterChainXml indent(int indent) { 1023 this.indent = indent; 1024 return this_; 1025 } 1026 1027 /** 1028 * Writes the xCal document to a string. 1029 * @return the XML string 1030 */ 1031 public String go() { 1032 StringWriter sw = new StringWriter(); 1033 try { 1034 go(sw); 1035 } catch (TransformerException e) { 1036 //writing to a string 1037 } 1038 return sw.toString(); 1039 } 1040 1041 /** 1042 * Writes the xCal document to an output stream. 1043 * @param out the output stream to write to 1044 * @throws TransformerException if there's a problem writing the XML 1045 */ 1046 public void go(OutputStream out) throws TransformerException { 1047 go(new OutputStreamWriter(out)); 1048 } 1049 1050 /** 1051 * Writes the xCal document to a file.. 1052 * @param file the file to write to 1053 * @throws TransformerException if there's a problem writing the XML 1054 * @throws IOException if there's a problem writing to the file 1055 */ 1056 public void go(File file) throws TransformerException, IOException { 1057 XCalDocument document = constructDocument(); 1058 document.write(file, indent); 1059 } 1060 1061 /** 1062 * Writes the xCal document to a writer. 1063 * @param writer the writer to write to 1064 * @throws TransformerException if there's a problem writing the XML 1065 */ 1066 public void go(Writer writer) throws TransformerException { 1067 XCalDocument document = constructDocument(); 1068 document.write(writer, indent); 1069 } 1070 1071 /** 1072 * Writes the xCal document to an XML DOM. 1073 * @return the XML DOM 1074 */ 1075 public Document dom() { 1076 XCalDocument document = constructDocument(); 1077 return document.getDocument(); 1078 } 1079 1080 private XCalDocument constructDocument() { 1081 XCalDocument document = new XCalDocument(); 1082 1083 for (ICalPropertyMarshaller<? extends ICalProperty> marshaller : propertyMarshallers) { 1084 document.registerMarshaller(marshaller); 1085 } 1086 for (ICalComponentMarshaller<? extends ICalComponent> marshaller : componentMarshallers) { 1087 document.registerMarshaller(marshaller); 1088 } 1089 for (Map.Entry<String, Value> entry : parameterDataTypes.entrySet()) { 1090 document.registerParameterDataType(entry.getKey(), entry.getValue()); 1091 } 1092 1093 for (ICalendar ical : icals) { 1094 document.add(ical); 1095 } 1096 1097 return document; 1098 } 1099 } 1100 1101 private Biweekly() { 1102 //hide 1103 } 1104 }