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