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 }