001    package ezvcard.io.json;
002    
003    import static ezvcard.util.IOUtils.utf8Writer;
004    
005    import java.io.Closeable;
006    import java.io.File;
007    import java.io.Flushable;
008    import java.io.IOException;
009    import java.io.OutputStream;
010    import java.io.Writer;
011    import java.util.ArrayList;
012    import java.util.List;
013    
014    import ezvcard.Ezvcard;
015    import ezvcard.VCard;
016    import ezvcard.VCardDataType;
017    import ezvcard.VCardVersion;
018    import ezvcard.io.EmbeddedVCardException;
019    import ezvcard.io.SkipMeException;
020    import ezvcard.io.scribe.ScribeIndex;
021    import ezvcard.io.scribe.VCardPropertyScribe;
022    import ezvcard.parameter.VCardParameters;
023    import ezvcard.property.ProductId;
024    import ezvcard.property.RawProperty;
025    import ezvcard.property.VCardProperty;
026    
027    /*
028     Copyright (c) 2013, Michael Angstadt
029     All rights reserved.
030    
031     Redistribution and use in source and binary forms, with or without
032     modification, are permitted provided that the following conditions are met: 
033    
034     1. Redistributions of source code must retain the above copyright notice, this
035     list of conditions and the following disclaimer. 
036     2. Redistributions in binary form must reproduce the above copyright notice,
037     this list of conditions and the following disclaimer in the documentation
038     and/or other materials provided with the distribution. 
039    
040     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
041     ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
042     WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
043     DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
044     ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
045     (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
046     LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
047     ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
048     (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
049     SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
050    
051     The views and conclusions contained in the software and documentation are those
052     of the authors and should not be interpreted as representing official policies, 
053     either expressed or implied, of the FreeBSD Project.
054     */
055    
056    /**
057     * <p>
058     * Writes {@link VCard} objects to a JSON data stream (jCard format).
059     * </p>
060     * <p>
061     * <b>Example:</b>
062     * 
063     * <pre class="brush:java">
064     * VCard vcard1 = ...
065     * VCard vcard2 = ...
066     * 
067     * File file = new File("vcard.json");
068     * JCardWriter jcardWriter = new JCardWriter(file);
069     * jcardWriter.write(vcard1);
070     * jcardWriter.write(vcard2);
071     * jcardWriter.close(); //"close()" must be called in order to terminate the JSON object
072     * </pre>
073     * 
074     * </p>
075     * @author Michael Angstadt
076     */
077    public class JCardWriter implements Closeable, Flushable {
078            private ScribeIndex index = new ScribeIndex();
079            private final JCardRawWriter writer;
080            private final VCardVersion targetVersion = VCardVersion.V4_0;
081            private boolean addProdId = true;
082            private boolean versionStrict = true;
083    
084            /**
085             * Creates a writer that writes jCards to an output stream (UTF-8 encoding
086             * will be used).
087             * @param out the output stream to write the vCard to
088             */
089            public JCardWriter(OutputStream out) {
090                    this(utf8Writer(out));
091            }
092    
093            /**
094             * Creates a writer that writes jCards to an output stream (UTF-8 encoding
095             * will be used).
096             * @param out the output stream to write the vCard to
097             * @param wrapInArray true to enclose all written vCards in a JSON array,
098             * false not to
099             */
100            public JCardWriter(OutputStream out, boolean wrapInArray) {
101                    this(utf8Writer(out), wrapInArray);
102            }
103    
104            /**
105             * Creates a writer that writes jCards to a file (UTF-8 encoding will be
106             * used).
107             * @param file the file to write the vCard to
108             * @throws IOException if there's a problem opening the file
109             */
110            public JCardWriter(File file) throws IOException {
111                    this(utf8Writer(file));
112            }
113    
114            /**
115             * Creates a writer that writes jCards to a file (UTF-8 encoding will be
116             * used).
117             * @param file the file to write the vCard to
118             * @param wrapInArray true to enclose all written vCards in a JSON array,
119             * false not to
120             * @throws IOException if there's a problem opening the file
121             */
122            public JCardWriter(File file, boolean wrapInArray) throws IOException {
123                    this(utf8Writer(file), wrapInArray);
124            }
125    
126            /**
127             * Creates a writer that writes jCards to a writer.
128             * @param writer the writer to write the vCard to
129             */
130            public JCardWriter(Writer writer) {
131                    this(writer, false);
132            }
133    
134            /**
135             * Creates a writer that writes jCards to a writer.
136             * @param writer the writer to write the vCard to
137             * @param wrapInArray true to enclose all written vCards in a JSON array,
138             * false not to
139             */
140            public JCardWriter(Writer writer, boolean wrapInArray) {
141                    this.writer = new JCardRawWriter(writer, wrapInArray);
142            }
143    
144            /**
145             * Writes a vCard to the stream.
146             * @param vcard the vCard to write
147             * @throws IOException if there's a problem writing to the output stream
148             * @throws IllegalArgumentException if a scribe hasn't been registered for a
149             * custom property class (see: {@link #registerScribe})
150             */
151            @SuppressWarnings({ "rawtypes", "unchecked" })
152            public void write(VCard vcard) throws IOException {
153                    List<VCardProperty> typesToAdd = new ArrayList<VCardProperty>();
154    
155                    for (VCardProperty type : vcard) {
156                            if (addProdId && type instanceof ProductId) {
157                                    //do not add the PRODID in the vCard if "addProdId" is true
158                                    continue;
159                            }
160    
161                            if (versionStrict && !type.getSupportedVersions().contains(targetVersion)) {
162                                    //do not add the property to the vCard if it is not supported by the target version
163                                    continue;
164                            }
165    
166                            //check for scribes before writing anything to the stream
167                            if (index.getPropertyScribe(type) == null) {
168                                    throw new IllegalArgumentException("No scribe found for property class \"" + type.getClass().getName() + "\".");
169                            }
170    
171                            typesToAdd.add(type);
172                    }
173    
174                    //add an extended type saying it was generated by this library
175                    if (addProdId) {
176                            VCardProperty property;
177                            if (targetVersion == VCardVersion.V2_1) {
178                                    property = new RawProperty("X-PRODID", "ezvcard " + Ezvcard.VERSION);
179                            } else {
180                                    property = new ProductId("ezvcard " + Ezvcard.VERSION);
181                            }
182                            typesToAdd.add(property);
183                    }
184    
185                    writer.writeStartVCard();
186                    writer.writeProperty("version", VCardDataType.TEXT, JCardValue.single(targetVersion.getVersion()));
187    
188                    for (VCardProperty type : typesToAdd) {
189                            VCardPropertyScribe scribe = index.getPropertyScribe(type);
190    
191                            try {
192                                    JCardValue value = scribe.writeJson(type);
193                                    value = new JCardValue(value.getValues());
194                                    VCardParameters subTypes = scribe.prepareParameters(type, targetVersion, vcard);
195                                    writer.writeProperty(type.getGroup(), scribe.getPropertyName().toLowerCase(), subTypes, scribe.dataType(type, targetVersion), value);
196                            } catch (SkipMeException e) {
197                                    //property has requested not to be written
198                            } catch (EmbeddedVCardException e) {
199                                    //don't write because jCard does not support embedded vCards
200                            }
201                    }
202    
203                    writer.writeEndVCard();
204            }
205    
206            /**
207             * Gets whether or not a "PRODID" property will be added to each vCard,
208             * saying that the vCard was generated by this library.
209             * @return true if this property will be added, false if not (defaults to
210             * true)
211             */
212            public boolean isAddProdId() {
213                    return addProdId;
214            }
215    
216            /**
217             * Sets whether or not to add a "PRODID" property to each vCard, saying that
218             * the vCard was generated by this library.
219             * @param addProdId true to add this property, false not to (defaults to
220             * true)
221             */
222            public void setAddProdId(boolean addProdId) {
223                    this.addProdId = addProdId;
224            }
225    
226            /**
227             * Gets whether properties that do not support jCard (vCard version 4.0)
228             * will be excluded from the written vCard.
229             * @return true to exclude properties that do not support jCard, false to
230             * include them anyway (defaults to true)
231             */
232            public boolean isVersionStrict() {
233                    return versionStrict;
234            }
235    
236            /**
237             * Sets whether properties that do not support jCard (vCard version 4.0)
238             * will be excluded from the written vCard.
239             * @param versionStrict true to exclude properties that do not support
240             * jCard, false to include them anyway (defaults to true)
241             */
242            public void setVersionStrict(boolean versionStrict) {
243                    this.versionStrict = versionStrict;
244            }
245    
246            /**
247             * Gets whether or not the JSON will be pretty-printed.
248             * @return true if it will be pretty-printed, false if not (defaults to
249             * false)
250             */
251            public boolean isIndent() {
252                    return writer.isIndent();
253            }
254    
255            /**
256             * Sets whether or not to pretty-print the JSON.
257             * @param indent true to pretty-print it, false not to (defaults to false)
258             */
259            public void setIndent(boolean indent) {
260                    writer.setIndent(indent);
261            }
262    
263            /**
264             * <p>
265             * Registers a property scribe. This is the same as calling:
266             * </p>
267             * <p>
268             * {@code getScribeIndex().register(scribe)}
269             * </p>
270             * @param scribe the scribe to register
271             */
272            public void registerScribe(VCardPropertyScribe<? extends VCardProperty> scribe) {
273                    index.register(scribe);
274            }
275    
276            /**
277             * Gets the scribe index.
278             * @return the scribe index
279             */
280            public ScribeIndex getScribeIndex() {
281                    return index;
282            }
283    
284            /**
285             * Sets the scribe index.
286             * @param index the scribe index
287             */
288            public void setScribeIndex(ScribeIndex index) {
289                    this.index = index;
290            }
291    
292            /**
293             * Flushes the jCard data stream.
294             * @throws IOException if there's a problem flushing the stream
295             */
296            public void flush() throws IOException {
297                    writer.flush();
298            }
299    
300            /**
301             * Ends the jCard data stream, but does not close the underlying writer.
302             * @throws IOException if there's a problem closing the stream
303             */
304            public void closeJsonStream() throws IOException {
305                    writer.closeJsonStream();
306            }
307    
308            /**
309             * Ends the jCard data stream and closes the underlying writer.
310             * @throws IOException if there's a problem closing the stream
311             */
312            public void close() throws IOException {
313                    writer.close();
314            }
315    }