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 }