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 }