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.Set;
009
010import ezvcard.Ezvcard;
011import ezvcard.Messages;
012import ezvcard.VCard;
013import ezvcard.VCardVersion;
014import ezvcard.io.scribe.ScribeIndex;
015import ezvcard.io.scribe.VCardPropertyScribe;
016import ezvcard.property.Address;
017import ezvcard.property.Label;
018import ezvcard.property.ProductId;
019import ezvcard.property.RawProperty;
020import ezvcard.property.VCardProperty;
021
022/*
023 Copyright (c) 2012-2023, Michael Angstadt
024 All rights reserved.
025
026 Redistribution and use in source and binary forms, with or without
027 modification, are permitted provided that the following conditions are met: 
028
029 1. Redistributions of source code must retain the above copyright notice, this
030 list of conditions and the following disclaimer. 
031 2. Redistributions in binary form must reproduce the above copyright notice,
032 this list of conditions and the following disclaimer in the documentation
033 and/or other materials provided with the distribution. 
034
035 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
036 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
037 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
038 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
039 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
040 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
041 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
042 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
043 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
044 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
045
046 The views and conclusions contained in the software and documentation are those
047 of the authors and should not be interpreted as representing official policies, 
048 either expressed or implied, of the FreeBSD Project.
049 */
050
051/**
052 * Writes vCards to a data stream.
053 * @author Michael Angstadt
054 */
055public abstract class StreamWriter implements Closeable {
056        protected ScribeIndex index = new ScribeIndex();
057        protected boolean addProdId = true;
058        protected boolean versionStrict = true;
059
060        /**
061         * Writes a vCard to the stream.
062         * @param vcard the vCard that is being written
063         * @throws IOException if there's a problem writing to the output stream
064         * @throws IllegalArgumentException if a scribe hasn't been registered for a
065         * custom property class (see: {@link #registerScribe registerScribe})
066         */
067        public void write(VCard vcard) throws IOException {
068                List<VCardProperty> properties = prepare(vcard);
069                _write(vcard, properties);
070        }
071
072        /**
073         * Writes a vCard to the stream.
074         * @param vcard the vCard that is being written
075         * @param properties the properties to write
076         * @throws IOException if there's a problem writing to the output stream
077         */
078        protected abstract void _write(VCard vcard, List<VCardProperty> properties) throws IOException;
079
080        /**
081         * Gets the version that the next vCard will be written as.
082         * @return the version
083         */
084        protected abstract VCardVersion getTargetVersion();
085
086        /**
087         * Gets whether a {@link ProductId} property will be added to each vCard
088         * that marks it as having been generated by this library. For 2.1 vCards,
089         * the extended property "X-PRODID" will be added, since {@link ProductId}
090         * is not supported by that version.
091         * @return true if the property will be added, false if not (defaults to
092         * true)
093         */
094        public boolean isAddProdId() {
095                return addProdId;
096        }
097
098        /**
099         * Sets whether to add a {@link ProductId} property to each vCard that marks
100         * it as having been generated by this library. For 2.1 vCards, the extended
101         * property "X-PRODID" will be added, since {@link ProductId} is not
102         * supported by that version.
103         * @param addProdId true to add the property, false not to (defaults to
104         * true)
105         */
106        public void setAddProdId(boolean addProdId) {
107                this.addProdId = addProdId;
108        }
109
110        /**
111         * Gets whether properties that do not support the target version will be
112         * excluded from the written vCard.
113         * @return true to exclude properties that do not support the target
114         * version, false not to (defaults to true)
115         */
116        public boolean isVersionStrict() {
117                return versionStrict;
118        }
119
120        /**
121         * Sets whether to exclude properties that do not support the target version
122         * from the written vCard.
123         * @param versionStrict true to exclude such properties, false not to
124         * (defaults to true)
125         */
126        public void setVersionStrict(boolean versionStrict) {
127                this.versionStrict = versionStrict;
128        }
129
130        /**
131         * <p>
132         * Registers a property scribe. This is the same as calling:
133         * </p>
134         * <p>
135         * {@code getScribeIndex().register(scribe)}
136         * </p>
137         * @param scribe the scribe to register
138         */
139        public void registerScribe(VCardPropertyScribe<? extends VCardProperty> scribe) {
140                index.register(scribe);
141        }
142
143        /**
144         * Gets the scribe index.
145         * @return the scribe index
146         */
147        public ScribeIndex getScribeIndex() {
148                return index;
149        }
150
151        /**
152         * Sets the scribe index.
153         * @param index the scribe index
154         */
155        public void setScribeIndex(ScribeIndex index) {
156                this.index = index;
157        }
158
159        /**
160         * Determines which properties need to be written.
161         * @param vcard the vCard to write
162         * @return the properties to write
163         * @throws IllegalArgumentException if a scribe hasn't been registered for a
164         * custom property class (see: {@link #registerScribe(VCardPropertyScribe)
165         * registerScribe})
166         */
167        private List<VCardProperty> prepare(VCard vcard) {
168                VCardVersion targetVersion = getTargetVersion();
169                List<VCardProperty> propertiesToAdd = new ArrayList<>();
170                Set<Class<? extends VCardProperty>> unregistered = new HashSet<>();
171                VCardProperty prodIdProperty = null;
172                for (VCardProperty property : vcard) {
173                        if (versionStrict && !property.isSupportedBy(targetVersion)) {
174                                //do not add the property to the vCard if it is not supported by the target version
175                                continue;
176                        }
177
178                        //do not add PRODID to the property list yet
179                        if (property instanceof ProductId) {
180                                prodIdProperty = property;
181                                continue;
182                        }
183
184                        //check for scribe
185                        if (!index.hasPropertyScribe(property)) {
186                                unregistered.add(property.getClass());
187                                continue;
188                        }
189
190                        propertiesToAdd.add(property);
191
192                        //add LABEL properties for each ADR property if the target version is 2.1 or 3.0
193                        if ((targetVersion == VCardVersion.V2_1 || targetVersion == VCardVersion.V3_0) && property instanceof Address) {
194                                Address adr = (Address) property;
195                                String labelStr = adr.getLabel();
196                                if (labelStr == null) {
197                                        continue;
198                                }
199
200                                Label label = new Label(labelStr);
201                                label.getTypes().addAll(adr.getTypes());
202                                propertiesToAdd.add(label);
203                        }
204                }
205
206                if (!unregistered.isEmpty()) {
207                        List<String> classes = new ArrayList<>(unregistered.size());
208                        for (Class<? extends VCardProperty> clazz : unregistered) {
209                                classes.add(clazz.getName());
210                        }
211                        throw Messages.INSTANCE.getIllegalArgumentException(14, classes);
212                }
213
214                //create a PRODID property, saying the vCard was generated by this library
215                if (addProdId) {
216                        if (targetVersion == VCardVersion.V2_1) {
217                                prodIdProperty = new RawProperty("X-PRODID", "ez-vcard " + Ezvcard.VERSION);
218                        } else {
219                                prodIdProperty = new ProductId("ez-vcard " + Ezvcard.VERSION);
220                        }
221                }
222
223                //add PRODID to the beginning of the vCard
224                if (prodIdProperty != null) {
225                        propertiesToAdd.add(0, prodIdProperty);
226                }
227
228                return propertiesToAdd;
229        }
230}