001    package ezvcard;
002    
003    import java.io.File;
004    import java.io.FileWriter;
005    import java.io.IOException;
006    import java.io.InputStream;
007    import java.io.InputStreamReader;
008    import java.io.OutputStream;
009    import java.io.OutputStreamWriter;
010    import java.io.Reader;
011    import java.io.StringWriter;
012    import java.io.Writer;
013    import java.net.URL;
014    import java.nio.charset.Charset;
015    import java.util.ArrayList;
016    import java.util.Arrays;
017    import java.util.Collection;
018    import java.util.List;
019    import java.util.Properties;
020    
021    import javax.xml.transform.TransformerException;
022    
023    import org.w3c.dom.Document;
024    import org.xml.sax.SAXException;
025    
026    import com.fasterxml.jackson.core.JsonParseException;
027    
028    import ezvcard.io.html.HCardPage;
029    import ezvcard.io.html.HCardReader;
030    import ezvcard.io.json.JCardParseException;
031    import ezvcard.io.json.JCardReader;
032    import ezvcard.io.json.JCardWriter;
033    import ezvcard.io.scribe.ScribeIndex;
034    import ezvcard.io.scribe.VCardPropertyScribe;
035    import ezvcard.io.text.VCardReader;
036    import ezvcard.io.text.VCardWriter;
037    import ezvcard.io.xml.XCardDocument;
038    import ezvcard.property.VCardProperty;
039    import ezvcard.util.IOUtils;
040    
041    /*
042     Copyright (c) 2013, Michael Angstadt
043     All rights reserved.
044    
045     Redistribution and use in source and binary forms, with or without
046     modification, are permitted provided that the following conditions are met: 
047    
048     1. Redistributions of source code must retain the above copyright notice, this
049     list of conditions and the following disclaimer. 
050     2. Redistributions in binary form must reproduce the above copyright notice,
051     this list of conditions and the following disclaimer in the documentation
052     and/or other materials provided with the distribution. 
053    
054     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
055     ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
056     WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
057     DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
058     ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
059     (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
060     LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
061     ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
062     (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
063     SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
064    
065     The views and conclusions contained in the software and documentation are those
066     of the authors and should not be interpreted as representing official policies, 
067     either expressed or implied, of the FreeBSD Project.
068     */
069    
070    /**
071     * <p>
072     * Contains chaining factory methods for parsing/writing vCards. They are
073     * convenience methods that make use of the following classes:
074     * </p>
075     * 
076     * 
077     * <table border="1">
078     * <tr>
079     * <th></th>
080     * <th>Reading</th>
081     * <th>Writing</th>
082     * </tr>
083     * <tr>
084     * <th>Plain text</th>
085     * <td>{@link VCardReader}</td>
086     * <td>{@link VCardWriter}</td>
087     * </tr>
088     * <tr>
089     * <th>XML</th>
090     * <td>{@link XCardDocument}</td>
091     * <td>{@link XCardDocument}</td>
092     * </tr>
093     * <tr>
094     * <th>HTML</th>
095     * <td>{@link HCardReader}</td>
096     * <td>{@link HCardPage}</td>
097     * </tr>
098     * <tr>
099     * <th>JSON</th>
100     * <td>{@link JCardReader}</td>
101     * <td>{@link JCardWriter}</td>
102     * </tr>
103     * </table>
104     * @author Michael Angstadt
105     */
106    public class Ezvcard {
107            /**
108             * The version of the library.
109             */
110            public static final String VERSION;
111    
112            /**
113             * The project webpage.
114             */
115            public static final String URL;
116    
117            static {
118                    InputStream in = null;
119                    try {
120                            in = Ezvcard.class.getResourceAsStream("/ez-vcard.properties");
121                            Properties props = new Properties();
122                            props.load(in);
123    
124                            VERSION = props.getProperty("version");
125                            URL = props.getProperty("url");
126                    } catch (IOException e) {
127                            throw new RuntimeException(e);
128                    } finally {
129                            IOUtils.closeQuietly(in);
130                    }
131            }
132    
133            /**
134             * <p>
135             * Parses plain text vCards.
136             * </p>
137             * <p>
138             * Use {@link VCardReader} for more control over the parsing.
139             * </p>
140             * @param str the vCard string
141             * @return chainer object for completing the parse operation
142             * @see VCardReader
143             * @see <a href="http://www.imc.org/pdi/vcard-21.rtf">vCard 2.1</a>
144             * @see <a href="http://tools.ietf.org/html/rfc2426">RFC 2426 (3.0)</a>
145             * @see <a href="http://tools.ietf.org/html/rfc6350">RFC 6350 (4.0)</a>
146             */
147            public static ParserChainTextString parse(String str) {
148                    return new ParserChainTextString(str);
149            }
150    
151            /**
152             * <p>
153             * Parses plain text vCards.
154             * </p>
155             * <p>
156             * Use {@link VCardReader} for more control over the parsing.
157             * </p>
158             * @param file the vCard file
159             * @return chainer object for completing the parse operation
160             * @see VCardReader
161             * @see <a href="http://www.imc.org/pdi/vcard-21.rtf">vCard 2.1</a>
162             * @see <a href="http://tools.ietf.org/html/rfc2426">RFC 2426 (3.0)</a>
163             * @see <a href="http://tools.ietf.org/html/rfc6350">RFC 6350 (4.0)</a>
164             */
165            public static ParserChainTextReader parse(File file) {
166                    return new ParserChainTextReader(file);
167            }
168    
169            /**
170             * <p>
171             * Parses plain text vCards.
172             * </p>
173             * <p>
174             * Use {@link VCardReader} for more control over the parsing.
175             * </p>
176             * @param in the input stream
177             * @return chainer object for completing the parse operation
178             * @see VCardReader
179             * @see <a href="http://www.imc.org/pdi/vcard-21.rtf">vCard 2.1</a>
180             * @see <a href="http://tools.ietf.org/html/rfc2426">RFC 2426 (3.0)</a>
181             * @see <a href="http://tools.ietf.org/html/rfc6350">RFC 6350 (4.0)</a>
182             */
183            public static ParserChainTextReader parse(InputStream in) {
184                    return parse(new InputStreamReader(in));
185            }
186    
187            /**
188             * <p>
189             * Parses plain text vCards.
190             * </p>
191             * <p>
192             * Use {@link VCardReader} for more control over the parsing.
193             * </p>
194             * @param reader the reader
195             * @return chainer object for completing the parse operation
196             * @see VCardReader
197             * @see <a href="http://www.imc.org/pdi/vcard-21.rtf">vCard 2.1</a>
198             * @see <a href="http://tools.ietf.org/html/rfc2426">RFC 2426 (3.0)</a>
199             * @see <a href="http://tools.ietf.org/html/rfc6350">RFC 6350 (4.0)</a>
200             */
201            public static ParserChainTextReader parse(Reader reader) {
202                    return new ParserChainTextReader(reader);
203            }
204    
205            /**
206             * <p>
207             * Parses XML-encoded vCards (xCard) from a string.
208             * </p>
209             * <p>
210             * Use {@link XCardDocument} for more control over the parsing.
211             * </p>
212             * @param xml the XML document
213             * @return chainer object for completing the parse operation
214             * @see XCardDocument
215             * @see <a href="http://tools.ietf.org/html/rfc6351">RFC 6351</a>
216             */
217            public static ParserChainXmlString parseXml(String xml) {
218                    return new ParserChainXmlString(xml);
219            }
220    
221            /**
222             * <p>
223             * Parses XML-encoded vCards (xCard) from a file.
224             * </p>
225             * <p>
226             * Use {@link XCardDocument} for more control over the parsing.
227             * </p>
228             * @param file the XML file
229             * @return chainer object for completing the parse operation
230             * @see XCardDocument
231             * @see <a href="http://tools.ietf.org/html/rfc6351">RFC 6351</a>
232             */
233            public static ParserChainXmlReader parseXml(File file) {
234                    return new ParserChainXmlReader(file);
235            }
236    
237            /**
238             * <p>
239             * Parses XML-encoded vCards (xCard) from an input stream.
240             * </p>
241             * <p>
242             * Use {@link XCardDocument} for more control over the parsing.
243             * </p>
244             * @param in the input stream to the XML document
245             * @return chainer object for completing the parse operation
246             * @see XCardDocument
247             * @see <a href="http://tools.ietf.org/html/rfc6351">RFC 6351</a>
248             */
249            public static ParserChainXmlReader parseXml(InputStream in) {
250                    return new ParserChainXmlReader(in);
251            }
252    
253            /**
254             * <p>
255             * Parses XML-encoded vCards (xCard) from a reader.
256             * </p>
257             * <p>
258             * Note that use of this method is discouraged. It ignores the character
259             * encoding that is defined within the XML document itself, and should only
260             * be used if the encoding is undefined or if the encoding needs to be
261             * ignored for whatever reason. The {@link #parseXml(InputStream)} method
262             * should be used instead, since it takes the XML document's character
263             * encoding into account when parsing.
264             * </p>
265             * <p>
266             * Use {@link XCardDocument} for more control over the parsing.
267             * </p>
268             * @param reader the reader to the XML document
269             * @return chainer object for completing the parse operation
270             * @see XCardDocument
271             * @see <a href="http://tools.ietf.org/html/rfc6351">RFC 6351</a>
272             */
273            public static ParserChainXmlReader parseXml(Reader reader) {
274                    return new ParserChainXmlReader(reader);
275            }
276    
277            /**
278             * <p>
279             * Parses XML-encoded vCards (xCard).
280             * </p>
281             * <p>
282             * Use {@link XCardDocument} for more control over the parsing.
283             * </p>
284             * @param document the XML document
285             * @return chainer object for completing the parse operation
286             * @see XCardDocument
287             * @see <a href="http://tools.ietf.org/html/rfc6351">RFC 6351</a>
288             */
289            public static ParserChainXmlDom parseXml(Document document) {
290                    return new ParserChainXmlDom(document);
291            }
292    
293            /**
294             * <p>
295             * Parses HTML-encoded vCards (hCard).
296             * </p>
297             * <p>
298             * Use {@link HCardReader} for more control over the parsing.
299             * </p>
300             * @param html the HTML page
301             * @return chainer object for completing the parse operation
302             * @see HCardReader
303             * @see <a href="http://microformats.org/wiki/hcard">hCard 1.0</a>
304             */
305            public static ParserChainHtmlString parseHtml(String html) {
306                    return new ParserChainHtmlString(html);
307            }
308    
309            /**
310             * <p>
311             * Parses HTML-encoded vCards (hCard).
312             * </p>
313             * <p>
314             * Use {@link HCardReader} for more control over the parsing.
315             * </p>
316             * @param file the HTML file
317             * @return chainer object for completing the parse operation
318             * @see HCardReader
319             * @see <a href="http://microformats.org/wiki/hcard">hCard 1.0</a>
320             */
321            public static ParserChainHtmlReader parseHtml(File file) {
322                    return new ParserChainHtmlReader(file);
323            }
324    
325            /**
326             * <p>
327             * Parses HTML-encoded vCards (hCard).
328             * </p>
329             * <p>
330             * Use {@link HCardReader} for more control over the parsing.
331             * </p>
332             * @param in the input stream to the HTML page
333             * @return chainer object for completing the parse operation
334             * @see HCardReader
335             * @see <a href="http://microformats.org/wiki/hcard">hCard 1.0</a>
336             */
337            public static ParserChainHtmlReader parseHtml(InputStream in) {
338                    return parseHtml(new InputStreamReader(in));
339            }
340    
341            /**
342             * <p>
343             * Parses HTML-encoded vCards (hCard).
344             * </p>
345             * <p>
346             * Use {@link HCardReader} for more control over the parsing.
347             * </p>
348             * @param reader the reader to the HTML page
349             * @return chainer object for completing the parse operation
350             * @see HCardReader
351             * @see <a href="http://microformats.org/wiki/hcard">hCard 1.0</a>
352             */
353            public static ParserChainHtmlReader parseHtml(Reader reader) {
354                    return new ParserChainHtmlReader(reader);
355            }
356    
357            /**
358             * <p>
359             * Parses HTML-encoded vCards (hCard).
360             * </p>
361             * <p>
362             * Use {@link HCardReader} for more control over the parsing.
363             * </p>
364             * @param url the URL of the webpage
365             * @return chainer object for completing the parse operation
366             * @see HCardReader
367             * @see <a href="http://microformats.org/wiki/hcard">hCard 1.0</a>
368             */
369            public static ParserChainHtmlReader parseHtml(URL url) {
370                    return new ParserChainHtmlReader(url);
371            }
372    
373            /**
374             * <p>
375             * Parses JSON-encoded vCards (jCard).
376             * </p>
377             * <p>
378             * Use {@link JCardReader} for more control over the parsing.
379             * </p>
380             * @param json the JSON string
381             * @return chainer object for completing the parse operation
382             * @see JCardReader
383             * @see <a href="http://tools.ietf.org/html/rfc7095">RFC 7095</a>
384             */
385            public static ParserChainJsonString parseJson(String json) {
386                    return new ParserChainJsonString(json);
387            }
388    
389            /**
390             * <p>
391             * Parses JSON-encoded vCards (jCard).
392             * </p>
393             * <p>
394             * Use {@link JCardReader} for more control over the parsing.
395             * </p>
396             * @param file the JSON file
397             * @return chainer object for completing the parse operation
398             * @see JCardReader
399             * @see <a href="http://tools.ietf.org/html/rfc7095">RFC 7095</a>
400             */
401            public static ParserChainJsonReader parseJson(File file) {
402                    return new ParserChainJsonReader(file);
403            }
404    
405            /**
406             * <p>
407             * Parses JSON-encoded vCards (jCard).
408             * </p>
409             * <p>
410             * Use {@link JCardReader} for more control over the parsing.
411             * </p>
412             * @param in the input stream
413             * @return chainer object for completing the parse operation
414             * @see JCardReader
415             * @see <a href="http://tools.ietf.org/html/rfc7095">RFC 7095</a>
416             */
417            public static ParserChainJsonReader parseJson(InputStream in) {
418                    return new ParserChainJsonReader(in);
419            }
420    
421            /**
422             * <p>
423             * Parses JSON-encoded vCards (jCard).
424             * </p>
425             * <p>
426             * Use {@link JCardReader} for more control over the parsing.
427             * </p>
428             * @param reader the reader
429             * @return chainer object for completing the parse operation
430             * @see JCardReader
431             * @see <a href="http://tools.ietf.org/html/rfc7095">RFC 7095</a>
432             */
433            public static ParserChainJsonReader parseJson(Reader reader) {
434                    return new ParserChainJsonReader(reader);
435            }
436    
437            /**
438             * <p>
439             * Marshals one or more vCards to their traditional, plain-text
440             * representation.
441             * </p>
442             * 
443             * <p>
444             * Use {@link VCardWriter} for more control over how the vCards are written.
445             * </p>
446             * @param vcards the vCards to marshal
447             * @return chainer object for completing the write operation
448             * @see VCardWriter
449             * @see <a href="http://www.imc.org/pdi/vcard-21.rtf">vCard 2.1</a>
450             * @see <a href="http://tools.ietf.org/html/rfc2426">RFC 2426 (3.0)</a>
451             * @see <a href="http://tools.ietf.org/html/rfc6350">RFC 6350 (4.0)</a>
452             */
453            public static WriterChainText write(VCard... vcards) {
454                    return write(Arrays.asList(vcards));
455            }
456    
457            /**
458             * <p>
459             * Marshals one or more vCards to their traditional, plain-text
460             * representation.
461             * </p>
462             * 
463             * <p>
464             * Use {@link VCardWriter} for more control over how the vCards are written.
465             * </p>
466             * @param vcards the vCards to marshal
467             * @return chainer object for completing the write operation
468             * @see VCardWriter
469             * @see <a href="http://www.imc.org/pdi/vcard-21.rtf">vCard 2.1</a>
470             * @see <a href="http://tools.ietf.org/html/rfc2426">RFC 2426 (3.0)</a>
471             * @see <a href="http://tools.ietf.org/html/rfc6350">RFC 6350 (4.0)</a>
472             */
473            public static WriterChainText write(Collection<VCard> vcards) {
474                    return new WriterChainText(vcards);
475            }
476    
477            /**
478             * <p>
479             * Marshals one or more vCards to their XML representation (xCard).
480             * </p>
481             * 
482             * <p>
483             * Use {@link XCardDocument} for more control over how the vCards are
484             * written.
485             * </p>
486             * @param vcards the vCards to marshal
487             * @return chainer object for completing the write operation
488             * @see XCardDocument
489             * @see <a href="http://tools.ietf.org/html/rfc6351">RFC 6351</a>
490             */
491            public static WriterChainXml writeXml(VCard... vcards) {
492                    return writeXml(Arrays.asList(vcards));
493            }
494    
495            /**
496             * <p>
497             * Marshals one or more vCards to their XML representation (xCard).
498             * </p>
499             * 
500             * <p>
501             * Use {@link XCardDocument} for more control over how the vCards are
502             * written.
503             * </p>
504             * @param vcards the vCard to marshal
505             * @return chainer object for completing the write operation
506             * @see XCardDocument
507             * @see <a href="http://tools.ietf.org/html/rfc6351">RFC 6351</a>
508             */
509            public static WriterChainXml writeXml(Collection<VCard> vcards) {
510                    return new WriterChainXml(vcards);
511            }
512    
513            /**
514             * <p>
515             * Marshals one or more vCards their HTML representation (hCard).
516             * </p>
517             * 
518             * <p>
519             * Use {@link HCardPage} for more control over how the vCards are written.
520             * </p>
521             * @param vcards the vCard(s) to marshal
522             * @return chainer object for completing the write operation
523             * @see HCardPage
524             * @see <a href="http://microformats.org/wiki/hcard">hCard 1.0</a>
525             */
526            public static WriterChainHtml writeHtml(VCard... vcards) {
527                    return writeHtml(Arrays.asList(vcards));
528            }
529    
530            /**
531             * <p>
532             * Marshals one or more vCards their HTML representation (hCard).
533             * </p>
534             * 
535             * <p>
536             * Use {@link HCardPage} for more control over how the vCards are written.
537             * </p>
538             * @param vcards the vCard(s) to marshal
539             * @return chainer object for completing the write operation
540             * @see HCardPage
541             * @see <a href="http://microformats.org/wiki/hcard">hCard 1.0</a>
542             */
543            public static WriterChainHtml writeHtml(Collection<VCard> vcards) {
544                    return new WriterChainHtml(vcards);
545            }
546    
547            /**
548             * <p>
549             * Marshals one or more vCards to their JSON representation (jCard).
550             * </p>
551             * 
552             * <p>
553             * Use {@link JCardWriter} for more control over how the vCards are written.
554             * </p>
555             * @param vcards the vCards to marshal
556             * @return chainer object for completing the write operation
557             * @see JCardWriter
558             * @see <a href="http://tools.ietf.org/html/rfc7095">RFC 7095</a>
559             */
560            public static WriterChainJson writeJson(VCard... vcards) {
561                    return writeJson(Arrays.asList(vcards));
562            }
563    
564            /**
565             * <p>
566             * Marshals one or more vCards to their JSON representation (jCard).
567             * </p>
568             * 
569             * <p>
570             * Use {@link JCardWriter} for more control over how the vCards are written.
571             * </p>
572             * @param vcards the vCards to marshal
573             * @return chainer object for completing the write operation
574             * @see JCardWriter
575             * @see <a href="http://tools.ietf.org/html/rfc7095">RFC 7095</a>
576             */
577            public static WriterChainJson writeJson(Collection<VCard> vcards) {
578                    return new WriterChainJson(vcards);
579            }
580    
581            static abstract class ParserChain<T> {
582                    final ScribeIndex index = new ScribeIndex();
583                    List<List<String>> warnings;
584    
585                    @SuppressWarnings("unchecked")
586                    final T this_ = (T) this;
587    
588                    /**
589                     * Registers a property scribe.
590                     * @param scribe the scribe
591                     * @return this
592                     */
593                    public T register(VCardPropertyScribe<? extends VCardProperty> scribe) {
594                            index.register(scribe);
595                            return this_;
596                    }
597    
598                    /**
599                     * Provides a list object that any unmarshal warnings will be put into.
600                     * @param warnings the list object that will be populated with the
601                     * warnings of each unmarshalled vCard. Each element of the list is the
602                     * list of warnings for one of the unmarshalled vCards. Therefore, the
603                     * size of this list will be equal to the number of parsed vCards. If a
604                     * vCard does not have any warnings, then its warning list will be
605                     * empty.
606                     * @return this
607                     */
608                    public T warnings(List<List<String>> warnings) {
609                            this.warnings = warnings;
610                            return this_;
611                    }
612    
613                    /**
614                     * Reads the first vCard from the stream.
615                     * @return the vCard or null if there are no vCards
616                     * @throws IOException if there's an I/O problem
617                     * @throws SAXException if there's a problem parsing the XML
618                     */
619                    public abstract VCard first() throws IOException, SAXException;
620    
621                    /**
622                     * Reads all vCards from the stream.
623                     * @return the parsed vCards
624                     * @throws IOException if there's an I/O problem
625                     * @throws SAXException if there's a problem parsing the XML
626                     */
627                    public abstract List<VCard> all() throws IOException, SAXException;
628            }
629    
630            static abstract class ParserChainText<T> extends ParserChain<T> {
631                    boolean caretDecoding = true;
632                    Charset defaultQuotedPrintableCharset;
633                    final boolean closeWhenDone;
634    
635                    private ParserChainText(boolean closeWhenDone) {
636                            this.closeWhenDone = closeWhenDone;
637                    }
638    
639                    /**
640                     * Sets whether the reader will decode characters in parameter values
641                     * that use circumflex accent encoding (enabled by default).
642                     * 
643                     * @param enable true to use circumflex accent decoding, false not to
644                     * @return this
645                     * @see VCardReader#setCaretDecodingEnabled(boolean)
646                     * @see <a href="http://tools.ietf.org/html/rfc6868">RFC 6868</a>
647                     */
648                    public T caretDecoding(boolean enable) {
649                            caretDecoding = enable;
650                            return this_;
651                    }
652    
653                    /**
654                     * <p>
655                     * Sets the character set to use when decoding quoted-printable values
656                     * if the property has no CHARSET parameter, or if the CHARSET parameter
657                     * is not a valid character set.
658                     * </p>
659                     * <p>
660                     * By default, the Reader's character encoding will be used. If the
661                     * Reader has no character encoding, then the system's default character
662                     * encoding will be used.
663                     * </p>
664                     * @param charset the character set
665                     * @return this
666                     */
667                    public T defaultQuotedPrintableCharset(Charset charset) {
668                            defaultQuotedPrintableCharset = charset;
669                            return this_;
670                    }
671    
672                    @Override
673                    public VCard first() throws IOException {
674                            VCardReader parser = constructReader();
675    
676                            try {
677                                    VCard vcard = parser.readNext();
678                                    if (warnings != null) {
679                                            warnings.add(parser.getWarnings());
680                                    }
681                                    return vcard;
682                            } finally {
683                                    if (closeWhenDone) {
684                                            IOUtils.closeQuietly(parser);
685                                    }
686                            }
687                    }
688    
689                    @Override
690                    public List<VCard> all() throws IOException {
691                            VCardReader parser = constructReader();
692    
693                            try {
694                                    List<VCard> vcards = new ArrayList<VCard>();
695                                    VCard vcard;
696                                    while ((vcard = parser.readNext()) != null) {
697                                            if (warnings != null) {
698                                                    warnings.add(parser.getWarnings());
699                                            }
700                                            vcards.add(vcard);
701                                    }
702                                    return vcards;
703                            } finally {
704                                    if (closeWhenDone) {
705                                            IOUtils.closeQuietly(parser);
706                                    }
707                            }
708                    }
709    
710                    private VCardReader constructReader() throws IOException {
711                            VCardReader parser = _constructReader();
712                            parser.setScribeIndex(index);
713                            parser.setCaretDecodingEnabled(caretDecoding);
714                            if (defaultQuotedPrintableCharset != null) {
715                                    parser.setDefaultQuotedPrintableCharset(defaultQuotedPrintableCharset);
716                            }
717                            return parser;
718                    }
719    
720                    abstract VCardReader _constructReader() throws IOException;
721            }
722    
723            /**
724             * Chainer class for parsing plain text vCards.
725             * @see Ezvcard#parse(InputStream)
726             * @see Ezvcard#parse(File)
727             * @see Ezvcard#parse(Reader)
728             */
729            public static class ParserChainTextReader extends ParserChainText<ParserChainTextReader> {
730                    private final Reader reader;
731                    private final File file;
732    
733                    private ParserChainTextReader(Reader reader) {
734                            super(false);
735                            this.reader = reader;
736                            this.file = null;
737                    }
738    
739                    private ParserChainTextReader(File file) {
740                            super(true);
741                            this.reader = null;
742                            this.file = file;
743                    }
744    
745                    @Override
746                    public ParserChainTextReader register(VCardPropertyScribe<? extends VCardProperty> scribe) {
747                            return super.register(scribe);
748                    }
749    
750                    @Override
751                    public ParserChainTextReader warnings(List<List<String>> warnings) {
752                            return super.warnings(warnings);
753                    }
754    
755                    @Override
756                    public ParserChainTextReader caretDecoding(boolean enable) {
757                            return super.caretDecoding(enable);
758                    }
759    
760                    @Override
761                    @SuppressWarnings("resource")
762                    VCardReader _constructReader() throws IOException {
763                            return (reader != null) ? new VCardReader(reader) : new VCardReader(file);
764                    }
765            }
766    
767            /**
768             * Chainer class for parsing plain text vCards.
769             * @see Ezvcard#parse(String)
770             */
771            public static class ParserChainTextString extends ParserChainText<ParserChainTextString> {
772                    private final String text;
773    
774                    private ParserChainTextString(String text) {
775                            super(false);
776                            this.text = text;
777                    }
778    
779                    @Override
780                    public ParserChainTextString register(VCardPropertyScribe<? extends VCardProperty> scribe) {
781                            return super.register(scribe);
782                    }
783    
784                    @Override
785                    public ParserChainTextString warnings(List<List<String>> warnings) {
786                            return super.warnings(warnings);
787                    }
788    
789                    @Override
790                    public ParserChainTextString caretDecoding(boolean enable) {
791                            return super.caretDecoding(enable);
792                    }
793    
794                    @Override
795                    VCardReader _constructReader() {
796                            return new VCardReader(text);
797                    }
798    
799                    @Override
800                    public VCard first() {
801                            try {
802                                    return super.first();
803                            } catch (IOException e) {
804                                    //should never be thrown because we're reading from a string
805                                    throw new RuntimeException(e);
806                            }
807                    }
808    
809                    @Override
810                    public List<VCard> all() {
811                            try {
812                                    return super.all();
813                            } catch (IOException e) {
814                                    //should never be thrown because we're reading from a string
815                                    throw new RuntimeException(e);
816                            }
817                    }
818            }
819    
820            static abstract class ParserChainXml<T> extends ParserChain<T> {
821                    @Override
822                    public VCard first() throws IOException, SAXException {
823                            XCardDocument document = constructDocument();
824                            VCard vcard = document.parseFirst();
825                            if (warnings != null) {
826                                    warnings.addAll(document.getParseWarnings());
827                            }
828                            return vcard;
829                    }
830    
831                    @Override
832                    public List<VCard> all() throws IOException, SAXException {
833                            XCardDocument document = constructDocument();
834                            List<VCard> icals = document.parseAll();
835                            if (warnings != null) {
836                                    warnings.addAll(document.getParseWarnings());
837                            }
838                            return icals;
839                    }
840    
841                    private XCardDocument constructDocument() throws SAXException, IOException {
842                            XCardDocument parser = _constructDocument();
843                            parser.setScribeIndex(index);
844                            return parser;
845                    }
846    
847                    abstract XCardDocument _constructDocument() throws IOException, SAXException;
848            }
849    
850            /**
851             * Chainer class for parsing XML vCards.
852             * @see Ezvcard#parseXml(InputStream)
853             * @see Ezvcard#parseXml(File)
854             * @see Ezvcard#parseXml(Reader)
855             */
856            public static class ParserChainXmlReader extends ParserChainXml<ParserChainXmlReader> {
857                    private final InputStream in;
858                    private final File file;
859                    private final Reader reader;
860    
861                    private ParserChainXmlReader(InputStream in) {
862                            this.in = in;
863                            this.reader = null;
864                            this.file = null;
865                    }
866    
867                    private ParserChainXmlReader(File file) {
868                            this.in = null;
869                            this.reader = null;
870                            this.file = file;
871                    }
872    
873                    private ParserChainXmlReader(Reader reader) {
874                            this.in = null;
875                            this.reader = reader;
876                            this.file = null;
877                    }
878    
879                    @Override
880                    public ParserChainXmlReader register(VCardPropertyScribe<? extends VCardProperty> scribe) {
881                            return super.register(scribe);
882                    }
883    
884                    @Override
885                    public ParserChainXmlReader warnings(List<List<String>> warnings) {
886                            return super.warnings(warnings);
887                    }
888    
889                    @Override
890                    XCardDocument _constructDocument() throws IOException, SAXException {
891                            if (in != null) {
892                                    return new XCardDocument(in);
893                            }
894                            if (file != null) {
895                                    return new XCardDocument(file);
896                            }
897                            return new XCardDocument(reader);
898                    }
899            }
900    
901            /**
902             * Chainer class for parsing XML vCards.
903             * @see Ezvcard#parseXml(String)
904             */
905            public static class ParserChainXmlString extends ParserChainXml<ParserChainXmlString> {
906                    private final String xml;
907    
908                    private ParserChainXmlString(String xml) {
909                            this.xml = xml;
910                    }
911    
912                    @Override
913                    public ParserChainXmlString register(VCardPropertyScribe<? extends VCardProperty> scribe) {
914                            return super.register(scribe);
915                    }
916    
917                    @Override
918                    public ParserChainXmlString warnings(List<List<String>> warnings) {
919                            return super.warnings(warnings);
920                    }
921    
922                    @Override
923                    XCardDocument _constructDocument() throws SAXException {
924                            return new XCardDocument(xml);
925                    }
926    
927                    @Override
928                    public VCard first() throws SAXException {
929                            try {
930                                    return super.first();
931                            } catch (IOException e) {
932                                    //should never be thrown because we're reading from a string
933                                    throw new RuntimeException(e);
934                            }
935                    }
936    
937                    @Override
938                    public List<VCard> all() throws SAXException {
939                            try {
940                                    return super.all();
941                            } catch (IOException e) {
942                                    //should never be thrown because we're reading from a string
943                                    throw new RuntimeException(e);
944                            }
945                    }
946            }
947    
948            /**
949             * Chainer class for parsing XML vCards.
950             * @see Ezvcard#parseXml(Document)
951             */
952            public static class ParserChainXmlDom extends ParserChainXml<ParserChainXmlDom> {
953                    private final Document document;
954    
955                    private ParserChainXmlDom(Document document) {
956                            this.document = document;
957                    }
958    
959                    @Override
960                    public ParserChainXmlDom register(VCardPropertyScribe<? extends VCardProperty> scribe) {
961                            return super.register(scribe);
962                    }
963    
964                    @Override
965                    public ParserChainXmlDom warnings(List<List<String>> warnings) {
966                            return super.warnings(warnings);
967                    }
968    
969                    @Override
970                    XCardDocument _constructDocument() {
971                            return new XCardDocument(document);
972                    }
973    
974                    @Override
975                    public VCard first() {
976                            try {
977                                    return super.first();
978                            } catch (IOException e) {
979                                    //should never be thrown because we're reading from a DOM
980                                    throw new RuntimeException(e);
981                            } catch (SAXException e) {
982                                    //should never be thrown because we're reading from a DOM
983                                    throw new RuntimeException(e);
984                            }
985                    }
986    
987                    @Override
988                    public List<VCard> all() {
989                            try {
990                                    return super.all();
991                            } catch (IOException e) {
992                                    //should never be thrown because we're reading from a DOM
993                                    throw new RuntimeException(e);
994                            } catch (SAXException e) {
995                                    //should never be thrown because we're reading from a DOM
996                                    throw new RuntimeException(e);
997                            }
998                    }
999            }
1000    
1001            static abstract class ParserChainHtml<T> extends ParserChain<T> {
1002                    String pageUrl;
1003    
1004                    /**
1005                     * Sets the original URL of the webpage. This is used to resolve
1006                     * relative links and to set the SOURCE property on the vCard. Setting
1007                     * this property has no effect if reading from a {@link URL}.
1008                     * @param pageUrl the webpage URL
1009                     * @return this
1010                     */
1011                    public T pageUrl(String pageUrl) {
1012                            this.pageUrl = pageUrl;
1013                            return this_;
1014                    }
1015    
1016                    @Override
1017                    public VCard first() throws IOException {
1018                            HCardReader parser = constructReader();
1019    
1020                            VCard vcard = parser.readNext();
1021                            if (warnings != null) {
1022                                    warnings.add(parser.getWarnings());
1023                            }
1024                            return vcard;
1025                    }
1026    
1027                    @Override
1028                    public List<VCard> all() throws IOException {
1029                            HCardReader parser = constructReader();
1030    
1031                            List<VCard> vcards = new ArrayList<VCard>();
1032                            VCard vcard;
1033                            while ((vcard = parser.readNext()) != null) {
1034                                    if (warnings != null) {
1035                                            warnings.add(parser.getWarnings());
1036                                    }
1037                                    vcards.add(vcard);
1038                            }
1039                            return vcards;
1040                    }
1041    
1042                    private HCardReader constructReader() throws IOException {
1043                            HCardReader parser = _constructReader();
1044                            parser.setScribeIndex(index);
1045                            return parser;
1046                    }
1047    
1048                    abstract HCardReader _constructReader() throws IOException;
1049            }
1050    
1051            /**
1052             * Chainer class for parsing HTML vCards.
1053             * @see Ezvcard#parseHtml(InputStream)
1054             * @see Ezvcard#parseHtml(File)
1055             * @see Ezvcard#parseHtml(Reader)
1056             */
1057            public static class ParserChainHtmlReader extends ParserChainHtml<ParserChainHtmlReader> {
1058                    private final Reader reader;
1059                    private final File file;
1060                    private final URL url;
1061    
1062                    private ParserChainHtmlReader(Reader reader) {
1063                            this.reader = reader;
1064                            this.file = null;
1065                            this.url = null;
1066                    }
1067    
1068                    private ParserChainHtmlReader(File file) {
1069                            this.reader = null;
1070                            this.file = file;
1071                            this.url = null;
1072                    }
1073    
1074                    private ParserChainHtmlReader(URL url) {
1075                            this.reader = null;
1076                            this.file = null;
1077                            this.url = url;
1078                    }
1079    
1080                    @Override
1081                    public ParserChainHtmlReader register(VCardPropertyScribe<? extends VCardProperty> scribe) {
1082                            return super.register(scribe);
1083                    }
1084    
1085                    @Override
1086                    public ParserChainHtmlReader warnings(List<List<String>> warnings) {
1087                            return super.warnings(warnings);
1088                    }
1089    
1090                    @Override
1091                    public ParserChainHtmlReader pageUrl(String pageUrl) {
1092                            return super.pageUrl(pageUrl);
1093                    }
1094    
1095                    @Override
1096                    HCardReader _constructReader() throws IOException {
1097                            if (reader != null) {
1098                                    return new HCardReader(reader, pageUrl);
1099                            }
1100    
1101                            if (file != null) {
1102                                    //Jsoup (presumably) closes the FileReader it creates
1103                                    return new HCardReader(file, pageUrl);
1104                            }
1105    
1106                            return new HCardReader(url);
1107                    }
1108            }
1109    
1110            /**
1111             * Chainer class for parsing HTML vCards.
1112             * @see Ezvcard#parseHtml(String)
1113             */
1114            public static class ParserChainHtmlString extends ParserChainHtml<ParserChainHtmlString> {
1115                    private final String html;
1116    
1117                    private ParserChainHtmlString(String html) {
1118                            this.html = html;
1119                    }
1120    
1121                    @Override
1122                    public ParserChainHtmlString register(VCardPropertyScribe<? extends VCardProperty> scribe) {
1123                            return super.register(scribe);
1124                    }
1125    
1126                    @Override
1127                    public ParserChainHtmlString warnings(List<List<String>> warnings) {
1128                            return super.warnings(warnings);
1129                    }
1130    
1131                    @Override
1132                    public ParserChainHtmlString pageUrl(String pageUrl) {
1133                            return super.pageUrl(pageUrl);
1134                    }
1135    
1136                    @Override
1137                    HCardReader _constructReader() {
1138                            return new HCardReader(html, pageUrl);
1139                    }
1140    
1141                    @Override
1142                    public VCard first() {
1143                            try {
1144                                    return super.first();
1145                            } catch (IOException e) {
1146                                    //should never be thrown because we're reading from a string
1147                                    throw new RuntimeException(e);
1148                            }
1149                    }
1150    
1151                    @Override
1152                    public List<VCard> all() {
1153                            try {
1154                                    return super.all();
1155                            } catch (IOException e) {
1156                                    //should never be thrown because we're reading from a string
1157                                    throw new RuntimeException(e);
1158                            }
1159                    }
1160            }
1161    
1162            static abstract class ParserChainJson<T> extends ParserChain<T> {
1163                    final boolean closeWhenDone;
1164    
1165                    private ParserChainJson(boolean closeWhenDone) {
1166                            this.closeWhenDone = closeWhenDone;
1167                    }
1168    
1169                    /**
1170                     * @throws JCardParseException if the jCard syntax is incorrect (the
1171                     * JSON syntax may be valid, but it is not in the correct jCard format).
1172                     * @throws JsonParseException if the JSON syntax is incorrect
1173                     */
1174                    @Override
1175                    public VCard first() throws IOException {
1176                            JCardReader parser = constructReader();
1177    
1178                            try {
1179                                    VCard vcard = parser.readNext();
1180                                    if (warnings != null) {
1181                                            warnings.add(parser.getWarnings());
1182                                    }
1183                                    return vcard;
1184                            } finally {
1185                                    if (closeWhenDone) {
1186                                            IOUtils.closeQuietly(parser);
1187                                    }
1188                            }
1189                    }
1190    
1191                    /**
1192                     * @throws JCardParseException if the jCard syntax is incorrect (the
1193                     * JSON syntax may be valid, but it is not in the correct jCard format).
1194                     * @throws JsonParseException if the JSON syntax is incorrect
1195                     */
1196                    @Override
1197                    public List<VCard> all() throws IOException {
1198                            JCardReader parser = constructReader();
1199    
1200                            try {
1201                                    List<VCard> vcards = new ArrayList<VCard>();
1202                                    VCard vcard;
1203                                    while ((vcard = parser.readNext()) != null) {
1204                                            if (warnings != null) {
1205                                                    warnings.add(parser.getWarnings());
1206                                            }
1207                                            vcards.add(vcard);
1208                                    }
1209                                    return vcards;
1210                            } finally {
1211                                    if (closeWhenDone) {
1212                                            IOUtils.closeQuietly(parser);
1213                                    }
1214                            }
1215                    }
1216    
1217                    private JCardReader constructReader() throws IOException {
1218                            JCardReader parser = _constructReader();
1219                            parser.setScribeIndex(index);
1220                            return parser;
1221                    }
1222    
1223                    abstract JCardReader _constructReader() throws IOException;
1224            }
1225    
1226            /**
1227             * Chainer class for parsing JSON-encoded vCards (jCard).
1228             * @see Ezvcard#parseJson(InputStream)
1229             * @see Ezvcard#parseJson(File)
1230             * @see Ezvcard#parseJson(Reader)
1231             */
1232            public static class ParserChainJsonReader extends ParserChainJson<ParserChainJsonReader> {
1233                    private final InputStream in;
1234                    private final File file;
1235                    private final Reader reader;
1236    
1237                    private ParserChainJsonReader(InputStream in) {
1238                            super(false);
1239                            this.in = in;
1240                            this.reader = null;
1241                            this.file = null;
1242                    }
1243    
1244                    private ParserChainJsonReader(File file) {
1245                            super(true);
1246                            this.in = null;
1247                            this.reader = null;
1248                            this.file = file;
1249                    }
1250    
1251                    private ParserChainJsonReader(Reader reader) {
1252                            super(false);
1253                            this.in = null;
1254                            this.reader = reader;
1255                            this.file = null;
1256                    }
1257    
1258                    @Override
1259                    public ParserChainJsonReader register(VCardPropertyScribe<? extends VCardProperty> scribe) {
1260                            return super.register(scribe);
1261                    }
1262    
1263                    @Override
1264                    public ParserChainJsonReader warnings(List<List<String>> warnings) {
1265                            return super.warnings(warnings);
1266                    }
1267    
1268                    @Override
1269                    JCardReader _constructReader() throws IOException {
1270                            if (in != null) {
1271                                    return new JCardReader(in);
1272                            }
1273                            if (file != null) {
1274                                    return new JCardReader(file);
1275                            }
1276                            return new JCardReader(reader);
1277                    }
1278            }
1279    
1280            /**
1281             * Chainer class for parsing JSON-encoded vCards (jCard).
1282             * @see Ezvcard#parseJson(String)
1283             */
1284            public static class ParserChainJsonString extends ParserChainJson<ParserChainJsonString> {
1285                    private final String json;
1286    
1287                    private ParserChainJsonString(String json) {
1288                            super(false);
1289                            this.json = json;
1290                    }
1291    
1292                    @Override
1293                    public ParserChainJsonString register(VCardPropertyScribe<? extends VCardProperty> scribe) {
1294                            return super.register(scribe);
1295                    }
1296    
1297                    @Override
1298                    public ParserChainJsonString warnings(List<List<String>> warnings) {
1299                            return super.warnings(warnings);
1300                    }
1301    
1302                    @Override
1303                    JCardReader _constructReader() {
1304                            return new JCardReader(json);
1305                    }
1306    
1307                    @Override
1308                    public VCard first() {
1309                            try {
1310                                    return super.first();
1311                            } catch (IOException e) {
1312                                    //should never be thrown because we're reading from a string
1313                                    throw new RuntimeException(e);
1314                            }
1315                    }
1316    
1317                    @Override
1318                    public List<VCard> all() {
1319                            try {
1320                                    return super.all();
1321                            } catch (IOException e) {
1322                                    //should never be thrown because we're reading from a string
1323                                    throw new RuntimeException(e);
1324                            }
1325                    }
1326            }
1327    
1328            static abstract class WriterChain<T> {
1329                    final Collection<VCard> vcards;
1330    
1331                    @SuppressWarnings("unchecked")
1332                    final T this_ = (T) this;
1333    
1334                    WriterChain(Collection<VCard> vcards) {
1335                            this.vcards = vcards;
1336                    }
1337            }
1338    
1339            /**
1340             * Chainer class for writing plain text vCards
1341             * @see Ezvcard#write(Collection)
1342             * @see Ezvcard#write(VCard...)
1343             */
1344            public static class WriterChainText extends WriterChain<WriterChainText> {
1345                    VCardVersion version;
1346                    boolean prodId = true;
1347                    boolean versionStrict = true;
1348                    boolean caretEncoding = false;
1349                    final ScribeIndex index = new ScribeIndex();
1350    
1351                    private WriterChainText(Collection<VCard> vcards) {
1352                            super(vcards);
1353                    }
1354    
1355                    /**
1356                     * <p>
1357                     * Sets the version that all the vCards will be marshalled to. The
1358                     * version that is attached to each individual {@link VCard} object will
1359                     * be ignored.
1360                     * </p>
1361                     * <p>
1362                     * If no version is passed into this method, the writer will look at the
1363                     * version attached to each individual {@link VCard} object and marshal
1364                     * it to that version. And if a {@link VCard} object has no version
1365                     * attached to it, then it will be marshalled to version 3.0.
1366                     * </p>
1367                     * @param version the version to marshal the vCards to
1368                     * @return this
1369                     */
1370                    public WriterChainText version(VCardVersion version) {
1371                            this.version = version;
1372                            return this_;
1373                    }
1374    
1375                    /**
1376                     * Sets whether or not to add a PRODID property to each vCard, saying
1377                     * that the vCard was generated by this library. For 2.1 vCards, the
1378                     * extended property X-PRODID is used, since PRODID is not supported by
1379                     * that version.
1380                     * @param include true to add PRODID (default), false not to
1381                     * @return this
1382                     */
1383                    public WriterChainText prodId(boolean include) {
1384                            this.prodId = include;
1385                            return this_;
1386                    }
1387    
1388                    /**
1389                     * Sets whether the writer will use circumflex accent encoding for vCard
1390                     * 3.0 and 4.0 parameter values (disabled by default).
1391                     * @param enable true to use circumflex accent encoding, false not to
1392                     * @return this
1393                     * @see VCardWriter#setCaretEncodingEnabled(boolean)
1394                     * @see <a href="http://tools.ietf.org/html/rfc6868">RFC 6868</a>
1395                     */
1396                    public WriterChainText caretEncoding(boolean enable) {
1397                            this.caretEncoding = enable;
1398                            return this_;
1399                    }
1400    
1401                    /**
1402                     * Sets whether properties that do not support the target version will
1403                     * be excluded from the written vCard.
1404                     * @param versionStrict true to exclude properties that do not support
1405                     * the target version, false to include them anyway (defaults to true)
1406                     * @return this
1407                     */
1408                    public WriterChainText versionStrict(boolean versionStrict) {
1409                            this.versionStrict = versionStrict;
1410                            return this_;
1411                    }
1412    
1413                    /**
1414                     * Registers a property scribe.
1415                     * @param scribe the scribe to register
1416                     * @return this
1417                     */
1418                    public WriterChainText register(VCardPropertyScribe<? extends VCardProperty> scribe) {
1419                            index.register(scribe);
1420                            return this_;
1421                    }
1422    
1423                    /**
1424                     * Writes the vCards to a string.
1425                     * @return the vCard string
1426                     */
1427                    public String go() {
1428                            StringWriter sw = new StringWriter();
1429                            try {
1430                                    go(sw);
1431                            } catch (IOException e) {
1432                                    //writing to a string
1433                            }
1434                            return sw.toString();
1435                    }
1436    
1437                    /**
1438                     * Writes the vCards to an output stream.
1439                     * @param out the output stream to write to
1440                     * @throws IOException if there's a problem writing to the output stream
1441                     */
1442                    public void go(OutputStream out) throws IOException {
1443                            VCardWriter vcardWriter = (version == null) ? new VCardWriter(out) : new VCardWriter(out, version);
1444                            go(vcardWriter);
1445                    }
1446    
1447                    /**
1448                     * Writes the vCards to a file. If the file exists, it will be
1449                     * overwritten.
1450                     * @param file the file to write to
1451                     * @throws IOException if there's a problem writing to the file
1452                     */
1453                    public void go(File file) throws IOException {
1454                            go(file, false);
1455                    }
1456    
1457                    /**
1458                     * Writes the vCards to a file.
1459                     * @param file the file to write to
1460                     * @param append true to append onto the end of the file, false to
1461                     * overwrite it
1462                     * @throws IOException if there's a problem writing to the file
1463                     */
1464                    public void go(File file, boolean append) throws IOException {
1465                            VCardWriter vcardWriter = (version == null) ? new VCardWriter(file, append) : new VCardWriter(file, append, version);
1466                            try {
1467                                    go(vcardWriter);
1468                            } finally {
1469                                    IOUtils.closeQuietly(vcardWriter);
1470                            }
1471                    }
1472    
1473                    /**
1474                     * Writes the vCards to a writer.
1475                     * @param writer the writer to write to
1476                     * @throws IOException if there's a problem writing to the writer
1477                     */
1478                    public void go(Writer writer) throws IOException {
1479                            VCardWriter vcardWriter = new VCardWriter(writer);
1480                            vcardWriter.setTargetVersion(version);
1481                            go(vcardWriter);
1482                    }
1483    
1484                    private void go(VCardWriter vcardWriter) throws IOException {
1485                            vcardWriter.setAddProdId(prodId);
1486                            vcardWriter.setCaretEncodingEnabled(caretEncoding);
1487                            vcardWriter.setVersionStrict(versionStrict);
1488                            vcardWriter.setScribeIndex(index);
1489    
1490                            for (VCard vcard : vcards) {
1491                                    if (version == null) {
1492                                            VCardVersion vcardVersion = vcard.getVersion();
1493                                            if (vcardVersion == null) {
1494                                                    vcardVersion = VCardVersion.V3_0;
1495                                            }
1496                                            vcardWriter.setTargetVersion(vcardVersion);
1497                                    }
1498                                    vcardWriter.write(vcard);
1499                                    vcardWriter.flush();
1500                            }
1501                    }
1502            }
1503    
1504            /**
1505             * Chainer class for writing XML vCards (xCard).
1506             * @see Ezvcard#writeXml(Collection)
1507             * @see Ezvcard#writeXml(VCard...)
1508             */
1509            public static class WriterChainXml extends WriterChain<WriterChainXml> {
1510                    boolean prodId = true;
1511                    boolean versionStrict = true;
1512                    int indent = -1;
1513                    final ScribeIndex index = new ScribeIndex();
1514    
1515                    private WriterChainXml(Collection<VCard> vcards) {
1516                            super(vcards);
1517                    }
1518    
1519                    /**
1520                     * Sets whether or not to add a PRODID property to each vCard, saying
1521                     * that the vCard was generated by this library.
1522                     * @param include true to add PRODID (default), false not to
1523                     * @return this
1524                     */
1525                    public WriterChainXml prodId(boolean include) {
1526                            this.prodId = include;
1527                            return this_;
1528                    }
1529    
1530                    /**
1531                     * Sets the number of indent spaces to use for pretty-printing. If not
1532                     * set, then the XML will not be pretty-printed.
1533                     * @param indent the number of spaces in the indent string
1534                     * @return this
1535                     */
1536                    public WriterChainXml indent(int indent) {
1537                            this.indent = indent;
1538                            return this_;
1539                    }
1540    
1541                    /**
1542                     * Sets whether properties that do not support xCard (vCard version 4.0)
1543                     * will be excluded from the written vCard.
1544                     * @param versionStrict true to exclude properties that do not support
1545                     * xCard, false to include them anyway (defaults to true)
1546                     * @return this
1547                     */
1548                    public WriterChainXml versionStrict(boolean versionStrict) {
1549                            this.versionStrict = versionStrict;
1550                            return this_;
1551                    }
1552    
1553                    /**
1554                     * Registers a property scribe.
1555                     * @param scribe the scribe to register
1556                     * @return this
1557                     */
1558                    public WriterChainXml register(VCardPropertyScribe<? extends VCardProperty> scribe) {
1559                            index.register(scribe);
1560                            return this_;
1561                    }
1562    
1563                    /**
1564                     * Writes the xCards to a string.
1565                     * @return the XML document
1566                     */
1567                    public String go() {
1568                            StringWriter sw = new StringWriter();
1569                            try {
1570                                    go(sw);
1571                            } catch (TransformerException e) {
1572                                    //writing to a string
1573                            }
1574                            return sw.toString();
1575                    }
1576    
1577                    /**
1578                     * Writes the xCards to an output stream.
1579                     * @param out the output stream to write to
1580                     * @throws TransformerException if there's a problem writing to the
1581                     * output stream
1582                     */
1583                    public void go(OutputStream out) throws TransformerException {
1584                            XCardDocument doc = createXCardDocument();
1585                            doc.write(out, indent);
1586                    }
1587    
1588                    /**
1589                     * Writes the xCards to a file.
1590                     * @param file the file to write to
1591                     * @throws IOException if the file can't be opened
1592                     * @throws TransformerException if there's a problem writing to the file
1593                     */
1594                    public void go(File file) throws IOException, TransformerException {
1595                            XCardDocument doc = createXCardDocument();
1596                            doc.write(file, indent);
1597                    }
1598    
1599                    /**
1600                     * Writes the xCards to a writer.
1601                     * @param writer the writer to write to
1602                     * @throws TransformerException if there's a problem writing to the
1603                     * writer
1604                     */
1605                    public void go(Writer writer) throws TransformerException {
1606                            XCardDocument doc = createXCardDocument();
1607                            doc.write(writer, indent);
1608                    }
1609    
1610                    /**
1611                     * Generates an XML document object model (DOM) containing the xCards.
1612                     * @return the DOM
1613                     */
1614                    public Document dom() {
1615                            XCardDocument doc = createXCardDocument();
1616                            return doc.getDocument();
1617                    }
1618    
1619                    private XCardDocument createXCardDocument() {
1620                            XCardDocument doc = new XCardDocument();
1621                            doc.setAddProdId(prodId);
1622                            doc.setVersionStrict(versionStrict);
1623                            doc.setScribeIndex(index);
1624    
1625                            for (VCard vcard : vcards) {
1626                                    doc.add(vcard);
1627                            }
1628    
1629                            return doc;
1630                    }
1631            }
1632    
1633            /**
1634             * Chainer class for writing HTML vCards (hCard).
1635             * @see Ezvcard#writeHtml(Collection)
1636             * @see Ezvcard#writeHtml(VCard...)
1637             */
1638            public static class WriterChainHtml extends WriterChain<WriterChainHtml> {
1639                    private WriterChainHtml(Collection<VCard> vcards) {
1640                            super(vcards);
1641                    }
1642    
1643                    /**
1644                     * Writes the hCards to a string.
1645                     * @return the HTML page
1646                     */
1647                    public String go() {
1648                            StringWriter sw = new StringWriter();
1649                            try {
1650                                    go(sw);
1651                            } catch (IOException e) {
1652                                    //writing string
1653                            }
1654                            return sw.toString();
1655                    }
1656    
1657                    /**
1658                     * Writes the hCards to an output stream.
1659                     * @param out the output stream to write to
1660                     * @throws IOException if there's a problem writing to the output stream
1661                     */
1662                    public void go(OutputStream out) throws IOException {
1663                            go(new OutputStreamWriter(out));
1664                    }
1665    
1666                    /**
1667                     * Writes the hCards to a file.
1668                     * @param file the file to write to
1669                     * @throws IOException if there's a problem writing to the file
1670                     */
1671                    public void go(File file) throws IOException {
1672                            FileWriter writer = null;
1673                            try {
1674                                    writer = new FileWriter(file);
1675                                    go(writer);
1676                            } finally {
1677                                    IOUtils.closeQuietly(writer);
1678                            }
1679                    }
1680    
1681                    /**
1682                     * Writes the hCards to a writer.
1683                     * @param writer the writer to write to
1684                     * @throws IOException if there's a problem writing to the writer
1685                     */
1686                    public void go(Writer writer) throws IOException {
1687                            HCardPage page = new HCardPage();
1688                            for (VCard vcard : vcards) {
1689                                    page.add(vcard);
1690                            }
1691                            page.write(writer);
1692                    }
1693            }
1694    
1695            /**
1696             * Chainer class for writing JSON-encoded vCards (jCard).
1697             * @see Ezvcard#writeJson(Collection)
1698             * @see Ezvcard#writeJson(VCard...)
1699             */
1700            public static class WriterChainJson extends WriterChain<WriterChainJson> {
1701                    boolean prodId = true;
1702                    boolean versionStrict = true;
1703                    boolean indent = false;
1704                    final ScribeIndex index = new ScribeIndex();
1705    
1706                    private WriterChainJson(Collection<VCard> vcards) {
1707                            super(vcards);
1708                    }
1709    
1710                    /**
1711                     * Sets whether or not to add a PRODID property to each vCard, saying
1712                     * that the vCard was generated by this library.
1713                     * @param include true to add PRODID (default), false not to
1714                     * @return this
1715                     */
1716                    public WriterChainJson prodId(boolean include) {
1717                            this.prodId = include;
1718                            return this_;
1719                    }
1720    
1721                    /**
1722                     * Sets whether or not to pretty-print the JSON.
1723                     * @param indent true to pretty-print it, false not to (defaults to
1724                     * false)
1725                     * @return this
1726                     */
1727                    public WriterChainJson indent(boolean indent) {
1728                            this.indent = indent;
1729                            return this_;
1730                    }
1731    
1732                    /**
1733                     * Sets whether properties that do not support jCard (vCard version 4.0)
1734                     * will be excluded from the written vCard.
1735                     * @param versionStrict true to exclude properties that do not support
1736                     * jCard, false to include them anyway (defaults to true)
1737                     * @return this
1738                     */
1739                    public WriterChainJson versionStrict(boolean versionStrict) {
1740                            this.versionStrict = versionStrict;
1741                            return this_;
1742                    }
1743    
1744                    /**
1745                     * Registers a property scribe.
1746                     * @param scribe the scribe to register
1747                     * @return this
1748                     */
1749                    public WriterChainJson register(VCardPropertyScribe<? extends VCardProperty> scribe) {
1750                            index.register(scribe);
1751                            return this_;
1752                    }
1753    
1754                    /**
1755                     * Writes the jCards to a string.
1756                     * @return the JSON string
1757                     */
1758                    public String go() {
1759                            StringWriter sw = new StringWriter();
1760                            try {
1761                                    go(sw);
1762                            } catch (IOException e) {
1763                                    //writing to a string
1764                            }
1765                            return sw.toString();
1766                    }
1767    
1768                    /**
1769                     * Writes the jCards to an output stream.
1770                     * @param out the output stream to write to
1771                     * @throws IOException if there's a problem writing to the output stream
1772                     */
1773                    public void go(OutputStream out) throws IOException {
1774                            go(new JCardWriter(out, vcards.size() > 1));
1775                    }
1776    
1777                    /**
1778                     * Writes the jCards to a file.
1779                     * @param file the file to write to
1780                     * @throws IOException if there's a problem writing to the file
1781                     */
1782                    public void go(File file) throws IOException {
1783                            JCardWriter writer = new JCardWriter(file, vcards.size() > 1);
1784                            try {
1785                                    go(writer);
1786                            } finally {
1787                                    IOUtils.closeQuietly(writer);
1788                            }
1789                    }
1790    
1791                    /**
1792                     * Writes the jCards to a writer.
1793                     * @param writer the writer to write to
1794                     * @throws IOException if there's a problem writing to the writer
1795                     */
1796                    public void go(Writer writer) throws IOException {
1797                            go(new JCardWriter(writer, vcards.size() > 1));
1798                    }
1799    
1800                    private void go(JCardWriter writer) throws IOException {
1801                            writer.setAddProdId(prodId);
1802                            writer.setIndent(indent);
1803                            writer.setVersionStrict(versionStrict);
1804                            writer.setScribeIndex(index);
1805                            try {
1806                                    for (VCard vcard : vcards) {
1807                                            writer.write(vcard);
1808                                            writer.flush();
1809                                    }
1810                            } finally {
1811                                    writer.closeJsonStream();
1812                            }
1813                    }
1814            }
1815    
1816            private Ezvcard() {
1817                    //hide
1818            }
1819    }