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 }