001package ezvcard.io.json;
002
003import java.io.File;
004import java.io.Flushable;
005import java.io.IOException;
006import java.io.OutputStream;
007import java.io.Writer;
008import java.util.List;
009
010import com.fasterxml.jackson.core.JsonGenerator;
011import com.fasterxml.jackson.core.PrettyPrinter;
012
013import ezvcard.VCard;
014import ezvcard.VCardDataType;
015import ezvcard.VCardVersion;
016import ezvcard.io.EmbeddedVCardException;
017import ezvcard.io.SkipMeException;
018import ezvcard.io.StreamWriter;
019import ezvcard.io.scribe.VCardPropertyScribe;
020import ezvcard.parameter.VCardParameters;
021import ezvcard.property.VCardProperty;
022import ezvcard.util.Utf8Writer;
023
024/*
025 Copyright (c) 2012-2018, Michael Angstadt
026 All rights reserved.
027
028 Redistribution and use in source and binary forms, with or without
029 modification, are permitted provided that the following conditions are met: 
030
031 1. Redistributions of source code must retain the above copyright notice, this
032 list of conditions and the following disclaimer. 
033 2. Redistributions in binary form must reproduce the above copyright notice,
034 this list of conditions and the following disclaimer in the documentation
035 and/or other materials provided with the distribution. 
036
037 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
038 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
039 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
040 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
041 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
042 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
043 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
044 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
045 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
046 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
047
048 The views and conclusions contained in the software and documentation are those
049 of the authors and should not be interpreted as representing official policies, 
050 either expressed or implied, of the FreeBSD Project.
051 */
052
053/**
054 * <p>
055 * Writes {@link VCard} objects to a JSON data stream (jCard format).
056 * </p>
057 * <p>
058 * <b>Example:</b>
059 * </p>
060 * 
061 * <pre class="brush:java">
062 * VCard vcard1 = ...
063 * VCard vcard2 = ...
064 * File file = new File("vcard.json");
065 * JCardWriter writer = null;
066 * try {
067 *   writer = new JCardWriter(file);
068 *   writer.write(vcard1);
069 *   writer.write(vcard2);
070 * } finally {
071 *   if (writer != null) writer.close();
072 * }
073 * </pre>
074 * @author Michael Angstadt
075 * @author Buddy Gorven
076 * @see <a href="http://tools.ietf.org/html/rfc7095">RFC 7095</a>
077 */
078public class JCardWriter extends StreamWriter implements Flushable {
079        private final JCardRawWriter writer;
080        private final VCardVersion targetVersion = VCardVersion.V4_0;
081        private JsonGenerator generator = null;
082
083        /**
084         * @param out the output stream to write to (UTF-8 encoding will be used)
085         */
086        public JCardWriter(OutputStream out) {
087                this(new Utf8Writer(out));
088        }
089
090        /**
091         * @param out the output stream to write to (UTF-8 encoding will be used)
092         * @param wrapInArray true to enclose all written vCards in a JSON array,
093         * false not to
094         */
095        public JCardWriter(OutputStream out, boolean wrapInArray) {
096                this(new Utf8Writer(out), wrapInArray);
097        }
098
099        /**
100         * @param file the file to write to (UTF-8 encoding will be used)
101         * @throws IOException if there's a problem opening the file
102         */
103        public JCardWriter(File file) throws IOException {
104                this(new Utf8Writer(file));
105        }
106
107        /**
108         * @param file the file to write to (UTF-8 encoding will be used)
109         * @param wrapInArray true to enclose all written vCards in a JSON array,
110         * false not to
111         * @throws IOException if there's a problem opening the file
112         */
113        public JCardWriter(File file, boolean wrapInArray) throws IOException {
114                this(new Utf8Writer(file), wrapInArray);
115        }
116
117        /**
118         * @param writer the writer to write to
119         */
120        public JCardWriter(Writer writer) {
121                this(writer, false);
122        }
123
124        /**
125         * @param writer the writer to write to
126         * @param wrapInArray true to enclose all written vCards in a JSON array,
127         * false not to
128         */
129        public JCardWriter(Writer writer, boolean wrapInArray) {
130                this.writer = new JCardRawWriter(writer, wrapInArray);
131        }
132
133        /**
134         * @param generator the generator to write to
135         */
136        public JCardWriter(JsonGenerator generator) {
137                this.generator = generator;
138                this.writer = new JCardRawWriter(generator);
139        }
140
141        /**
142         * Writes a vCard to the stream.
143         * @param vcard the vCard that is being written
144         * @param properties the properties to write
145         * @throws IOException if there's a problem writing to the output stream
146         * @throws IllegalArgumentException if a scribe hasn't been registered for a
147         * custom property class (see: {@link #registerScribe registerScribe})
148         */
149        @Override
150        @SuppressWarnings({ "rawtypes", "unchecked" })
151        protected void _write(VCard vcard, List<VCardProperty> properties) throws IOException {
152                Object previousValue = getCurrentValue();
153
154                writer.writeStartVCard();
155                writer.writeProperty("version", VCardDataType.TEXT, JCardValue.single(targetVersion.getVersion()));
156
157                for (VCardProperty property : properties) {
158                        VCardPropertyScribe scribe = index.getPropertyScribe(property);
159
160                        //marshal the value
161                        JCardValue value;
162                        try {
163                                value = scribe.writeJson(property);
164                        } catch (SkipMeException e) {
165                                //property has requested not to be written
166                                continue;
167                        } catch (EmbeddedVCardException e) {
168                                //don't write because jCard does not support embedded vCards
169                                continue;
170                        }
171
172                        String group = property.getGroup();
173                        String name = scribe.getPropertyName().toLowerCase();
174                        VCardParameters parameters = scribe.prepareParameters(property, targetVersion, vcard);
175                        VCardDataType dataType = scribe.dataType(property, targetVersion);
176
177                        writer.writeProperty(group, name, parameters, dataType, value);
178                }
179
180                writer.writeEndVCard();
181
182                setCurrentValue(previousValue);
183        }
184
185/**
186         * If this object has a {@link JsonGenerator), and the generator has an
187         * output context, gets the current value of the output context.
188         * 
189         * @return the value of the object that is currently being serialized, if
190         *         available
191         */
192        private Object getCurrentValue() {
193                return (generator == null) ? null : generator.getCurrentValue();
194        }
195
196/**
197         * If this object has a {@link JsonGenerator), and the generator has an
198         * output context, sets the current value of the output context.
199         * 
200         * @param value
201         *            the object that is currently being serialized
202         */
203        private void setCurrentValue(Object value) {
204                if (generator != null) {
205                        generator.setCurrentValue(value);
206                }
207        }
208
209        @Override
210        protected VCardVersion getTargetVersion() {
211                return targetVersion;
212        }
213
214        /**
215         * Gets whether or not the JSON will be pretty-printed.
216         * @return true if it will be pretty-printed, false if not (defaults to
217         * false)
218         */
219        public boolean isPrettyPrint() {
220                return writer.isPrettyPrint();
221        }
222
223        /**
224         * Sets whether or not to pretty-print the JSON.
225         * @param prettyPrint true to pretty-print it, false not to (defaults to
226         * false)
227         */
228        public void setPrettyPrint(boolean prettyPrint) {
229                writer.setPrettyPrint(prettyPrint);
230        }
231
232        /**
233         * Sets the pretty printer to pretty-print the JSON with. Note that this
234         * method implicitly enables indenting, so {@code setPrettyPrint(true)} does
235         * not also need to be called.
236         * @param prettyPrinter the custom pretty printer (defaults to an instance
237         * of {@link JCardPrettyPrinter}, if {@code setPrettyPrint(true)} has been
238         * called.
239         */
240        public void setPrettyPrinter(PrettyPrinter prettyPrinter) {
241                writer.setPrettyPrinter(prettyPrinter);
242        }
243
244        /**
245         * Flushes the jCard data stream.
246         * @throws IOException if there's a problem flushing the stream
247         */
248        public void flush() throws IOException {
249                writer.flush();
250        }
251
252        /**
253         * Ends the jCard data stream, but does not close the underlying writer.
254         * @throws IOException if there's a problem closing the stream
255         */
256        public void closeJsonStream() throws IOException {
257                writer.closeJsonStream();
258        }
259
260        /**
261         * Ends the jCard data stream and closes the underlying writer.
262         * @throws IOException if there's a problem closing the stream
263         */
264        public void close() throws IOException {
265                writer.close();
266        }
267}