001package ezvcard.property; 002 003import java.util.ArrayList; 004import java.util.LinkedHashMap; 005import java.util.List; 006import java.util.Map; 007 008import ezvcard.VCard; 009import ezvcard.VCardVersion; 010import ezvcard.ValidationWarning; 011import ezvcard.parameter.AddressType; 012import ezvcard.parameter.Pid; 013import ezvcard.parameter.VCardParameters; 014import ezvcard.util.GeoUri; 015import ezvcard.util.StringUtils; 016 017/* 018 Copyright (c) 2012-2023, Michael Angstadt 019 All rights reserved. 020 021 Redistribution and use in source and binary forms, with or without 022 modification, are permitted provided that the following conditions are met: 023 024 1. Redistributions of source code must retain the above copyright notice, this 025 list of conditions and the following disclaimer. 026 2. Redistributions in binary form must reproduce the above copyright notice, 027 this list of conditions and the following disclaimer in the documentation 028 and/or other materials provided with the distribution. 029 030 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 031 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 032 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 033 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 034 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 035 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 036 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 037 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 038 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 039 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 040 041 The views and conclusions contained in the software and documentation are those 042 of the authors and should not be interpreted as representing official policies, 043 either expressed or implied, of the FreeBSD Project. 044 */ 045 046/** 047 * <p> 048 * Defines a mailing address. 049 * </p> 050 * 051 * <p> 052 * <b>Code sample (creating)</b> 053 * </p> 054 * 055 * <pre class="brush:java"> 056 * VCard vcard = new VCard(); 057 * 058 * Address adr = new Address(); 059 * adr.setStreetAddress("123 Main St."); 060 * adr.setLocality("Austin"); 061 * adr.setRegion("TX"); 062 * adr.setPostalCode("12345"); 063 * adr.setCountry("USA"); 064 * adr.getTypes().add(AddressType.WORK); 065 * 066 * //optionally, set the text to print on the mailing label 067 * adr.setLabel("123 Main St.\nAustin, TX 12345\nUSA"); 068 * 069 * vcard.addAddress(adr); 070 * </pre> 071 * 072 * <p> 073 * <b>Code sample (retrieving)</b> 074 * </p> 075 * 076 * <pre class="brush:java"> 077 * VCard vcard = ... 078 * for (Address adr : vcard.getAddresses()) { 079 * String street = adr.getStreetAddress(); 080 * String city = adr.getLocality(); 081 * //etc. 082 * } 083 * </pre> 084 * 085 * <p> 086 * <b>Only part of the street address is being returned!</b> 087 * </p> 088 * <p> 089 * This usually means that the vCard you parsed contains unescaped comma 090 * characters. To get the full address, use the {@link #getStreetAddressFull} 091 * method. 092 * </p> 093 * 094 * <p> 095 * <b>Property name:</b> {@code ADR} 096 * </p> 097 * <p> 098 * <b>Supported versions:</b> {@code 2.1, 3.0, 4.0} 099 * </p> 100 * @author Michael Angstadt 101 * @see <a href="http://tools.ietf.org/html/rfc6350#page-32">RFC 6350 p.32</a> 102 * @see <a href="http://tools.ietf.org/html/rfc2426#page-11">RFC 2426 p.11</a> 103 * @see <a href="http://www.imc.org/pdi/vcard-21.doc">vCard 2.1 p.11</a> 104 */ 105public class Address extends VCardProperty implements HasAltId { 106 private final List<String> poBoxes; 107 private final List<String> extendedAddresses; 108 private final List<String> streetAddresses; 109 private final List<String> localities; 110 private final List<String> regions; 111 private final List<String> postalCodes; 112 private final List<String> countries; 113 114 public Address() { 115 poBoxes = new ArrayList<>(1); 116 extendedAddresses = new ArrayList<>(1); 117 streetAddresses = new ArrayList<>(1); 118 localities = new ArrayList<>(1); 119 regions = new ArrayList<>(1); 120 postalCodes = new ArrayList<>(1); 121 countries = new ArrayList<>(1); 122 } 123 124 /** 125 * Copy constructor. 126 * @param original the property to make a copy of 127 */ 128 public Address(Address original) { 129 super(original); 130 poBoxes = new ArrayList<>(original.poBoxes); 131 extendedAddresses = new ArrayList<>(original.extendedAddresses); 132 streetAddresses = new ArrayList<>(original.streetAddresses); 133 localities = new ArrayList<>(original.localities); 134 regions = new ArrayList<>(original.regions); 135 postalCodes = new ArrayList<>(original.postalCodes); 136 countries = new ArrayList<>(original.countries); 137 } 138 139 /** 140 * Gets the P.O. (post office) box. 141 * @return the P.O. box or null if not set 142 */ 143 public String getPoBox() { 144 return first(poBoxes); 145 } 146 147 /** 148 * Gets the list that holds the P.O. (post office) boxes that are assigned 149 * to this address. An address is unlikely to have more than one, but it's 150 * possible nonetheless. 151 * @return the P.O. boxes (this list is mutable) 152 */ 153 public List<String> getPoBoxes() { 154 return poBoxes; 155 } 156 157 /** 158 * Sets the P.O. (post office) box. 159 * @param poBox the P.O. box or null to remove 160 */ 161 public void setPoBox(String poBox) { 162 set(poBoxes, poBox); 163 } 164 165 /** 166 * Gets the extended address. 167 * @return the extended address (e.g. "Suite 200") or null if not set 168 */ 169 public String getExtendedAddress() { 170 return first(extendedAddresses); 171 } 172 173 /** 174 * Gets the list that holds the extended addresses that are assigned to this 175 * address. An address is unlikely to have more than one, but it's possible 176 * nonetheless. 177 * @return the extended addresses (this list is mutable) 178 */ 179 public List<String> getExtendedAddresses() { 180 return extendedAddresses; 181 } 182 183 /** 184 * Gets the extended address. Use this method when the ADR property of the 185 * vCard you are parsing contains unescaped comma characters. 186 * @return the extended address or null if not set 187 */ 188 public String getExtendedAddressFull() { 189 return getAddressFull(extendedAddresses); 190 } 191 192 /** 193 * Sets the extended address. 194 * @param extendedAddress the extended address (e.g. "Suite 200") or null to 195 * remove 196 */ 197 public void setExtendedAddress(String extendedAddress) { 198 set(extendedAddresses, extendedAddress); 199 } 200 201 /** 202 * Gets the street address 203 * @return the street address (e.g. "123 Main St") 204 */ 205 public String getStreetAddress() { 206 return first(streetAddresses); 207 } 208 209 /** 210 * Gets the list that holds the street addresses that are assigned to this 211 * address. An address is unlikely to have more than one, but it's possible 212 * nonetheless. 213 * @return the street addresses (this list is mutable) 214 */ 215 public List<String> getStreetAddresses() { 216 return streetAddresses; 217 } 218 219 /** 220 * Gets the street address. Use this method when the ADR property of the 221 * vCard you are parsing contains unescaped comma characters. 222 * @return the street address or null if not set 223 */ 224 public String getStreetAddressFull() { 225 return getAddressFull(streetAddresses); 226 } 227 228 /** 229 * Sets the street address. 230 * @param streetAddress the street address (e.g. "123 Main St") or null to 231 * remove 232 */ 233 public void setStreetAddress(String streetAddress) { 234 set(streetAddresses, streetAddress); 235 } 236 237 /** 238 * Gets the locality (city) 239 * @return the locality (e.g. "Boston") or null if not set 240 */ 241 public String getLocality() { 242 return first(localities); 243 } 244 245 /** 246 * Gets the list that holds the localities that are assigned to this 247 * address. An address is unlikely to have more than one, but it's possible 248 * nonetheless. 249 * @return the localities (this list is mutable) 250 */ 251 public List<String> getLocalities() { 252 return localities; 253 } 254 255 /** 256 * Sets the locality (city). 257 * @param locality the locality or null to remove 258 */ 259 public void setLocality(String locality) { 260 set(localities, locality); 261 } 262 263 /** 264 * Gets the region (state). 265 * @return the region (e.g. "Texas") or null if not set 266 */ 267 public String getRegion() { 268 return first(regions); 269 } 270 271 /** 272 * Gets the list that holds the regions that are assigned to this address. 273 * An address is unlikely to have more than one, but it's possible 274 * nonetheless. 275 * @return the regions (this list is mutable) 276 */ 277 public List<String> getRegions() { 278 return regions; 279 } 280 281 /** 282 * Sets the region (state). 283 * @param region the region (e.g. "Texas") or null to remove 284 */ 285 public void setRegion(String region) { 286 set(regions, region); 287 } 288 289 /** 290 * Gets the postal code (zip code). 291 * @return the postal code (e.g. "90210") or null if not set 292 */ 293 public String getPostalCode() { 294 return first(postalCodes); 295 } 296 297 /** 298 * Gets the list that holds the postal codes that are assigned to this 299 * address. An address is unlikely to have more than one, but it's possible 300 * nonetheless. 301 * @return the postal codes (this list is mutable) 302 */ 303 public List<String> getPostalCodes() { 304 return postalCodes; 305 } 306 307 /** 308 * Sets the postal code (zip code). 309 * @param postalCode the postal code (e.g. "90210") or null to remove 310 */ 311 public void setPostalCode(String postalCode) { 312 set(postalCodes, postalCode); 313 } 314 315 /** 316 * Gets the country. 317 * @return the country (e.g. "USA") or null if not set 318 */ 319 public String getCountry() { 320 return first(countries); 321 } 322 323 /** 324 * Gets the list that holds the countries that are assigned to this address. 325 * An address is unlikely to have more than one, but it's possible 326 * nonetheless. 327 * @return the countries (this list is mutable) 328 */ 329 public List<String> getCountries() { 330 return countries; 331 } 332 333 /** 334 * Sets the country. 335 * @param country the country (e.g. "USA") or null to remove 336 */ 337 public void setCountry(String country) { 338 set(countries, country); 339 } 340 341 /** 342 * Gets the list that stores this property's address types (TYPE 343 * parameters). 344 * @return the address types (e.g. "HOME", "WORK") (this list is mutable) 345 */ 346 public List<AddressType> getTypes() { 347 return parameters.new TypeParameterList<AddressType>() { 348 @Override 349 protected AddressType _asObject(String value) { 350 return AddressType.get(value); 351 } 352 }; 353 } 354 355 @Override 356 public String getLanguage() { 357 return super.getLanguage(); 358 } 359 360 @Override 361 public void setLanguage(String language) { 362 super.setLanguage(language); 363 } 364 365 /** 366 * Gets the label of the address. 367 * @return the label or null if not set 368 */ 369 public String getLabel() { 370 return parameters.getLabel(); 371 } 372 373 /** 374 * Sets the label of the address. 375 * @param label the label or null to remove 376 */ 377 public void setLabel(String label) { 378 parameters.setLabel(label); 379 } 380 381 /** 382 * <p> 383 * Gets the global positioning coordinates that are associated with this 384 * address. 385 * </p> 386 * <p> 387 * <b>Supported versions:</b> {@code 4.0} 388 * </p> 389 * @return the geo URI or not if not found 390 * @see VCardParameters#getGeo 391 */ 392 public GeoUri getGeo() { 393 return parameters.getGeo(); 394 } 395 396 /** 397 * <p> 398 * Sets the global positioning coordinates that are associated with this 399 * address. 400 * </p> 401 * <p> 402 * <b>Supported versions:</b> {@code 4.0} 403 * </p> 404 * @param uri the geo URI or null to remove 405 * @see VCardParameters#setGeo 406 */ 407 public void setGeo(GeoUri uri) { 408 parameters.setGeo(uri); 409 } 410 411 @Override 412 public List<Pid> getPids() { 413 return super.getPids(); 414 } 415 416 @Override 417 public Integer getPref() { 418 return super.getPref(); 419 } 420 421 @Override 422 public void setPref(Integer pref) { 423 super.setPref(pref); 424 } 425 426 //@Override 427 public String getAltId() { 428 return parameters.getAltId(); 429 } 430 431 //@Override 432 public void setAltId(String altId) { 433 parameters.setAltId(altId); 434 } 435 436 /** 437 * Gets the timezone that's associated with this address. 438 * <p> 439 * <b>Supported versions:</b> {@code 4.0} 440 * </p> 441 * @return the timezone (e.g. "America/New_York") or null if not set 442 */ 443 public String getTimezone() { 444 return parameters.getTimezone(); 445 } 446 447 /** 448 * Sets the timezone that's associated with this address. 449 * <p> 450 * <b>Supported versions:</b> {@code 4.0} 451 * </p> 452 * @param timezone the timezone (e.g. "America/New_York") or null to remove 453 */ 454 public void setTimezone(String timezone) { 455 parameters.setTimezone(timezone); 456 } 457 458 @Override 459 protected void _validate(List<ValidationWarning> warnings, VCardVersion version, VCard vcard) { 460 for (AddressType type : getTypes()) { 461 if (type == AddressType.PREF) { 462 //ignore because it is converted to a PREF parameter for 4.0 vCards 463 continue; 464 } 465 466 if (!type.isSupportedBy(version)) { 467 warnings.add(new ValidationWarning(9, type.getValue())); 468 } 469 } 470 471 /* 472 * 2.1 does not allow multi-valued components. 473 */ 474 if (version == VCardVersion.V2_1) { 475 //@formatter:off 476 if (poBoxes.size() > 1 || 477 extendedAddresses.size() > 1 || 478 streetAddresses.size() > 1 || 479 localities.size() > 1 || 480 regions.size() > 1 || 481 postalCodes.size() > 1 || 482 countries.size() > 1) { 483 warnings.add(new ValidationWarning(35)); 484 } 485 //@formatter:on 486 } 487 } 488 489 @Override 490 protected Map<String, Object> toStringValues() { 491 Map<String, Object> values = new LinkedHashMap<>(); 492 values.put("poBoxes", poBoxes); 493 values.put("extendedAddresses", extendedAddresses); 494 values.put("streetAddresses", streetAddresses); 495 values.put("localities", localities); 496 values.put("regions", regions); 497 values.put("postalCodes", postalCodes); 498 values.put("countries", countries); 499 return values; 500 } 501 502 @Override 503 public Address copy() { 504 return new Address(this); 505 } 506 507 @Override 508 public int hashCode() { 509 final int prime = 31; 510 int result = super.hashCode(); 511 result = prime * result + countries.hashCode(); 512 result = prime * result + extendedAddresses.hashCode(); 513 result = prime * result + localities.hashCode(); 514 result = prime * result + poBoxes.hashCode(); 515 result = prime * result + postalCodes.hashCode(); 516 result = prime * result + regions.hashCode(); 517 result = prime * result + streetAddresses.hashCode(); 518 return result; 519 } 520 521 @Override 522 public boolean equals(Object obj) { 523 if (this == obj) return true; 524 if (!super.equals(obj)) return false; 525 Address other = (Address) obj; 526 if (!countries.equals(other.countries)) return false; 527 if (!extendedAddresses.equals(other.extendedAddresses)) return false; 528 if (!localities.equals(other.localities)) return false; 529 if (!poBoxes.equals(other.poBoxes)) return false; 530 if (!postalCodes.equals(other.postalCodes)) return false; 531 if (!regions.equals(other.regions)) return false; 532 if (!streetAddresses.equals(other.streetAddresses)) return false; 533 return true; 534 } 535 536 private static String first(List<String> list) { 537 return list.isEmpty() ? null : list.get(0); 538 } 539 540 private static void set(List<String> list, String value) { 541 list.clear(); 542 if (value != null) { 543 list.add(value); 544 } 545 } 546 547 private static String getAddressFull(List<String> list) { 548 return list.isEmpty() ? null : StringUtils.join(list, ","); 549 } 550}