001package ezvcard.property; 002 003import java.lang.reflect.Constructor; 004import java.util.ArrayList; 005import java.util.Arrays; 006import java.util.Collections; 007import java.util.List; 008import java.util.Map; 009 010import com.github.mangstadt.vinnie.SyntaxStyle; 011import com.github.mangstadt.vinnie.validate.AllowedCharacters; 012import com.github.mangstadt.vinnie.validate.VObjectValidator; 013 014import ezvcard.Messages; 015import ezvcard.SupportedVersions; 016import ezvcard.VCard; 017import ezvcard.VCardVersion; 018import ezvcard.ValidationWarning; 019import ezvcard.parameter.Pid; 020import ezvcard.parameter.VCardParameters; 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 * Base class for all vCard property classes. 053 * @author Michael Angstadt 054 */ 055public abstract class VCardProperty implements Comparable<VCardProperty> { 056 /** 057 * The group that this property belongs to or null if it doesn't belong to a 058 * group. 059 */ 060 protected String group; 061 062 /** 063 * The property's parameters. 064 */ 065 protected VCardParameters parameters; 066 067 public VCardProperty() { 068 parameters = new VCardParameters(); 069 } 070 071 /** 072 * Copy constructor. 073 * @param original the property to make a copy of 074 */ 075 protected VCardProperty(VCardProperty original) { 076 group = original.group; 077 parameters = new VCardParameters(original.parameters); 078 } 079 080 /** 081 * <p> 082 * Gets the vCard versions that support this property. 083 * </p> 084 * <p> 085 * The supported versions are defined by assigning a 086 * {@link SupportedVersions @SupportedVersions} annotation to the property 087 * class. Property classes without this annotation are considered to be 088 * supported by all versions. 089 * </p> 090 * @return the vCard versions that support this property. 091 */ 092 public final VCardVersion[] getSupportedVersions() { 093 SupportedVersions supportedVersionsAnnotation = getClass().getAnnotation(SupportedVersions.class); 094 return (supportedVersionsAnnotation == null) ? VCardVersion.values() : supportedVersionsAnnotation.value(); 095 } 096 097 /** 098 * <p> 099 * Determines if this property is supported by the given vCard version. 100 * </p> 101 * <p> 102 * The supported versions are defined by assigning a 103 * {@link SupportedVersions} annotation to the property class. Property 104 * classes without this annotation are considered to be supported by all 105 * versions. 106 * </p> 107 * @param version the vCard version 108 * @return true if it is supported, false if not 109 */ 110 public final boolean isSupportedBy(VCardVersion version) { 111 VCardVersion supportedVersions[] = getSupportedVersions(); 112 for (VCardVersion supportedVersion : supportedVersions) { 113 if (supportedVersion == version) { 114 return true; 115 } 116 } 117 return false; 118 } 119 120 /** 121 * Checks the property for data consistency problems or deviations from the 122 * spec. These problems will not prevent the property from being written to 123 * a data stream, but may prevent it from being parsed correctly by the 124 * consuming application. These problems can largely be avoided by reading 125 * the Javadocs of the property class, or by being familiar with the vCard 126 * standard. 127 * @param version the version to check the property against (use 4.0 for 128 * xCard and jCard) 129 * @param vcard the vCard this property belongs to 130 * @see VCard#validate 131 * @return a list of warnings or an empty list if no problems were found 132 */ 133 public final List<ValidationWarning> validate(VCardVersion version, VCard vcard) { 134 List<ValidationWarning> warnings = new ArrayList<>(0); 135 136 //check the supported versions 137 if (!isSupportedBy(version)) { 138 warnings.add(new ValidationWarning(2, Arrays.toString(getSupportedVersions()))); 139 } 140 141 //check parameters 142 warnings.addAll(parameters.validate(version)); 143 144 //check group 145 if (group != null) { 146 SyntaxStyle syntax = version.getSyntaxStyle(); 147 AllowedCharacters allowed = VObjectValidator.allowedCharactersGroup(syntax, true); 148 if (!allowed.check(group)) { 149 if (syntax == SyntaxStyle.OLD) { 150 AllowedCharacters notAllowed = allowed.flip(); 151 warnings.add(new ValidationWarning(32, group, notAllowed.toString(true))); 152 } else { 153 warnings.add(new ValidationWarning(23, group)); 154 } 155 } 156 } 157 158 _validate(warnings, version, vcard); 159 160 return warnings; 161 } 162 163 /** 164 * Checks the property for data consistency problems or deviations from the 165 * spec. Meant to be overridden by child classes that wish to provide 166 * validation logic. 167 * @param warnings the list to add the warnings to 168 * @param version the version to check the property against 169 * @param vcard the vCard this property belongs to 170 */ 171 protected void _validate(List<ValidationWarning> warnings, VCardVersion version, VCard vcard) { 172 //empty 173 } 174 175 /** 176 * Gets all of the property's parameters. 177 * @return the property's parameters 178 */ 179 public VCardParameters getParameters() { 180 return parameters; 181 } 182 183 /** 184 * Sets the property's parameters. 185 * @param parameters the parameters (cannot be null) 186 */ 187 public void setParameters(VCardParameters parameters) { 188 if (parameters == null) { 189 throw new NullPointerException(Messages.INSTANCE.getExceptionMessage(42)); 190 } 191 this.parameters = parameters; 192 } 193 194 /** 195 * Gets the first value of a parameter. 196 * @param name the parameter name (case insensitive, e.g. "LANGUAGE") 197 * @return the parameter value or null if not found 198 */ 199 public String getParameter(String name) { 200 return parameters.first(name); 201 } 202 203 /** 204 * Gets all values of a parameter. 205 * @param name the parameter name (case insensitive, e.g. "LANGUAGE") 206 * @return the parameter values (this list is immutable) 207 */ 208 public List<String> getParameters(String name) { 209 return Collections.unmodifiableList(parameters.get(name)); 210 } 211 212 /** 213 * Replaces all existing values of a parameter with the given value. 214 * @param name the parameter name (case insensitive, e.g. "LANGUAGE") 215 * @param value the parameter value 216 */ 217 public void setParameter(String name, String value) { 218 parameters.replace(name, value); 219 } 220 221 /** 222 * Adds a value to a parameter. 223 * @param name the parameter name (case insensitive, e.g. "LANGUAGE") 224 * @param value the parameter value 225 */ 226 public void addParameter(String name, String value) { 227 parameters.put(name, value); 228 } 229 230 /** 231 * Removes a parameter from the property. 232 * @param name the parameter name (case insensitive, e.g. "LANGUAGE") 233 */ 234 public void removeParameter(String name) { 235 parameters.removeAll(name); 236 } 237 238 /** 239 * Gets this property's group. 240 * @return the group or null if it does not belong to a group 241 */ 242 public String getGroup() { 243 return group; 244 } 245 246 /** 247 * Sets this property's group. 248 * @param group the group or null to remove the property's group 249 */ 250 public void setGroup(String group) { 251 this.group = group; 252 } 253 254 /** 255 * Sorts by PREF parameter ascending. Properties that do not have a PREF 256 * parameter are pushed to the end of the list. 257 */ 258 public int compareTo(VCardProperty that) { 259 Integer pref0 = this.getParameters().getPref(); 260 Integer pref1 = that.getParameters().getPref(); 261 if (pref0 == null && pref1 == null) { 262 return 0; 263 } 264 if (pref0 == null) { 265 return 1; 266 } 267 if (pref1 == null) { 268 return -1; 269 } 270 return pref1.compareTo(pref0); 271 } 272 273 /** 274 * <p> 275 * Gets string representations of the class's fields for the 276 * {@link #toString} method. 277 * </p> 278 * <p> 279 * Meant to be overridden by child classes. The default implementation 280 * returns an empty map. 281 * </p> 282 * @return the values of the class's fields (key = field name, value = field 283 * value) 284 */ 285 protected Map<String, Object> toStringValues() { 286 return Collections.emptyMap(); 287 } 288 289 @Override 290 public String toString() { 291 StringBuilder sb = new StringBuilder(); 292 sb.append(getClass().getName()); 293 sb.append(" [ group=").append(group); 294 sb.append(" | parameters=").append(parameters); 295 for (Map.Entry<String, Object> field : toStringValues().entrySet()) { 296 String fieldName = field.getKey(); 297 Object fieldValue = field.getValue(); 298 sb.append(" | ").append(fieldName).append('=').append(fieldValue); 299 } 300 sb.append(" ]"); 301 return sb.toString(); 302 } 303 304 /** 305 * <p> 306 * Creates a copy of this property object. 307 * </p> 308 * <p> 309 * The default implementation of this method uses reflection to look for a 310 * copy constructor. Child classes SHOULD override this method to avoid the 311 * performance overhead involved in using reflection. 312 * </p> 313 * <p> 314 * The child class's copy constructor, if present, MUST invoke the 315 * {@link #VCardProperty(VCardProperty)} super constructor to ensure that 316 * the group name and parameters are also copied. 317 * </p> 318 * <p> 319 * This method MUST be overridden by the child class if the child class does 320 * not have a copy constructor. Otherwise, an 321 * {@link UnsupportedOperationException} will be thrown when an attempt is 322 * made to copy the property (such as in the {@link VCard#VCard(VCard) VCard 323 * class's copy constructor}). 324 * </p> 325 * @return the copy 326 * @throws UnsupportedOperationException if the class does not have a copy 327 * constructor or there is a problem invoking it 328 */ 329 public VCardProperty copy() { 330 Class<? extends VCardProperty> clazz = getClass(); 331 332 try { 333 Constructor<? extends VCardProperty> copyConstructor = clazz.getConstructor(clazz); 334 return copyConstructor.newInstance(this); 335 } catch (Exception e) { 336 throw new UnsupportedOperationException(Messages.INSTANCE.getExceptionMessage(31, clazz.getName()), e); 337 } 338 } 339 340 @Override 341 public int hashCode() { 342 final int prime = 31; 343 int result = 1; 344 result = prime * result + ((group == null) ? 0 : group.toLowerCase().hashCode()); 345 result = prime * result + parameters.hashCode(); 346 return result; 347 } 348 349 @Override 350 public boolean equals(Object obj) { 351 if (this == obj) return true; 352 if (obj == null) return false; 353 if (getClass() != obj.getClass()) return false; 354 VCardProperty other = (VCardProperty) obj; 355 if (group == null) { 356 if (other.group != null) return false; 357 } else if (!group.equalsIgnoreCase(other.group)) return false; 358 if (!parameters.equals(other.parameters)) return false; 359 return true; 360 } 361 362 /* 363 * Note: The following parameter helper methods are package-scoped so they 364 * don't clutter up the Javadocs for the VCardProperty class. They are 365 * defined here instead of in the child classes that use them, so that their 366 * Javadocs don't have to be repeated. 367 */ 368 369 /** 370 * <p> 371 * Gets the list that stores this property's PID (property ID) parameter 372 * values. 373 * </p> 374 * <p> 375 * PIDs can exist on any property where multiple instances are allowed (such 376 * as {@link Email} or {@link Address}, but not {@link StructuredName} 377 * because only 1 instance of this property is allowed per vCard). 378 * </p> 379 * <p> 380 * When used in conjunction with the {@link ClientPidMap} property, it 381 * allows an individual property instance to be uniquely identifiable. This 382 * feature is made use of when two different versions of the same vCard have 383 * to be merged together (called "synchronizing"). 384 * </p> 385 * <p> 386 * <b>Supported versions:</b> {@code 4.0} 387 * </p> 388 * @return the PID parameter values (this list is mutable) 389 * @throws IllegalStateException if one or more parameter values cannot be 390 * parsed as PIDs. If this happens, you may use the 391 * {@link #getParameters(String)} method to retrieve the raw values. 392 * @see <a href="http://tools.ietf.org/html/rfc6350#page-19">RFC 6350 393 * p.19</a> 394 */ 395 List<Pid> getPids() { 396 return parameters.getPids(); 397 } 398 399 /** 400 * <p> 401 * Gets this property's preference value. The lower this number is, the more 402 * "preferred" the property instance is compared with other properties of 403 * the same type. If a property doesn't have a preference value, then it is 404 * considered the least preferred. 405 * </p> 406 * <p> 407 * In the vCard below, the {@link Address} on the second row is the most 408 * preferred because it has the lowest PREF value. 409 * </p> 410 * 411 * <pre> 412 * ADR;TYPE=work;PREF=2:;;1600 Amphitheatre Parkway;Mountain View;CA;94043 413 * ADR;TYPE=work;PREF=1:;;One Microsoft Way;Redmond;WA;98052 414 * ADR;TYPE=home:;;123 Maple St;Hometown;KS;12345 415 * </pre> 416 * 417 * <p> 418 * <b>Supported versions:</b> {@code 4.0} 419 * </p> 420 * @return the preference value or null if not set 421 * @throws IllegalStateException if the parameter value cannot be parsed as 422 * an integer. If this happens, you may use the 423 * {@link #getParameter(String)} method to retrieve its raw value. 424 * @see <a href="http://tools.ietf.org/html/rfc6350#page-17">RFC 6350 425 * p.17</a> 426 */ 427 Integer getPref() { 428 return parameters.getPref(); 429 } 430 431 /** 432 * <p> 433 * Sets this property's preference value. The lower this number is, the more 434 * "preferred" the property instance is compared with other properties of 435 * the same type. If a property doesn't have a preference value, then it is 436 * considered the least preferred. 437 * </p> 438 * <p> 439 * In the vCard below, the {@link Address} on the second row is the most 440 * preferred because it has the lowest PREF value. 441 * </p> 442 * 443 * <pre> 444 * ADR;TYPE=work;PREF=2:;;1600 Amphitheatre Parkway;Mountain View;CA;94043 445 * ADR;TYPE=work;PREF=1:;;One Microsoft Way;Redmond;WA;98052 446 * ADR;TYPE=home:;;123 Maple St;Hometown;KS;12345 447 * </pre> 448 * 449 * <p> 450 * <b>Supported versions:</b> {@code 4.0} 451 * </p> 452 * @param pref the preference value or null to remove 453 * @see <a href="http://tools.ietf.org/html/rfc6350#page-17">RFC 6350 454 * p.17</a> 455 */ 456 void setPref(Integer pref) { 457 parameters.setPref(pref); 458 } 459 460 /** 461 * Gets the language that the property value is written in. 462 * @return the language or null if not set 463 */ 464 String getLanguage() { 465 return parameters.getLanguage(); 466 } 467 468 /** 469 * Sets the language that the property value is written in. 470 * @param language the language or null to remove 471 */ 472 void setLanguage(String language) { 473 parameters.setLanguage(language); 474 } 475 476 /** 477 * <p> 478 * Gets the sorted position of this property when it is grouped together 479 * with other properties of the same type. Properties with low index values 480 * are put at the beginning of the sorted list. Properties with high index 481 * values are put at the end of the list. 482 * </p> 483 * <p> 484 * <b>Supported versions:</b> {@code 4.0} 485 * </p> 486 * @return the index or null if not set 487 * @throws IllegalStateException if the parameter value cannot be parsed as 488 * an integer. If this happens, you may use the 489 * {@link #getParameter(String)} method to retrieve its raw value. 490 * @see <a href="https://tools.ietf.org/html/rfc6715#page-7">RFC 6715 491 * p.7</a> 492 */ 493 Integer getIndex() { 494 return parameters.getIndex(); 495 } 496 497 /** 498 * <p> 499 * Sets the sorted position of this property when it is grouped together 500 * with other properties of the same type. Properties with low index values 501 * are put at the beginning of the sorted list. Properties with high index 502 * values are put at the end of the list. 503 * </p> 504 * <p> 505 * <b>Supported versions:</b> {@code 4.0} 506 * </p> 507 * @param index the index or null to remove 508 * @throws IllegalStateException if the parameter value is malformed and 509 * cannot be parsed. If this happens, you may use the 510 * {@link #getParameter(String)} method to retrieve its raw value. 511 * @see <a href="https://tools.ietf.org/html/rfc6715#page-7">RFC 6715 512 * p.7</a> 513 */ 514 void setIndex(Integer index) { 515 parameters.setIndex(index); 516 } 517}