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}