001 package ezvcard.types; 002 003 import java.net.URI; 004 import java.net.URISyntaxException; 005 import java.util.ArrayList; 006 import java.util.List; 007 import java.util.regex.Matcher; 008 import java.util.regex.Pattern; 009 010 import ezvcard.VCardSubTypes; 011 import ezvcard.VCardVersion; 012 import ezvcard.io.CompatibilityMode; 013 import ezvcard.io.SkipMeException; 014 import ezvcard.parameters.ImppTypeParameter; 015 import ezvcard.util.HCardElement; 016 import ezvcard.util.VCardStringUtils; 017 import ezvcard.util.XCardElement; 018 019 /* 020 Copyright (c) 2012, Michael Angstadt 021 All rights reserved. 022 023 Redistribution and use in source and binary forms, with or without 024 modification, are permitted provided that the following conditions are met: 025 026 1. Redistributions of source code must retain the above copyright notice, this 027 list of conditions and the following disclaimer. 028 2. Redistributions in binary form must reproduce the above copyright notice, 029 this list of conditions and the following disclaimer in the documentation 030 and/or other materials provided with the distribution. 031 032 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 033 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 034 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 035 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 036 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 037 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 038 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 039 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 040 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 041 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 042 043 The views and conclusions contained in the software and documentation are those 044 of the authors and should not be interpreted as representing official policies, 045 either expressed or implied, of the FreeBSD Project. 046 */ 047 048 /** 049 * An instant message handle. The handle is represented as a URI in the format " 050 * <code><IM-PROTOCOL>:<IM-HANDLE></code>". For example, someone 051 * with a Yahoo! Messenger handle of "johndoe@yahoo.com" would have an IMPP 052 * vCard property value of "ymsgr:johndoe@yahoo.com". 053 * 054 * <pre> 055 * VCard vcard = new VCard(); 056 * 057 * //URI 058 * ImppType impp = new ImppType("aim:johndoe@aol.com"); 059 * vcard.addImpp(impp); 060 * 061 * //static helper constructors 062 * impp = ImppType.msn("janedoe@msn.com"); 063 * vcard.addImpp(impp); 064 * </pre> 065 * 066 * <p> 067 * vCard property name: IMPP 068 * </p> 069 * <p> 070 * vCard versions: 3.0, 4.0 071 * </p> 072 * @author Michael Angstadt 073 */ 074 public class ImppType extends MultiValuedTypeParameterType<ImppTypeParameter> { 075 public static final String NAME = "IMPP"; 076 077 private static final String AIM = "aim"; 078 private static final String ICQ = "icq"; 079 private static final String IRC = "irc"; 080 private static final String MSN = "msnim"; 081 private static final String SIP = "sip"; 082 private static final String SKYPE = "skype"; 083 private static final String XMPP = "xmpp"; 084 private static final String YAHOO = "ymsgr"; 085 086 /** 087 * List of recognized IM protocols that can be parsed from an HTML link 088 * (hCard). 089 */ 090 private static final List<ImHtmlLink> htmlParseableProtocols = new ArrayList<ImHtmlLink>(); 091 static { 092 //http://en.wikipedia.org/wiki/AOL_Instant_Messenger#URI_scheme 093 htmlParseableProtocols.add(new ImHtmlLink(AIM, "(goim|addbuddy)\\?.*?\\bscreenname=(.*?)(&|$)", 2, "goim?screenname=%s")); 094 095 //http://en.wikipedia.org/wiki/Yahoo!_Messenger#URI_scheme 096 htmlParseableProtocols.add(new ImHtmlLink(YAHOO, "(sendim|addfriend|sendfile|call)\\?(.*)", 2, "sendim?%s")); 097 098 //http://developer.skype.com/skype-uri/skype-uri-ref-api 099 htmlParseableProtocols.add(new ImHtmlLink(SKYPE, "(.*?)(\\?|$)", 1, "%s")); 100 101 //http://www.tech-recipes.com/rx/1157/msn-messenger-msnim-hyperlink-command-codes/ 102 htmlParseableProtocols.add(new ImHtmlLink(MSN, "(chat|add|voice|video)\\?contact=(.*?)(&|$)", 2, "chat?contact=%s")); 103 104 //http://www.tech-recipes.com/rx/1157/msn-messenger-msnim-hyperlink-command-codes/ 105 htmlParseableProtocols.add(new ImHtmlLink(XMPP, "(.*?)(\\?|$)", 1, "%s?message")); 106 107 //http://forums.miranda-im.org/showthread.php?26589-Add-support-to-quot-icq-message-uin-12345-quot-web-links 108 htmlParseableProtocols.add(new ImHtmlLink(ICQ, "message\\?uin=(\\d+)", 1, "message?uin=%s")); 109 110 //SIP: http://en.wikipedia.org/wiki/Session_Initiation_Protocol 111 //leave as-is 112 htmlParseableProtocols.add(new ImHtmlLink(SIP)); 113 114 //IRC: http://stackoverflow.com/questions/11970897/how-do-i-open-a-query-window-using-the-irc-uri-scheme 115 //IRC handles are not globally unique, so leave as-is 116 htmlParseableProtocols.add(new ImHtmlLink(IRC)); 117 } 118 119 private URI uri; 120 121 public ImppType() { 122 super(NAME); 123 } 124 125 /** 126 * Constructs a new IMPP type. Note that this class has static convenience 127 * methods for creating IMPP types of common IM protocols. 128 * @param uri the IM URI (e.g. "aim:johndoe@aol.com") 129 * @throws IllegalArgumentException if the URI is not a valid URI 130 */ 131 public ImppType(String uri) { 132 this(); 133 setUri(uri); 134 } 135 136 /** 137 * Constructs a new IMPP type. Note that this class has static convenience 138 * methods for creating IMPP types of common IM protocols. 139 * @param uri the IM URI (e.g. "aim:johndoe@aol.com") 140 */ 141 public ImppType(URI uri) { 142 this(); 143 setUri(uri); 144 } 145 146 /** 147 * Constructs a new IMPP type. Note that this class has static convenience 148 * methods for creating IMPP types of common IM protocols. 149 * @param protocol the IM protocol (e.g. "aim") 150 * @param handle the IM handle (e.g. "johndoe@aol.com") 151 */ 152 public ImppType(String protocol, String handle) { 153 this(); 154 setUri(protocol, handle); 155 } 156 157 /** 158 * Creates an IMPP property that contains a AOL Instant Messenger handle. 159 * @param handle the IM handle 160 * @return the IMPP property instance 161 */ 162 public static ImppType aim(String handle) { 163 return new ImppType(AIM, handle); 164 } 165 166 /** 167 * Determines if this IMPP property contains an AOL Instant Messenger 168 * handle. 169 * @return true if it contains an AOL Instant Messenger handle, false if not 170 */ 171 public boolean isAim() { 172 return isProtocol(AIM); 173 } 174 175 /** 176 * Creates an IMPP property that contains a Yahoo! Messenger handle. 177 * @param handle the IM handle 178 * @return the IMPP property instance 179 */ 180 public static ImppType yahoo(String handle) { 181 return new ImppType(YAHOO, handle); 182 } 183 184 /** 185 * Determines if this IMPP property contains a Yahoo! Messenger handle. 186 * @return true if it contains a Yahoo! Messenger handle, false if not 187 */ 188 public boolean isYahoo() { 189 return isProtocol(YAHOO); 190 } 191 192 /** 193 * Creates an IMPP property that contains an MSN IMPP property. 194 * @param handle the IM handle 195 * @return the IMPP property instance 196 */ 197 public static ImppType msn(String handle) { 198 return new ImppType(MSN, handle); 199 } 200 201 /** 202 * Determines if this IMPP property contains an MSN handle. 203 * @return true if it contains an MSN handle, false if not 204 */ 205 public boolean isMsn() { 206 return isProtocol(MSN); 207 } 208 209 /** 210 * Creates an IMPP property that contains an ICQ handle. 211 * @param handle the IM handle 212 * @return the IMPP property instance 213 */ 214 public static ImppType icq(String handle) { 215 return new ImppType(ICQ, handle); 216 } 217 218 /** 219 * Determines if this IMPP property contains an ICQ handle. 220 * @return true if it contains an ICQ handle, false if not 221 */ 222 public boolean isIcq() { 223 return isProtocol(ICQ); 224 } 225 226 /** 227 * Creates an IMPP property that contains an IRC handle. 228 * @param handle the IM handle 229 * @return the IMPP property instance 230 */ 231 public static ImppType irc(String handle) { 232 return new ImppType(IRC, handle); 233 } 234 235 /** 236 * Determines if this IMPP property contains an IRC handle. 237 * @return true if it contains an IRC handle, false if not 238 */ 239 public boolean isIrc() { 240 return isProtocol(IRC); 241 } 242 243 /** 244 * Creates an IMPP property that contains a Session Initiation Protocol 245 * handle. 246 * @param handle the IM handle 247 * @return the IMPP property instance 248 */ 249 public static ImppType sip(String handle) { 250 return new ImppType(SIP, handle); 251 } 252 253 /** 254 * Determines if this IMPP property contains a Session Initiation Protocol 255 * handle. 256 * @return true if it contains a SIP handle, false if not 257 */ 258 public boolean isSip() { 259 return isProtocol(SIP); 260 } 261 262 /** 263 * Creates an IMPP property that contains a Skype handle. 264 * @param handle the IM handle 265 * @return the IMPP property instance 266 */ 267 public static ImppType skype(String handle) { 268 return new ImppType(SKYPE, handle); 269 } 270 271 /** 272 * Determines if this IMPP property contains a Skype handle. 273 * @return true if it contains a Skype handle, false if not 274 */ 275 public boolean isSkype() { 276 return isProtocol(SKYPE); 277 } 278 279 /** 280 * Creates an IMPP property that contains an Extensible Messaging and 281 * Presence Protocol handle. 282 * @param handle the IM handle 283 * @return the IMPP property instance 284 */ 285 public static ImppType xmpp(String handle) { 286 return new ImppType(XMPP, handle); 287 } 288 289 /** 290 * Determines if this IMPP property contains an Extensible Messaging and 291 * Presence Protocol handle. 292 * @return true if it contains an XMPP handle, false if not 293 */ 294 public boolean isXmpp() { 295 return isProtocol(XMPP); 296 } 297 298 private boolean isProtocol(String protocol) { 299 return uri != null && protocol.equals(uri.getScheme()); 300 } 301 302 /** 303 * Gets the IM URI. 304 * @return the IM URI 305 */ 306 public URI getUri() { 307 return uri; 308 } 309 310 /** 311 * Sets the IM URI. 312 * @param uri the IM URI (e.g. "aim:theuser@aol.com") 313 * @throws IllegalArgumentException if the URI is not a valid URI 314 */ 315 public void setUri(String uri) { 316 setUri(URI.create(uri)); 317 } 318 319 /** 320 * Sets the IM URI. 321 * @param uri the IM URI (e.g. "aim:theuser@aol.com") 322 */ 323 public void setUri(URI uri) { 324 this.uri = uri; 325 } 326 327 /** 328 * Sets the IM URI. 329 * @param protocol the IM protocol (e.g. "aim") 330 * @param handle the IM handle (e.g. "theuser@aol.com") 331 */ 332 public void setUri(String protocol, String handle) { 333 try { 334 this.uri = new URI(protocol, handle, null); 335 } catch (URISyntaxException e) { 336 throw new IllegalArgumentException(e); 337 } 338 } 339 340 /** 341 * Gets the IM protocol. Use {@link #setUri(String, String)} to set the 342 * protocol. 343 * @return the IM protocol (e.g. "aim") or null if not set 344 */ 345 public String getProtocol() { 346 if (uri == null) { 347 return null; 348 } 349 return uri.getScheme(); 350 } 351 352 /** 353 * Gets the IM handle. Use {@link #setUri(String, String)} to set the 354 * handle. 355 * @return the IM handle (e.g. "johndoe@aol.com") or null if not set 356 */ 357 public String getHandle() { 358 if (uri == null) { 359 return null; 360 } 361 return uri.getSchemeSpecificPart(); 362 } 363 364 /** 365 * Gets the MEDIATYPE parameter. 366 * <p> 367 * vCard versions: 4.0 368 * </p> 369 * @return the media type or null if not set 370 */ 371 public String getMediaType() { 372 return subTypes.getMediaType(); 373 } 374 375 /** 376 * Sets the MEDIATYPE parameter. 377 * <p> 378 * vCard versions: 4.0 379 * </p> 380 * @param mediaType the media type or null to remove 381 */ 382 public void setMediaType(String mediaType) { 383 subTypes.setMediaType(mediaType); 384 } 385 386 /** 387 * Gets all PID parameter values. 388 * <p> 389 * vCard versions: 4.0 390 * </p> 391 * @return the PID values or empty set if there are none 392 * @see VCardSubTypes#getPids 393 */ 394 public List<Integer[]> getPids() { 395 return subTypes.getPids(); 396 } 397 398 /** 399 * Adds a PID value. 400 * <p> 401 * vCard versions: 4.0 402 * </p> 403 * @param localId the local ID 404 * @param clientPidMapRef the ID used to reference the property's globally 405 * unique identifier in the CLIENTPIDMAP property. 406 * @see VCardSubTypes#addPid(int, int) 407 */ 408 public void addPid(int localId, int clientPidMapRef) { 409 subTypes.addPid(localId, clientPidMapRef); 410 } 411 412 /** 413 * Removes all PID values. 414 * <p> 415 * vCard versions: 4.0 416 * </p> 417 * @see VCardSubTypes#removePids 418 */ 419 public void removePids() { 420 subTypes.removePids(); 421 } 422 423 /** 424 * Gets the preference value. 425 * <p> 426 * vCard versions: 4.0 427 * </p> 428 * @return the preference value or null if it doesn't exist 429 * @see VCardSubTypes#getPref 430 */ 431 public Integer getPref() { 432 return subTypes.getPref(); 433 } 434 435 /** 436 * Sets the preference value. 437 * <p> 438 * vCard versions: 4.0 439 * </p> 440 * @param pref the preference value or null to remove 441 * @see VCardSubTypes#setPref 442 */ 443 public void setPref(Integer pref) { 444 subTypes.setPref(pref); 445 } 446 447 /** 448 * Gets the ALTID. 449 * <p> 450 * vCard versions: 4.0 451 * </p> 452 * @return the ALTID or null if it doesn't exist 453 * @see VCardSubTypes#getAltId 454 */ 455 public String getAltId() { 456 return subTypes.getAltId(); 457 } 458 459 /** 460 * Sets the ALTID. 461 * <p> 462 * vCard versions: 4.0 463 * </p> 464 * @param altId the ALTID or null to remove 465 * @see VCardSubTypes#setAltId 466 */ 467 public void setAltId(String altId) { 468 subTypes.setAltId(altId); 469 } 470 471 @Override 472 protected ImppTypeParameter buildTypeObj(String type) { 473 ImppTypeParameter param = ImppTypeParameter.valueOf(type); 474 if (param == null) { 475 param = new ImppTypeParameter(type); 476 } 477 return param; 478 } 479 480 @Override 481 public VCardVersion[] getSupportedVersions() { 482 return new VCardVersion[] { VCardVersion.V3_0, VCardVersion.V4_0 }; 483 } 484 485 @Override 486 protected void doMarshalText(StringBuilder sb, VCardVersion version, List<String> warnings, CompatibilityMode compatibilityMode) { 487 if (uri != null) { 488 sb.append(VCardStringUtils.escape(uri.toString())); 489 } 490 } 491 492 @Override 493 protected void doUnmarshalText(String value, VCardVersion version, List<String> warnings, CompatibilityMode compatibilityMode) { 494 value = VCardStringUtils.unescape(value); 495 try { 496 setUri(value); 497 } catch (IllegalArgumentException e) { 498 throw new SkipMeException("Cannot parse URI \"" + value + "\": " + e.getMessage()); 499 } 500 } 501 502 @Override 503 protected void doMarshalXml(XCardElement parent, List<String> warnings, CompatibilityMode compatibilityMode) { 504 if (uri != null) { 505 parent.uri(uri.toString()); 506 } 507 } 508 509 @Override 510 protected void doUnmarshalXml(XCardElement element, List<String> warnings, CompatibilityMode compatibilityMode) { 511 String value = element.uri(); 512 try { 513 setUri(value); 514 } catch (IllegalArgumentException e) { 515 throw new SkipMeException("Cannot parse URI \"" + value + "\": " + e.getMessage()); 516 } 517 } 518 519 @Override 520 protected void doUnmarshalHtml(HCardElement element, List<String> warnings) { 521 String href = element.attr("href"); 522 if (href.length() == 0) { 523 href = element.value(); 524 } 525 526 try { 527 URI uri = parseUriFromLink(href); 528 if (uri == null) { 529 throw new IllegalArgumentException(); 530 } 531 setUri(uri); 532 } catch (IllegalArgumentException e) { 533 throw new SkipMeException("Could not parse instant messenger information out of link: " + href); 534 } 535 } 536 537 /** 538 * Parses an IM URI from an HTML link. 539 * @param linkUri the HTML link (e.g. "aim:goim?screenname=theuser") 540 * @return the IM URI or null if not recognized 541 */ 542 protected static URI parseUriFromLink(String linkUri) { 543 for (ImHtmlLink imLink : htmlParseableProtocols) { 544 String handle = imLink.parseHandle(linkUri); 545 if (handle != null) { 546 try { 547 return new URI(imLink.getProtocol(), handle, null); 548 } catch (URISyntaxException e) { 549 throw new IllegalArgumentException(e); 550 } 551 } 552 } 553 return null; 554 } 555 556 /** 557 * Builds a URI suitable for use as a link on a webpage. 558 * @return the link URI (e.g. "aim:goim?screenname=theuser") or null if the 559 * IMPP URI was never set 560 */ 561 public String buildLink() { 562 if (uri == null) { 563 return null; 564 } 565 566 String protocol = uri.getScheme(); 567 String handle = uri.getSchemeSpecificPart(); 568 569 for (ImHtmlLink imLink : htmlParseableProtocols) { 570 if (protocol.equals(imLink.getProtocol())) { 571 return imLink.buildLink(handle); 572 } 573 } 574 return uri.toString(); 575 } 576 577 /** 578 * Helper class for parsing and building instant messenger links for 579 * webpages. 580 */ 581 protected static class ImHtmlLink { 582 private final Pattern linkRegex; 583 private final String protocol; 584 private final int handleGroup; 585 private final String linkFormat; 586 587 /** 588 * @param protocol the IM protocol (e.g. "aim") 589 */ 590 public ImHtmlLink(String protocol) { 591 this(protocol, "(.*)", 1, "%s"); 592 } 593 594 /** 595 * @param protocol the IM protocol (e.g. "aim") 596 * @param linkRegex the regular expression used to parse a link 597 * @param handleGroup the group number from the regular expression that 598 * contains the IM handle 599 * @param linkFormat the format string for building a link 600 */ 601 public ImHtmlLink(String protocol, String linkRegex, int handleGroup, String linkFormat) { 602 this.linkRegex = Pattern.compile('^' + protocol + ':' + linkRegex, Pattern.CASE_INSENSITIVE); 603 this.protocol = protocol; 604 this.handleGroup = handleGroup; 605 this.linkFormat = protocol + ':' + linkFormat; 606 } 607 608 /** 609 * Parses the IM handle out of a link. 610 * @param linkUri the link 611 * @return the IM handle or null if it can't be found 612 */ 613 public String parseHandle(String linkUri) { 614 Matcher m = linkRegex.matcher(linkUri); 615 if (m.find()) { 616 return m.group(handleGroup); 617 } 618 return null; 619 } 620 621 /** 622 * Builds a link for inclusion in a webpage. 623 * @param handle the IM handle 624 * @return the link 625 */ 626 public String buildLink(String handle) { 627 return String.format(linkFormat, handle); 628 } 629 630 public String getProtocol() { 631 return protocol; 632 } 633 } 634 }