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