001 package ezvcard.io.json; 002 003 import static ezvcard.util.IOUtils.utf8Reader; 004 005 import java.io.Closeable; 006 import java.io.File; 007 import java.io.FileNotFoundException; 008 import java.io.IOException; 009 import java.io.InputStream; 010 import java.io.Reader; 011 import java.io.StringReader; 012 import java.util.ArrayList; 013 import java.util.List; 014 015 import ezvcard.Messages; 016 import ezvcard.VCard; 017 import ezvcard.VCardDataType; 018 import ezvcard.VCardVersion; 019 import ezvcard.io.CannotParseException; 020 import ezvcard.io.EmbeddedVCardException; 021 import ezvcard.io.SkipMeException; 022 import ezvcard.io.json.JCardRawReader.JCardDataStreamListener; 023 import ezvcard.io.scribe.RawPropertyScribe; 024 import ezvcard.io.scribe.ScribeIndex; 025 import ezvcard.io.scribe.VCardPropertyScribe; 026 import ezvcard.io.scribe.VCardPropertyScribe.Result; 027 import ezvcard.parameter.VCardParameters; 028 import ezvcard.property.RawProperty; 029 import ezvcard.property.VCardProperty; 030 031 /* 032 Copyright (c) 2013, Michael Angstadt 033 All rights reserved. 034 035 Redistribution and use in source and binary forms, with or without 036 modification, are permitted provided that the following conditions are met: 037 038 1. Redistributions of source code must retain the above copyright notice, this 039 list of conditions and the following disclaimer. 040 2. Redistributions in binary form must reproduce the above copyright notice, 041 this list of conditions and the following disclaimer in the documentation 042 and/or other materials provided with the distribution. 043 044 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 045 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 046 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 047 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 048 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 049 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 050 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 051 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 052 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 053 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 054 055 The views and conclusions contained in the software and documentation are those 056 of the authors and should not be interpreted as representing official policies, 057 either expressed or implied, of the FreeBSD Project. 058 */ 059 060 /** 061 * <p> 062 * Parses {@link VCard} objects from a JSON data stream (jCard format). 063 * </p> 064 * <p> 065 * <b>Example:</b> 066 * 067 * <pre class="brush:java"> 068 * File file = new File("vcards.json"); 069 * JCardReader jcardReader = new JCardReader(file); 070 * VCard vcard; 071 * while ((vcard = jcardReader.readNext()) != null){ 072 * ... 073 * } 074 * jcardReader.close(); 075 * </pre> 076 * 077 * </p> 078 * @author Michael Angstadt 079 * @see <a 080 * href="http://tools.ietf.org/html/draft-kewisch-vcard-in-json-04">jCard 081 * draft</a> 082 */ 083 public class JCardReader implements Closeable { 084 private ScribeIndex index = new ScribeIndex(); 085 private final List<String> warnings = new ArrayList<String>(); 086 private final JCardRawReader reader; 087 088 /** 089 * Creates a reader that parses jCards from a JSON string. 090 * @param json the JSON string 091 */ 092 public JCardReader(String json) { 093 this(new StringReader(json)); 094 } 095 096 /** 097 * Creates a reader that parses jCards from an input stream. 098 * @param in the input stream to read the vCards from 099 */ 100 public JCardReader(InputStream in) { 101 this(utf8Reader(in)); 102 } 103 104 /** 105 * Creates a reader that parses jCards from a file. 106 * @param file the file to read the vCards from 107 * @throws FileNotFoundException if the file doesn't exist 108 */ 109 public JCardReader(File file) throws FileNotFoundException { 110 this(utf8Reader(file)); 111 } 112 113 /** 114 * Creates a reader that parses jCards from a reader. 115 * @param reader the reader to read the vCards from 116 */ 117 public JCardReader(Reader reader) { 118 this.reader = new JCardRawReader(reader); 119 } 120 121 /** 122 * Reads the next vCard from the data stream. 123 * @return the next vCard or null if there are no more 124 * @throws IOException if there's a problem reading from the stream 125 */ 126 public VCard readNext() throws IOException { 127 if (reader.eof()) { 128 return null; 129 } 130 131 warnings.clear(); 132 133 JCardDataStreamListenerImpl listener = new JCardDataStreamListenerImpl(); 134 reader.readNext(listener); 135 VCard vcard = listener.vcard; 136 if (vcard != null && !listener.versionFound) { 137 addWarning(null, 29); 138 } 139 return vcard; 140 } 141 142 /** 143 * <p> 144 * Registers a property scribe. This is the same as calling: 145 * </p> 146 * <p> 147 * {@code getScribeIndex().register(scribe)} 148 * </p> 149 * @param scribe the scribe to register 150 */ 151 public void registerScribe(VCardPropertyScribe<? extends VCardProperty> scribe) { 152 index.register(scribe); 153 } 154 155 /** 156 * Gets the scribe index. 157 * @return the scribe index 158 */ 159 public ScribeIndex getScribeIndex() { 160 return index; 161 } 162 163 /** 164 * Sets the scribe index. 165 * @param index the scribe index 166 */ 167 public void setScribeIndex(ScribeIndex index) { 168 this.index = index; 169 } 170 171 /** 172 * Gets the warnings from the last vCard that was unmarshalled. This list is 173 * reset every time a new vCard is read. 174 * @return the warnings or empty list if there were no warnings 175 */ 176 public List<String> getWarnings() { 177 return new ArrayList<String>(warnings); 178 } 179 180 private void addWarning(String propertyName, int code, Object... args) { 181 String message = Messages.INSTANCE.getParseMessage(code, args); 182 addWarning(propertyName, message); 183 } 184 185 private void addWarning(String propertyName, String message) { 186 int code = (propertyName == null) ? 37 : 36; 187 int line = reader.getLineNum(); 188 189 String warning = Messages.INSTANCE.getParseMessage(code, line, propertyName, message); 190 warnings.add(warning); 191 } 192 193 public void close() throws IOException { 194 reader.close(); 195 } 196 197 private class JCardDataStreamListenerImpl implements JCardDataStreamListener { 198 private VCard vcard = null; 199 private boolean versionFound = false; 200 201 public void beginVCard() { 202 vcard = new VCard(); 203 vcard.setVersion(VCardVersion.V4_0); 204 } 205 206 public void readProperty(String group, String propertyName, VCardParameters parameters, VCardDataType dataType, JCardValue value) { 207 if ("version".equalsIgnoreCase(propertyName)) { 208 //don't unmarshal "version" because we don't treat it as a property 209 versionFound = true; 210 211 VCardVersion version = VCardVersion.valueOfByStr(value.asSingle()); 212 if (version != VCardVersion.V4_0) { 213 addWarning(propertyName, 30); 214 } 215 return; 216 } 217 218 VCardPropertyScribe<? extends VCardProperty> scribe = index.getPropertyScribe(propertyName); 219 if (scribe == null) { 220 scribe = new RawPropertyScribe(propertyName); 221 } 222 223 VCardProperty property; 224 try { 225 Result<? extends VCardProperty> result = scribe.parseJson(value, dataType, parameters); 226 227 for (String warning : result.getWarnings()) { 228 addWarning(warning, propertyName); 229 } 230 231 property = result.getProperty(); 232 property.setGroup(group); 233 } catch (SkipMeException e) { 234 addWarning(propertyName, 22, e.getMessage()); 235 return; 236 } catch (CannotParseException e) { 237 scribe = new RawPropertyScribe(propertyName); 238 Result<? extends VCardProperty> result = scribe.parseJson(value, dataType, parameters); 239 240 property = result.getProperty(); 241 property.setGroup(group); 242 243 String valueStr = ((RawProperty) property).getValue(); 244 addWarning(propertyName, 25, valueStr, e.getMessage()); 245 } catch (EmbeddedVCardException e) { 246 addWarning(propertyName, 31); 247 return; 248 } 249 250 vcard.addProperty(property); 251 } 252 } 253 }