001package ezvcard.io.scribe; 002 003import java.util.Arrays; 004import java.util.Date; 005import java.util.List; 006 007import javax.xml.namespace.QName; 008 009import org.w3c.dom.Element; 010 011import com.github.mangstadt.vinnie.io.VObjectPropertyValues; 012 013import ezvcard.VCard; 014import ezvcard.VCardDataType; 015import ezvcard.VCardVersion; 016import ezvcard.io.CannotParseException; 017import ezvcard.io.EmbeddedVCardException; 018import ezvcard.io.ParseContext; 019import ezvcard.io.SkipMeException; 020import ezvcard.io.html.HCardElement; 021import ezvcard.io.json.JCardValue; 022import ezvcard.io.json.JsonValue; 023import ezvcard.io.text.WriteContext; 024import ezvcard.io.xml.XCardElement; 025import ezvcard.io.xml.XCardElement.XCardValue; 026import ezvcard.parameter.VCardParameters; 027import ezvcard.property.VCardProperty; 028import ezvcard.util.VCardDateFormat; 029 030/* 031 Copyright (c) 2012-2018, Michael Angstadt 032 All rights reserved. 033 034 Redistribution and use in source and binary forms, with or without 035 modification, are permitted provided that the following conditions are met: 036 037 1. Redistributions of source code must retain the above copyright notice, this 038 list of conditions and the following disclaimer. 039 2. Redistributions in binary form must reproduce the above copyright notice, 040 this list of conditions and the following disclaimer in the documentation 041 and/or other materials provided with the distribution. 042 043 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 044 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 045 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 046 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 047 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 048 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 049 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 050 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 051 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 052 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 053 */ 054 055/** 056 * Base class for vCard property scribes (aka "marshallers" or "serializers"). 057 * @param <T> the property class 058 * @author Michael Angstadt 059 */ 060public abstract class VCardPropertyScribe<T extends VCardProperty> { 061 protected final Class<T> clazz; 062 protected final String propertyName; 063 protected final QName qname; 064 065 /** 066 * Creates a new scribe. 067 * @param clazz the property class 068 * @param propertyName the property name (e.g. "FN") 069 */ 070 public VCardPropertyScribe(Class<T> clazz, String propertyName) { 071 this(clazz, propertyName, new QName(VCardVersion.V4_0.getXmlNamespace(), propertyName.toLowerCase())); 072 } 073 074 /** 075 * Creates a new scribe. 076 * @param clazz the property class 077 * @param propertyName the property name (e.g. "FN") 078 * @param qname the XML element name and namespace to use for xCard 079 * documents (by default, the XML element name is set to the lower-cased 080 * property name, and the element namespace is set to the xCard namespace) 081 */ 082 public VCardPropertyScribe(Class<T> clazz, String propertyName, QName qname) { 083 this.clazz = clazz; 084 this.propertyName = propertyName; 085 this.qname = qname; 086 } 087 088 /** 089 * Gets the property class. 090 * @return the property class 091 */ 092 public Class<T> getPropertyClass() { 093 return clazz; 094 } 095 096 /** 097 * Gets the property name. 098 * @return the property name (e.g. "FN") 099 */ 100 public String getPropertyName() { 101 return propertyName; 102 } 103 104 /** 105 * Gets this property's local name and namespace for xCard documents. 106 * @return the XML local name and namespace 107 */ 108 public QName getQName() { 109 return qname; 110 } 111 112 /** 113 * Sanitizes a property's parameters (called before the property is 114 * written). Note that a copy of the parameters is returned so that the 115 * property object does not get modified. 116 * @param property the property 117 * @param version the version of the vCard that is being generated 118 * @param vcard the vCard that the property belongs to 119 * @return the sanitized parameters 120 */ 121 public final VCardParameters prepareParameters(T property, VCardVersion version, VCard vcard) { 122 //make a copy because the property should not get modified when it is marshalled 123 VCardParameters copy = new VCardParameters(property.getParameters()); 124 _prepareParameters(property, copy, version, vcard); 125 return copy; 126 } 127 128 /** 129 * <p> 130 * Determines the property's default data type. 131 * </p> 132 * <p> 133 * When writing a plain-text vCard, if the data type of a property instance 134 * (as determined by the {@link #dataType} method) matches the default data 135 * type, then a VALUE parameter will *not* be written. 136 * </p> 137 * <p> 138 * When parsing a plain-text vCard, if a property has no VALUE parameter, 139 * then the property's default data type will be passed into the 140 * {@link #parseText} method. 141 * </p> 142 * @param version the vCard version 143 * @return the default data type or null if unknown 144 */ 145 public final VCardDataType defaultDataType(VCardVersion version) { 146 return _defaultDataType(version); 147 } 148 149 /** 150 * Determines the data type of a property instance. 151 * @param property the property 152 * @param version the version of the vCard that is being generated 153 * @return the data type or null if unknown 154 */ 155 public final VCardDataType dataType(T property, VCardVersion version) { 156 return _dataType(property, version); 157 } 158 159 /** 160 * Marshals a property's value to a string. 161 * @param property the property 162 * @param context contains information about the vCard being written, such 163 * as the target version 164 * @return the string value 165 * @throws SkipMeException if the property should not be written to the data 166 * stream 167 */ 168 public final String writeText(T property, WriteContext context) { 169 return _writeText(property, context); 170 } 171 172 /** 173 * Marshals a property's value to an XML element (xCard). 174 * @param property the property 175 * @param element the property's XML element. 176 * @throws SkipMeException if the property should not be written to the data 177 * stream 178 */ 179 public final void writeXml(T property, Element element) { 180 XCardElement xCardElement = new XCardElement(element); 181 _writeXml(property, xCardElement); 182 } 183 184 /** 185 * Marshals a property's value to a JSON data stream (jCard). 186 * @param property the property 187 * @return the marshalled value 188 * @throws SkipMeException if the property should not be written to the data 189 * stream 190 */ 191 public final JCardValue writeJson(T property) { 192 return _writeJson(property); 193 } 194 195 /** 196 * Unmarshals a property from a plain-text vCard. 197 * @param value the value as read off the wire 198 * @param dataType the data type of the property value. The property's VALUE 199 * parameter is used to determine the data type. If the property has no 200 * VALUE parameter, then this parameter will be set to the property's 201 * default datatype, as determined by the {@link #defaultDataType} method. 202 * Note that the VALUE parameter is removed from the property's parameter 203 * list after it has been read. 204 * @param parameters the parsed parameters 205 * @param context the parse context 206 * @return the unmarshalled property 207 * @throws CannotParseException if the marshaller could not parse the 208 * property's value 209 * @throws SkipMeException if the property should not be added to the final 210 * {@link VCard} object 211 * @throws EmbeddedVCardException if the property value is an embedded vCard 212 * (i.e. the AGENT property) 213 */ 214 public final T parseText(String value, VCardDataType dataType, VCardParameters parameters, ParseContext context) { 215 T property = _parseText(value, dataType, parameters, context); 216 property.setParameters(parameters); 217 return property; 218 } 219 220 /** 221 * Unmarshals a property's value from an XML document (xCard). 222 * @param element the property's XML element 223 * @param parameters the parsed parameters 224 * @param context the parse context 225 * @return the unmarshalled property 226 * @throws CannotParseException if the marshaller could not parse the 227 * property's value 228 * @throws SkipMeException if the property should not be added to the final 229 * {@link VCard} object 230 */ 231 public final T parseXml(Element element, VCardParameters parameters, ParseContext context) { 232 T property = _parseXml(new XCardElement(element), parameters, context); 233 property.setParameters(parameters); 234 return property; 235 } 236 237 /** 238 * Unmarshals the property from an HTML document (hCard). 239 * @param element the property's HTML element 240 * @param context the parse context 241 * @return the unmarshalled property 242 * @throws CannotParseException if the property value could not be parsed 243 * @throws SkipMeException if this type should NOT be added to the 244 * {@link VCard} object 245 * @throws EmbeddedVCardException if the property value is an embedded vCard 246 * (i.e. the AGENT property) 247 */ 248 public final T parseHtml(HCardElement element, ParseContext context) { 249 return _parseHtml(element, context); 250 } 251 252 /** 253 * Unmarshals a property's value from a JSON data stream (jCard). 254 * @param value the property's JSON value 255 * @param dataType the data type 256 * @param parameters the parsed parameters 257 * @param context the parse context 258 * @return the unmarshalled property 259 * @throws CannotParseException if the marshaller could not parse the 260 * property's value 261 * @throws SkipMeException if the property should not be added to the final 262 * {@link VCard} object 263 */ 264 public final T parseJson(JCardValue value, VCardDataType dataType, VCardParameters parameters, ParseContext context) { 265 T property = _parseJson(value, dataType, parameters, context); 266 property.setParameters(parameters); 267 return property; 268 } 269 270 /** 271 * <p> 272 * Sanitizes a property's parameters before the property is written. 273 * </p> 274 * <p> 275 * This method should be overridden by child classes that wish to tweak the 276 * property's parameters before the property is written. The default 277 * implementation of this method does nothing. 278 * </p> 279 * @param property the property 280 * @param copy the list of parameters to make modifications to (it is a copy 281 * of the property's parameters) 282 * @param version the version of the vCard that is being generated 283 * @param vcard the vCard that the property belongs to 284 */ 285 protected void _prepareParameters(T property, VCardParameters copy, VCardVersion version, VCard vcard) { 286 //do nothing 287 } 288 289 /** 290 * <p> 291 * Determines the property's default data type. 292 * </p> 293 * <p> 294 * When writing a plain-text vCard, if the data type of a property instance 295 * (as determined by the {@link #dataType} method) matches the default data 296 * type, then a VALUE parameter will *not* be written. 297 * </p> 298 * <p> 299 * When parsing a plain-text vCard, if a property has no VALUE parameter, 300 * then the property's default data type will be passed into the 301 * {@link #parseText} method. 302 * </p> 303 * @param version the vCard version 304 * @return the default data type or null if unknown 305 */ 306 protected abstract VCardDataType _defaultDataType(VCardVersion version); 307 308 /** 309 * <p> 310 * Determines the data type of a property instance. 311 * </p> 312 * <p> 313 * This method should be overridden by child classes if a property's data 314 * type changes depending on its value. The default implementation of this 315 * method calls {@link #_defaultDataType}. 316 * </p> 317 * @param property the property 318 * @param version the version of the vCard that is being generated 319 * @return the data type or null if unknown 320 */ 321 protected VCardDataType _dataType(T property, VCardVersion version) { 322 return _defaultDataType(version); 323 } 324 325 /** 326 * Marshals a property's value to a string. 327 * @param property the property 328 * @param context contains information about the vCard being written, such 329 * as the target version 330 * @return the marshalled value 331 * @throws SkipMeException if the property should not be written to the data 332 * stream 333 */ 334 protected abstract String _writeText(T property, WriteContext context); 335 336 /** 337 * <p> 338 * Marshals a property's value to an XML element (xCard). 339 * </p> 340 * <p> 341 * This method should be overridden by child classes that wish to support 342 * xCard. The default implementation of this method will append one child 343 * element to the property's XML element. The child element's name will be 344 * that of the property's data type (retrieved using the {@link #dataType} 345 * method), and the child element's text content will be set to the 346 * property's marshalled plain-text value (retrieved using the 347 * {@link #writeText} method). 348 * </p> 349 * @param property the property 350 * @param element the property's XML element 351 * @throws SkipMeException if the property should not be written to the data 352 * stream 353 */ 354 protected void _writeXml(T property, XCardElement element) { 355 String value = writeText(property, new WriteContext(VCardVersion.V4_0, null, false)); 356 VCardDataType dataType = dataType(property, VCardVersion.V4_0); 357 element.append(dataType, value); 358 } 359 360 /** 361 * <p> 362 * Marshals a property's value to a JSON data stream (jCard). 363 * </p> 364 * <p> 365 * This method should be overridden by child classes that wish to support 366 * jCard. The default implementation of this method will create a jCard 367 * property that has a single JSON string value (generated by the 368 * {@link #writeText} method). 369 * </p> 370 * @param property the property 371 * @return the marshalled value 372 * @throws SkipMeException if the property should not be written to the data 373 * stream 374 */ 375 protected JCardValue _writeJson(T property) { 376 String value = writeText(property, new WriteContext(VCardVersion.V4_0, null, false)); 377 return JCardValue.single(value); 378 } 379 380 /** 381 * Unmarshals a property from a plain-text vCard. 382 * @param value the value as read off the wire 383 * @param dataType the data type of the property value. The property's VALUE 384 * parameter is used to determine the data type. If the property has no 385 * VALUE parameter, then this parameter will be set to the property's 386 * default datatype, as determined by the {@link #defaultDataType} method. 387 * Note that the VALUE parameter is removed from the property's parameter 388 * list after it has been read. 389 * @param parameters the parsed parameters. These parameters will be 390 * assigned to the property object once this method returns. Therefore, do 391 * not assign any parameters to the property object itself whilst inside of 392 * this method, or else they will be overwritten. 393 * @param context the parse context 394 * @return the unmarshalled property object 395 * @throws CannotParseException if the marshaller could not parse the 396 * property's value 397 * @throws SkipMeException if the property should not be added to the final 398 * {@link VCard} object 399 */ 400 protected abstract T _parseText(String value, VCardDataType dataType, VCardParameters parameters, ParseContext context); 401 402 /** 403 * <p> 404 * Unmarshals a property from an XML document (xCard). 405 * </p> 406 * <p> 407 * This method should be overridden by child classes that wish to support 408 * xCard. The default implementation of this method will find the first 409 * child element with the xCard namespace. The element's name will be used 410 * as the property's data type and its text content (escaped for inclusion 411 * in a text-based vCard, e.g. escaping comma characters) will be passed 412 * into the {@link #_parseText} method. If no such child element is found, 413 * then the parent element's text content will be passed into 414 * {@link #_parseText} and the data type will be {@code null}. 415 * </p> 416 * @param element the property's XML element 417 * @param parameters the parsed parameters. These parameters will be 418 * assigned to the property object once this method returns. Therefore, do 419 * not assign any parameters to the property object itself whilst inside of 420 * this method, or else they will be overwritten. 421 * @param context the parse context 422 * @return the unmarshalled property object 423 * @throws CannotParseException if the marshaller could not parse the 424 * property's value 425 * @throws SkipMeException if the property should not be added to the final 426 * {@link VCard} object 427 */ 428 protected T _parseXml(XCardElement element, VCardParameters parameters, ParseContext context) { 429 XCardValue firstValue = element.firstValue(); 430 VCardDataType dataType = firstValue.getDataType(); 431 String value = VObjectPropertyValues.escape(firstValue.getValue()); 432 return _parseText(value, dataType, parameters, context); 433 } 434 435 /** 436 * <p> 437 * Unmarshals the property from an hCard (HTML document). 438 * </p> 439 * <p> 440 * This method should be overridden by child classes that wish to support 441 * hCard. The default implementation of this method will retrieve the HTML 442 * element's hCard value (as described in {@link HCardElement#value}), and 443 * pass it into the {@link #_parseText} method. 444 * </p> 445 * @param element the property's HTML element 446 * @param context the parse context 447 * @return the unmarshalled property object 448 * @throws CannotParseException if the property value could not be parsed 449 * @throws SkipMeException if this property should NOT be added to the 450 * {@link VCard} object 451 * @throws EmbeddedVCardException if the value of this property is an 452 * embedded vCard (i.e. the AGENT property) 453 */ 454 protected T _parseHtml(HCardElement element, ParseContext context) { 455 String value = VObjectPropertyValues.escape(element.value()); 456 VCardParameters parameters = new VCardParameters(); 457 T property = _parseText(value, null, parameters, context); 458 property.setParameters(parameters); 459 return property; 460 } 461 462 /** 463 * <p> 464 * Unmarshals a property from a JSON data stream (jCard). 465 * </p> 466 * <p> 467 * This method should be overridden by child classes that wish to support 468 * jCard. The default implementation of this method will convert the jCard 469 * property value to a string and pass it into the {@link #_parseText} 470 * method. 471 * </p> 472 * 473 * <hr> 474 * 475 * <p> 476 * The following paragraphs describe the way in which this method's default 477 * implementation converts a jCard value to a string: 478 * </p> 479 * <p> 480 * If the jCard value consists of a single, non-array, non-object value, 481 * then the value is converted to a string. Special characters (backslashes, 482 * commas, and semicolons) are escaped in order to simulate what the value 483 * might look like in a plain-text vCard.<br> 484 * <code>["x-foo", {}, "text", "the;value"] --> "the\;value"</code><br> 485 * <code>["x-foo", {}, "text", 2] --> "2"</code> 486 * </p> 487 * <p> 488 * If the jCard value consists of multiple, non-array, non-object values, 489 * then all the values are appended together in a single string, separated 490 * by commas. Special characters (backslashes, commas, and semicolons) are 491 * escaped for each value in order to prevent commas from being treated as 492 * delimiters, and to simulate what the value might look like in a 493 * plain-text vCard.<br> 494 * <code>["x-foo", {}, "text", "one", "two,three"] --> 495 * "one,two\,three"</code> 496 * </p> 497 * <p> 498 * If the jCard value is a single array, then this array is treated as a 499 * "structured value", and converted its plain-text representation. Special 500 * characters (backslashes, commas, and semicolons) are escaped for each 501 * value in order to prevent commas and semicolons from being treated as 502 * delimiters.<br> 503 * <code>["x-foo", {}, "text", ["one", ["two", "three"], "four;five"]] 504 * --> "one;two,three;four\;five"</code> 505 * </p> 506 * <p> 507 * If the jCard value starts with a JSON object, then it is converted to an 508 * empty string (JSON objects are not supported by this method).<br> 509 * <code>["x-foo", , "text", {"one": 1}] --> ""}</code> 510 * </p> 511 * <p> 512 * For all other cases, behavior is undefined. 513 * </p> 514 * @param value the property's JSON value 515 * @param dataType the data type 516 * @param parameters the parsed parameters. These parameters will be 517 * assigned to the property object once this method returns. Therefore, do 518 * not assign any parameters to the property object itself whilst inside of 519 * this method, or else they will be overwritten. 520 * @param context the parse context 521 * @return the unmarshalled property object 522 * @throws CannotParseException if the marshaller could not parse the 523 * property's value 524 * @throws SkipMeException if the property should not be added to the final 525 * {@link VCard} object 526 */ 527 protected T _parseJson(JCardValue value, VCardDataType dataType, VCardParameters parameters, ParseContext context) { 528 String valueStr = jcardValueToString(value); 529 return _parseText(valueStr, dataType, parameters, context); 530 } 531 532 /** 533 * Converts a jCard value to its plain-text format representation. 534 * @param value the jCard value 535 * @return the plain-text format representation (for example, "1,2,3" for a 536 * list of values) 537 */ 538 private static String jcardValueToString(JCardValue value) { 539 List<JsonValue> values = value.getValues(); 540 if (values.size() > 1) { 541 List<String> multi = value.asMulti(); 542 if (!multi.isEmpty()) { 543 return VObjectPropertyValues.writeList(multi); 544 } 545 } 546 547 if (!values.isEmpty() && values.get(0).getArray() != null) { 548 List<List<String>> structured = value.asStructured(); 549 if (!structured.isEmpty()) { 550 return VObjectPropertyValues.writeStructured(structured, true); 551 } 552 } 553 554 return VObjectPropertyValues.escape(value.asSingle()); 555 } 556 557 /** 558 * Parses a date string. 559 * @param value the date string 560 * @return the parsed date 561 * @throws IllegalArgumentException if the date cannot be parsed 562 */ 563 protected static Date date(String value) { 564 return VCardDateFormat.parse(value); 565 } 566 567 /** 568 * Formats a {@link Date} object as a string. 569 * @param date the date 570 * @return a helper object for customizing the write operation 571 */ 572 protected static DateWriter date(Date date) { 573 return new DateWriter(date); 574 } 575 576 /** 577 * A helper class for writing date values. 578 */ 579 protected static class DateWriter { 580 private Date date; 581 private boolean hasTime = true; 582 private boolean extended = false; 583 private boolean utc = true; 584 585 /** 586 * Creates a new date writer object. 587 * @param date the date to format 588 */ 589 public DateWriter(Date date) { 590 this.date = date; 591 } 592 593 /** 594 * Sets whether to output the date's time component. 595 * @param hasTime true include the time, false if it's strictly a date 596 * (defaults to "true") 597 * @return this 598 */ 599 public DateWriter time(boolean hasTime) { 600 this.hasTime = hasTime; 601 return this; 602 } 603 604 /** 605 * Sets whether to use extended format or basic. 606 * @param extended true to use extended format, false to use basic 607 * (defaults to "false") 608 * @return this 609 */ 610 public DateWriter extended(boolean extended) { 611 this.extended = extended; 612 return this; 613 } 614 615 /** 616 * Sets whether to format the date in UTC time, or to include a UTC 617 * offset. 618 * @param utc true to format in UTC time, false to include the local 619 * timezone's UTC offset (defaults to "true") 620 * @return this 621 */ 622 public DateWriter utc(boolean utc) { 623 this.utc = utc; 624 return this; 625 } 626 627 /** 628 * Creates the date string. 629 * @return the date string 630 */ 631 public String write() { 632 VCardDateFormat format; 633 if (hasTime) { 634 if (utc) { 635 format = extended ? VCardDateFormat.UTC_DATE_TIME_EXTENDED : VCardDateFormat.UTC_DATE_TIME_BASIC; 636 } else { 637 format = extended ? VCardDateFormat.DATE_TIME_EXTENDED : VCardDateFormat.DATE_TIME_BASIC; 638 } 639 } else { 640 format = extended ? VCardDateFormat.DATE_EXTENDED : VCardDateFormat.DATE_BASIC; 641 } 642 643 return format.format(date); 644 } 645 } 646 647 /** 648 * Creates a {@link CannotParseException} to indicate that a scribe could 649 * not find the necessary XML elements required in order to successfully 650 * parse a property (xCards only). 651 * @param dataTypes the expected data types (null for "unknown") 652 * @return the exception object (note that the exception is NOT thrown!) 653 */ 654 protected static CannotParseException missingXmlElements(VCardDataType... dataTypes) { 655 String[] elements = new String[dataTypes.length]; 656 for (int i = 0; i < dataTypes.length; i++) { 657 VCardDataType dataType = dataTypes[i]; 658 elements[i] = (dataType == null) ? "unknown" : dataType.getName().toLowerCase(); 659 } 660 return missingXmlElements(elements); 661 } 662 663 /** 664 * Creates a {@link CannotParseException} to indicate that a scribe could 665 * not find the necessary XML elements required in order to successfully 666 * parse a property (xCards only). 667 * @param elements the names of the expected XML elements. 668 * @return the exception object (note that the exception is NOT thrown!) 669 */ 670 protected static CannotParseException missingXmlElements(String... elements) { 671 return new CannotParseException(0, Arrays.toString(elements)); 672 } 673 674 /** 675 * A utility method for switching between the "PREF" and "TYPE=PREF" 676 * parameters when marshalling a property (version 4.0 vCards use "PREF=1", 677 * while version 3.0 vCards use "TYPE=PREF"). This method is meant to be 678 * called from a scribe's {@link #_prepareParameters} method. 679 * @param property the property that is being marshalled 680 * @param parameters the parameters that are being marshalled (this should 681 * be a copy of the property's parameters so that changes can be made to 682 * them without affecting the original object) 683 * @param version the vCard version that the vCard is being marshalled to 684 * @param vcard the vCard that's being marshalled 685 */ 686 protected static void handlePrefParam(VCardProperty property, VCardParameters parameters, VCardVersion version, VCard vcard) { 687 switch (version) { 688 case V2_1: 689 case V3_0: 690 parameters.setPref(null); 691 692 //find the property with the lowest PREF value in the vCard 693 VCardProperty mostPreferred = null; 694 Integer lowestPref = null; 695 for (VCardProperty p : vcard.getProperties(property.getClass())) { 696 Integer pref; 697 try { 698 pref = p.getParameters().getPref(); 699 } catch (IllegalStateException e) { 700 continue; 701 } 702 703 if (pref == null) { 704 continue; 705 } 706 707 if (lowestPref == null || pref < lowestPref) { 708 mostPreferred = p; 709 lowestPref = pref; 710 } 711 } 712 713 if (property == mostPreferred) { 714 parameters.put(VCardParameters.TYPE, "pref"); 715 } 716 717 break; 718 case V4_0: 719 for (String type : property.getParameters().get(VCardParameters.TYPE)) { 720 if ("pref".equalsIgnoreCase(type)) { 721 parameters.remove(VCardParameters.TYPE, type); 722 parameters.setPref(1); 723 break; 724 } 725 } 726 break; 727 } 728 } 729 730 /** 731 * <p> 732 * Escapes special characters in a property value for writing to a 733 * plain-text output stream. 734 * </p> 735 * <p> 736 * If the target version is 2.1, then the value will be returned unchanged. 737 * 2.1 only requires special characters to be escaped within properties that 738 * make use of those special characters. 739 * </p> 740 * @param value the value to escape 741 * @param context the write context 742 * @return the escaped value 743 */ 744 protected static String escape(String value, WriteContext context) { 745 if (context.getVersion() == VCardVersion.V2_1) { 746 return value; 747 } 748 749 return VObjectPropertyValues.escape(value); 750 } 751}