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    }