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