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 }