001 package ezvcard.io.json;
002
003 import java.io.Closeable;
004 import java.io.IOException;
005 import java.io.Reader;
006 import java.util.ArrayList;
007 import java.util.HashMap;
008 import java.util.List;
009 import java.util.Map;
010
011 import com.fasterxml.jackson.core.JsonFactory;
012 import com.fasterxml.jackson.core.JsonParseException;
013 import com.fasterxml.jackson.core.JsonParser;
014 import com.fasterxml.jackson.core.JsonToken;
015
016 import ezvcard.VCardDataType;
017 import ezvcard.parameter.VCardParameters;
018
019 /*
020 Copyright (c) 2013, Michael Angstadt
021 All rights reserved.
022
023 Redistribution and use in source and binary forms, with or without
024 modification, are permitted provided that the following conditions are met:
025
026 1. Redistributions of source code must retain the above copyright notice, this
027 list of conditions and the following disclaimer.
028 2. Redistributions in binary form must reproduce the above copyright notice,
029 this list of conditions and the following disclaimer in the documentation
030 and/or other materials provided with the distribution.
031
032 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
033 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
034 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
035 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
036 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
037 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
038 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
039 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
040 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
041 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
042 */
043
044 /**
045 * Parses an vCard JSON data stream (jCard).
046 * @author Michael Angstadt
047 * @see <a
048 * href="http://tools.ietf.org/html/draft-kewisch-vcard-in-json-04">jCard
049 * draft</a>
050 */
051 public class JCardRawReader implements Closeable {
052 private final Reader reader;
053 private JsonParser jp;
054 private boolean eof = false;
055 private JCardDataStreamListener listener;
056
057 /**
058 * Creates a new reader.
059 * @param reader the reader to the data stream
060 */
061 public JCardRawReader(Reader reader) {
062 this.reader = reader;
063 }
064
065 /**
066 * Gets the current line number.
067 * @return the line number
068 */
069 public int getLineNum() {
070 return (jp == null) ? 0 : jp.getCurrentLocation().getLineNr();
071 }
072
073 /**
074 * Reads the next vCard from the jCard data stream.
075 * @param listener handles the vCard data as it is read off the wire
076 * @throws JCardParseException if the jCard syntax is incorrect (the JSON
077 * syntax may be valid, but it is not in the correct jCard format).
078 * @throws JsonParseException if the JSON syntax is incorrect
079 * @throws IOException if there is a problem reading from the data stream
080 */
081 public void readNext(JCardDataStreamListener listener) throws IOException {
082 if (jp == null) {
083 JsonFactory factory = new JsonFactory();
084 jp = factory.createJsonParser(reader);
085 } else if (jp.isClosed()) {
086 return;
087 }
088
089 this.listener = listener;
090
091 //find the next vCard object
092 JsonToken prev = null;
093 JsonToken cur;
094 while ((cur = jp.nextToken()) != null) {
095 if (prev == JsonToken.START_ARRAY && cur == JsonToken.VALUE_STRING && "vcard".equals(jp.getValueAsString())) {
096 break;
097 }
098 prev = cur;
099 }
100 if (cur == null) {
101 //EOF
102 eof = true;
103 return;
104 }
105
106 listener.beginVCard();
107 parseProperties();
108 }
109
110 private void parseProperties() throws IOException {
111 //start properties array
112 if (jp.nextToken() != JsonToken.START_ARRAY) {
113 throw new JCardParseException(JsonToken.START_ARRAY, jp.getCurrentToken());
114 }
115
116 //read properties
117 while (jp.nextToken() != JsonToken.END_ARRAY) { //until we reach the end properties array
118 if (jp.getCurrentToken() != JsonToken.START_ARRAY) {
119 throw new JCardParseException(JsonToken.START_ARRAY, jp.getCurrentToken());
120 }
121 jp.nextToken();
122 parseProperty();
123 }
124 }
125
126 private void parseProperty() throws IOException {
127 //get property name
128 if (jp.getCurrentToken() != JsonToken.VALUE_STRING) {
129 throw new JCardParseException(JsonToken.VALUE_STRING, jp.getCurrentToken());
130 }
131 String propertyName = jp.getValueAsString().toLowerCase();
132
133 //get parameters
134 VCardParameters parameters = parseParameters();
135
136 //get group
137 List<String> removed = parameters.removeAll("group");
138 String group = removed.isEmpty() ? null : removed.get(0);
139
140 //get data type
141 if (jp.nextToken() != JsonToken.VALUE_STRING) {
142 throw new JCardParseException(JsonToken.VALUE_STRING, jp.getCurrentToken());
143 }
144 String dataTypeStr = jp.getText().toLowerCase();
145 VCardDataType dataType = "unknown".equals(dataTypeStr) ? null : VCardDataType.get(dataTypeStr);
146
147 //get property value(s)
148 List<JsonValue> values = parseValues();
149
150 JCardValue value = new JCardValue(values);
151 listener.readProperty(group, propertyName, parameters, dataType, value);
152 }
153
154 private VCardParameters parseParameters() throws IOException {
155 if (jp.nextToken() != JsonToken.START_OBJECT) {
156 throw new JCardParseException(JsonToken.START_OBJECT, jp.getCurrentToken());
157 }
158
159 VCardParameters parameters = new VCardParameters();
160 while (jp.nextToken() != JsonToken.END_OBJECT) {
161 String parameterName = jp.getText();
162
163 if (jp.nextToken() == JsonToken.START_ARRAY) {
164 //multi-valued parameter
165 while (jp.nextToken() != JsonToken.END_ARRAY) {
166 parameters.put(parameterName, jp.getText());
167 }
168 } else {
169 parameters.put(parameterName, jp.getValueAsString());
170 }
171 }
172
173 return parameters;
174 }
175
176 private List<JsonValue> parseValues() throws IOException {
177 List<JsonValue> values = new ArrayList<JsonValue>();
178 while (jp.nextToken() != JsonToken.END_ARRAY) { //until we reach the end of the property array
179 JsonValue value = parseValue();
180 values.add(value);
181 }
182 return values;
183 }
184
185 private Object parseValueElement() throws IOException {
186 switch (jp.getCurrentToken()) {
187 case VALUE_FALSE:
188 case VALUE_TRUE:
189 return jp.getBooleanValue();
190 case VALUE_NUMBER_FLOAT:
191 return jp.getDoubleValue();
192 case VALUE_NUMBER_INT:
193 return jp.getLongValue();
194 case VALUE_NULL:
195 return null;
196 default:
197 return jp.getText();
198 }
199 }
200
201 private List<JsonValue> parseValueArray() throws IOException {
202 List<JsonValue> array = new ArrayList<JsonValue>();
203
204 while (jp.nextToken() != JsonToken.END_ARRAY) {
205 JsonValue value = parseValue();
206 array.add(value);
207 }
208
209 return array;
210 }
211
212 private Map<String, JsonValue> parseValueObject() throws IOException {
213 Map<String, JsonValue> object = new HashMap<String, JsonValue>();
214
215 jp.nextToken();
216 while (jp.getCurrentToken() != JsonToken.END_OBJECT) {
217 if (jp.getCurrentToken() != JsonToken.FIELD_NAME) {
218 throw new JCardParseException(JsonToken.FIELD_NAME, jp.getCurrentToken());
219 }
220
221 String key = jp.getText();
222 jp.nextToken();
223 JsonValue value = parseValue();
224 object.put(key, value);
225
226 jp.nextToken();
227 }
228
229 return object;
230 }
231
232 private JsonValue parseValue() throws IOException {
233 switch (jp.getCurrentToken()) {
234 case START_ARRAY:
235 return new JsonValue(parseValueArray());
236 case START_OBJECT:
237 return new JsonValue(parseValueObject());
238 default:
239 return new JsonValue(parseValueElement());
240 }
241 }
242
243 /**
244 * Determines whether the end of the data stream has been reached.
245 * @return true if the end has been reached, false if not
246 */
247 public boolean eof() {
248 return eof;
249 }
250
251 /**
252 * Handles the vCard data as it is read off the data stream.
253 * @author Michael Angstadt
254 */
255 public static interface JCardDataStreamListener {
256 /**
257 * Called when a vCard has been found in the stream.
258 */
259 void beginVCard();
260
261 /**
262 * Called when a property is read.
263 * @param group the group or null if there is not group
264 * @param propertyName the property name (e.g. "summary")
265 * @param parameters the parameters
266 * @param dataType the data type or null for "unknown"
267 * @param value the property value
268 */
269 void readProperty(String group, String propertyName, VCardParameters parameters, VCardDataType dataType, JCardValue value);
270 }
271
272 /**
273 * Closes the underlying {@link Reader} object.
274 */
275 public void close() throws IOException {
276 reader.close();
277 }
278 }