001package ezvcard.io;
002
003import java.io.Closeable;
004import java.io.IOException;
005import java.util.ArrayList;
006import java.util.HashSet;
007import java.util.List;
008import java.util.Optional;
009import java.util.Set;
010
011import ezvcard.VCard;
012import ezvcard.io.scribe.ScribeIndex;
013import ezvcard.io.scribe.VCardPropertyScribe;
014import ezvcard.parameter.AddressType;
015import ezvcard.property.Address;
016import ezvcard.property.Label;
017import ezvcard.property.VCardProperty;
018
019/*
020 Copyright (c) 2012-2026, 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 The views and conclusions contained in the software and documentation are those
044 of the authors and should not be interpreted as representing official policies, 
045 either expressed or implied, of the FreeBSD Project.
046 */
047
048/**
049 * Parses vCards from a data stream.
050 * @author Michael Angstadt
051 */
052public abstract class StreamReader implements Closeable {
053        protected final List<ParseWarning> warnings = new ArrayList<>();
054        protected ScribeIndex index = new ScribeIndex();
055        protected ParseContext context;
056
057        /**
058         * Reads all vCards from the data stream.
059         * @return the vCards
060         * @throws IOException if there's a problem reading from the stream
061         */
062        public List<VCard> readAll() throws IOException {
063                List<VCard> vcards = new ArrayList<>();
064                VCard vcard;
065                while ((vcard = readNext()) != null) {
066                        vcards.add(vcard);
067                }
068                return vcards;
069        }
070
071        /**
072         * Reads the next vCard from the data stream.
073         * @return the next vCard or null if there are no more
074         * @throws IOException if there's a problem reading from the stream
075         */
076        public VCard readNext() throws IOException {
077                warnings.clear();
078                context = new ParseContext();
079                return _readNext();
080        }
081
082        /**
083         * Reads the next vCard from the data stream.
084         * @return the next vCard or null if there are no more
085         * @throws IOException if there's a problem reading from the stream
086         */
087        protected abstract VCard _readNext() throws IOException;
088
089        /**
090         * Matches up a list of {@link Label} properties with their corresponding
091         * {@link Address} properties. If no match can be found, then the LABEL
092         * property itself is assigned to the vCard.
093         * @param vcard the vCard that the properties belong to
094         * @param labels the LABEL properties
095         */
096        protected void assignLabels(VCard vcard, List<Label> labels) {
097                List<Address> adrs = vcard.getAddresses();
098                for (Label label : labels) {
099                        Set<AddressType> labelTypes = new HashSet<>(label.getTypes());
100
101                        //@formatter:off
102                        Optional<Address> matchingAdr = adrs.stream()
103                                .filter(adr -> adr.getLabel() == null) //only consider addresses that don't have a label assigned to them
104                                .filter(adr -> labelTypes.equals(new HashSet<>(adr.getTypes()))) //do the types match?
105                        .findFirst();
106                        //@formatter:on
107
108                        if (matchingAdr.isPresent()) {
109                                matchingAdr.get().setLabel(label.getValue());
110                        } else {
111                                vcard.addOrphanedLabel(label);
112                        }
113                }
114        }
115
116        /**
117         * <p>
118         * Registers a property scribe. This is the same as calling:
119         * </p>
120         * <p>
121         * {@code getScribeIndex().register(scribe)}
122         * </p>
123         * @param scribe the scribe to register
124         */
125        public void registerScribe(VCardPropertyScribe<? extends VCardProperty> scribe) {
126                index.register(scribe);
127        }
128
129        /**
130         * Gets the scribe index.
131         * @return the scribe index
132         */
133        public ScribeIndex getScribeIndex() {
134                return index;
135        }
136
137        /**
138         * Sets the scribe index.
139         * @param index the scribe index
140         */
141        public void setScribeIndex(ScribeIndex index) {
142                this.index = index;
143        }
144
145        /**
146         * Gets the warnings from the last vCard that was unmarshalled. This list is
147         * reset every time a new vCard is read.
148         * @return the warnings or empty list if there were no warnings
149         */
150        public List<ParseWarning> getWarnings() {
151                return new ArrayList<>(warnings);
152        }
153}