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}