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