001package ezvcard.io.chain;
002
003import java.io.IOException;
004import java.io.OutputStream;
005import java.io.StringWriter;
006import java.io.UncheckedIOException;
007import java.io.Writer;
008import java.nio.file.Path;
009import java.util.Collection;
010
011import ezvcard.Ezvcard;
012import ezvcard.VCard;
013import ezvcard.VCardVersion;
014import ezvcard.io.scribe.VCardPropertyScribe;
015import ezvcard.io.text.TargetApplication;
016import ezvcard.io.text.VCardWriter;
017import ezvcard.property.Address;
018import ezvcard.property.StructuredName;
019import ezvcard.property.VCardProperty;
020
021/*
022 Copyright (c) 2012-2023, Michael Angstadt
023 All rights reserved.
024
025 Redistribution and use in source and binary forms, with or without
026 modification, are permitted provided that the following conditions are met: 
027
028 1. Redistributions of source code must retain the above copyright notice, this
029 list of conditions and the following disclaimer. 
030 2. Redistributions in binary form must reproduce the above copyright notice,
031 this list of conditions and the following disclaimer in the documentation
032 and/or other materials provided with the distribution. 
033
034 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
035 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
036 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
037 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
038 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
039 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
040 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
041 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
042 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
043 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
044 */
045
046/**
047 * Chainer class for writing traditional, plain-text vCards.
048 * @see Ezvcard#write(Collection)
049 * @see Ezvcard#write(VCard...)
050 * @author Michael Angstadt
051 */
052public class ChainingTextWriter extends ChainingWriter<ChainingTextWriter> {
053        private VCardVersion version;
054        private boolean caretEncoding = false;
055        private Boolean includeTrailingSemicolons;
056        private boolean foldLines = true;
057        private TargetApplication targetApplication;
058
059        /**
060         * @param vcards the vCards to write
061         */
062        public ChainingTextWriter(Collection<VCard> vcards) {
063                super(vcards);
064        }
065
066        /**
067         * <p>
068         * Sets the version that all the vCards will be marshalled to. The version
069         * that is attached to each individual {@link VCard} object will be ignored.
070         * </p>
071         * <p>
072         * If no version is passed into this method, the writer will look at the
073         * version attached to each individual {@link VCard} object and marshal it
074         * to that version. And if a {@link VCard} object has no version attached to
075         * it, then it will be marshalled to version 3.0.
076         * </p>
077         * @param version the version to marshal the vCards to
078         * @return this
079         */
080        public ChainingTextWriter version(VCardVersion version) {
081                this.version = version;
082                return this;
083        }
084
085        /**
086         * Sets whether the writer will use circumflex accent encoding for vCard 3.0
087         * and 4.0 parameter values (disabled by default).
088         * @param enable true to use circumflex accent encoding, false not to
089         * @return this
090         * @see VCardWriter#setCaretEncodingEnabled(boolean)
091         * @see <a href="http://tools.ietf.org/html/rfc6868">RFC 6868</a>
092         */
093        public ChainingTextWriter caretEncoding(boolean enable) {
094                this.caretEncoding = enable;
095                return this;
096        }
097
098        /**
099         * <p>
100         * Sets whether to include trailing semicolon delimiters for structured
101         * property values whose list of values end with null or empty values.
102         * Examples of properties that use structured values are
103         * {@link StructuredName} and {@link Address}.
104         * </p>
105         * <p>
106         * This setting exists for compatibility reasons and should not make a
107         * difference to consumers that correctly implement the vCard grammar.
108         * </p>
109         * @param include true to include the trailing semicolons, false not to,
110         * null to use the default behavior (defaults to false for vCard versions
111         * 2.1 and 3.0 and true for vCard version 4.0)
112         * @return this
113         * @see <a href="https://github.com/mangstadt/ez-vcard/issues/57">Issue
114         * 57</a>
115         */
116        public ChainingTextWriter includeTrailingSemicolons(Boolean include) {
117                this.includeTrailingSemicolons = include;
118                return this;
119        }
120
121        /**
122         * <p>
123         * Sets whether to fold long lines. Line folding is when long lines are
124         * split up into multiple lines. No data is lost or changed when a line is
125         * folded.
126         * </p>
127         * <p>
128         * Line folding is enabled by default. If the vCard consumer is not parsing
129         * your vCards properly, disabling line folding may help.
130         * </p>
131         * @param foldLines true to enable line folding, false to disable it
132         * (defaults to true)
133         * @return this
134         */
135        public ChainingTextWriter foldLines(boolean foldLines) {
136                this.foldLines = foldLines;
137                return this;
138        }
139
140        /**
141         * <p>
142         * Sets the application that the vCards will be targeted for.
143         * </p>
144         * <p>
145         * Some vCard consumers do not completely adhere to the vCard specifications
146         * and require their vCards to be formatted in a specific way. See the
147         * {@link TargetApplication} class for a list of these applications.
148         * </p>
149         * @param targetApplication the target application or null if the vCards do
150         * not require any special processing (defaults to null)
151         * @return this
152         * @see VCardWriter#setTargetApplication(TargetApplication)
153         */
154        public ChainingTextWriter targetApplication(TargetApplication targetApplication) {
155                this.targetApplication = targetApplication;
156                return this;
157        }
158
159        @Override
160        public ChainingTextWriter prodId(boolean include) {
161                return super.prodId(include);
162        }
163
164        @Override
165        public ChainingTextWriter versionStrict(boolean versionStrict) {
166                return super.versionStrict(versionStrict);
167        }
168
169        @Override
170        public ChainingTextWriter register(VCardPropertyScribe<? extends VCardProperty> scribe) {
171                return super.register(scribe);
172        }
173
174        /**
175         * Writes the vCards to a string.
176         * @return the vCard string
177         */
178        public String go() {
179                StringWriter sw = new StringWriter();
180                try {
181                        go(sw);
182                } catch (IOException e) {
183                        //should never be thrown because we're writing to a string
184                        throw new UncheckedIOException(e);
185                }
186                return sw.toString();
187        }
188
189        /**
190         * Writes the vCards to an output stream.
191         * @param out the output stream to write to
192         * @throws IOException if there's a problem writing to the output stream
193         */
194        public void go(OutputStream out) throws IOException {
195                go(new VCardWriter(out, getVCardWriterConstructorVersion()));
196        }
197
198        /**
199         * Writes the vCards to a file. If the file exists, it will be overwritten.
200         * @param file the file to write to
201         * @throws IOException if there's a problem writing to the file
202         */
203        public void go(Path file) throws IOException {
204                go(file, false);
205        }
206
207        /**
208         * Writes the vCards to a file.
209         * @param file the file to write to
210         * @param append true to append onto the end of the file, false to overwrite
211         * it
212         * @throws IOException if there's a problem writing to the file
213         */
214        public void go(Path file, boolean append) throws IOException {
215                try (VCardWriter writer = new VCardWriter(file, append, getVCardWriterConstructorVersion())) {
216                        go(writer);
217                }
218        }
219
220        /**
221         * Writes the vCards to a writer.
222         * @param writer the writer to write to
223         * @throws IOException if there's a problem writing to the writer
224         */
225        public void go(Writer writer) throws IOException {
226                go(new VCardWriter(writer, getVCardWriterConstructorVersion()));
227        }
228
229        private void go(VCardWriter writer) throws IOException {
230                writer.setAddProdId(prodId);
231                writer.setCaretEncodingEnabled(caretEncoding);
232                writer.setVersionStrict(versionStrict);
233                writer.setIncludeTrailingSemicolons(includeTrailingSemicolons);
234                if (!foldLines) {
235                        writer.getVObjectWriter().getFoldedLineWriter().setLineLength(null);
236                }
237                writer.setTargetApplication(targetApplication);
238                if (index != null) {
239                        writer.setScribeIndex(index);
240                }
241
242                for (VCard vcard : vcards) {
243                        if (version == null) {
244                                //use the version that's assigned to each individual vCard
245                                VCardVersion vcardVersion = vcard.getVersion();
246                                if (vcardVersion == null) {
247                                        vcardVersion = VCardVersion.V3_0;
248                                }
249                                writer.setTargetVersion(vcardVersion);
250                        }
251                        writer.write(vcard);
252                        writer.flush();
253                }
254        }
255
256        /**
257         * <p>
258         * Gets the {@link VCardVersion} object to pass into the {@link VCardWriter}
259         * constructor. The constructor does not allow a null version, so this
260         * method ensures that a non-null version is passed in.
261         * </p>
262         * <p>
263         * If the user hasn't chosen a version, the version that is passed into the
264         * constructor doesn't matter. This is because the writer's target version
265         * is reset every time a vCard is written (see the {@link #go(VCardWriter)}
266         * method).
267         * </p>
268         * @return the version to pass into the constructor
269         */
270        private VCardVersion getVCardWriterConstructorVersion() {
271                return (version == null) ? VCardVersion.V3_0 : version;
272        }
273}