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 }