001package ezvcard.io.json;
002
003import java.io.IOException;
004import java.io.InputStream;
005import java.io.InputStreamReader;
006import java.io.Reader;
007import java.io.StringReader;
008import java.nio.charset.StandardCharsets;
009import java.nio.file.Files;
010import java.nio.file.Path;
011
012import com.fasterxml.jackson.core.JsonParser;
013
014import ezvcard.VCard;
015import ezvcard.VCardDataType;
016import ezvcard.VCardVersion;
017import ezvcard.io.CannotParseException;
018import ezvcard.io.EmbeddedVCardException;
019import ezvcard.io.ParseWarning;
020import ezvcard.io.SkipMeException;
021import ezvcard.io.StreamReader;
022import ezvcard.io.json.JCardRawReader.JCardDataStreamListener;
023import ezvcard.io.scribe.RawPropertyScribe;
024import ezvcard.io.scribe.VCardPropertyScribe;
025import ezvcard.parameter.VCardParameters;
026import ezvcard.property.VCardProperty;
027
028/*
029 Copyright (c) 2012-2023, Michael Angstadt
030 All rights reserved.
031
032 Redistribution and use in source and binary forms, with or without
033 modification, are permitted provided that the following conditions are met: 
034
035 1. Redistributions of source code must retain the above copyright notice, this
036 list of conditions and the following disclaimer. 
037 2. Redistributions in binary form must reproduce the above copyright notice,
038 this list of conditions and the following disclaimer in the documentation
039 and/or other materials provided with the distribution. 
040
041 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
042 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
043 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
044 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
045 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
046 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
047 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
048 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
049 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
050 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
051
052 The views and conclusions contained in the software and documentation are those
053 of the authors and should not be interpreted as representing official policies, 
054 either expressed or implied, of the FreeBSD Project.
055 */
056
057/**
058 * <p>
059 * Parses {@link VCard} objects from a JSON data stream (jCard format).
060 * </p>
061 * <p>
062 * <b>Example:</b>
063 * </p>
064 * 
065 * <pre class="brush:java">
066 * Path file = Paths.get("vcards.json");
067 * try (JCardReader reader = new JCardReader(file)) {
068 *   VCard vcard;
069 *   while ((vcard = reader.readNext()) != null) {
070 *     //...
071 *   }
072 * }
073 * </pre>
074 * @author Michael Angstadt
075 * @see <a href="http://tools.ietf.org/html/rfc7095">RFC 7095</a>
076 */
077public class JCardReader extends StreamReader {
078        private final JCardRawReader reader;
079
080        /**
081         * @param json the JSON string to read from
082         */
083        public JCardReader(String json) {
084                this(new StringReader(json));
085        }
086
087        /**
088         * @param in the input stream to read from
089         */
090        public JCardReader(InputStream in) {
091                this(new InputStreamReader(in, StandardCharsets.UTF_8));
092        }
093
094        /**
095         * @param file the file to read from
096         * @throws IOException if there is a problem reading the file
097         */
098        public JCardReader(Path file) throws IOException {
099                this(Files.newBufferedReader(file, StandardCharsets.UTF_8));
100        }
101
102        /**
103         * @param reader the reader to read from
104         */
105        public JCardReader(Reader reader) {
106                this.reader = new JCardRawReader(reader);
107        }
108
109        /**
110         * @param parser the parser to read from
111         */
112        public JCardReader(JsonParser parser) {
113                this.reader = new JCardRawReader(parser, true);
114        }
115
116        @Override
117        protected VCard _readNext() throws IOException {
118                if (reader.eof()) {
119                        return null;
120                }
121
122                context.setVersion(VCardVersion.V4_0);
123
124                JCardDataStreamListenerImpl listener = new JCardDataStreamListenerImpl();
125                reader.readNext(listener);
126                VCard vcard = listener.vcard;
127                if (vcard != null && !listener.versionFound) {
128                        //@formatter:off
129                        warnings.add(new ParseWarning.Builder()
130                                .lineNumber(reader.getLineNum())
131                                .message(29)
132                                .build()
133                        );
134                        //@formatter:on
135                }
136                return vcard;
137        }
138
139        public void close() throws IOException {
140                reader.close();
141        }
142
143        private class JCardDataStreamListenerImpl implements JCardDataStreamListener {
144                private VCard vcard = null;
145                private boolean versionFound = false;
146
147                public void beginVCard() {
148                        vcard = new VCard();
149                        vcard.setVersion(VCardVersion.V4_0);
150                }
151
152                public void readProperty(String group, String propertyName, VCardParameters parameters, VCardDataType dataType, JCardValue value) {
153                        context.getWarnings().clear();
154                        context.setLineNumber(reader.getLineNum());
155                        context.setPropertyName(propertyName);
156
157                        if ("version".equalsIgnoreCase(propertyName)) {
158                                //don't unmarshal "version" because we don't treat it as a property
159                                versionFound = true;
160
161                                VCardVersion version = VCardVersion.valueOfByStr(value.asSingle());
162                                if (version != VCardVersion.V4_0) {
163                                        //@formatter:off
164                                        warnings.add(new ParseWarning.Builder(context)
165                                                .message(30)
166                                                .build()
167                                        );
168                                        //@formatter:on
169                                }
170                                return;
171                        }
172
173                        VCardPropertyScribe<? extends VCardProperty> scribe = index.getPropertyScribe(propertyName);
174                        if (scribe == null) {
175                                scribe = new RawPropertyScribe(propertyName);
176                        }
177
178                        VCardProperty property;
179                        try {
180                                property = scribe.parseJson(value, dataType, parameters, context);
181                                warnings.addAll(context.getWarnings());
182                        } catch (SkipMeException e) {
183                                //@formatter:off
184                                warnings.add(new ParseWarning.Builder(context)
185                                        .message(22, e.getMessage())
186                                        .build()
187                                );
188                                //@formatter:on
189                                return;
190                        } catch (CannotParseException e) {
191                                scribe = new RawPropertyScribe(propertyName);
192                                property = scribe.parseJson(value, dataType, parameters, context);
193
194                                //@formatter:off
195                                warnings.add(new ParseWarning.Builder(context)
196                                        .message(e)
197                                        .build()
198                                );
199                                //@formatter:on
200                        } catch (EmbeddedVCardException e) {
201                                //@formatter:off
202                                warnings.add(new ParseWarning.Builder(context)
203                                        .message(31)
204                                        .build()
205                                );
206                                //@formatter:on
207                                return;
208                        }
209
210                        property.setGroup(group);
211                        vcard.addProperty(property);
212                }
213        }
214}