001    package ezvcard.io.scribe;
002    
003    import java.net.URI;
004    import java.net.URISyntaxException;
005    import java.util.ArrayList;
006    import java.util.Collections;
007    import java.util.List;
008    import java.util.regex.Matcher;
009    import java.util.regex.Pattern;
010    
011    import ezvcard.VCardDataType;
012    import ezvcard.VCardVersion;
013    import ezvcard.io.CannotParseException;
014    import ezvcard.io.html.HCardElement;
015    import ezvcard.io.json.JCardValue;
016    import ezvcard.io.xml.XCardElement;
017    import ezvcard.parameter.VCardParameters;
018    import ezvcard.property.Impp;
019    
020    /*
021     Copyright (c) 2013, Michael Angstadt
022     All rights reserved.
023    
024     Redistribution and use in source and binary forms, with or without
025     modification, are permitted provided that the following conditions are met: 
026    
027     1. Redistributions of source code must retain the above copyright notice, this
028     list of conditions and the following disclaimer. 
029     2. Redistributions in binary form must reproduce the above copyright notice,
030     this list of conditions and the following disclaimer in the documentation
031     and/or other materials provided with the distribution. 
032    
033     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
034     ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
035     WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
036     DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
037     ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
038     (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
039     LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
040     ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
041     (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
042     SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
043     */
044    
045    /**
046     * Marshals {@link Impp} properties.
047     * @author Michael Angstadt
048     */
049    public class ImppScribe extends VCardPropertyScribe<Impp> {
050            public static final String AIM = "aim";
051            public static final String ICQ = "icq";
052            public static final String IRC = "irc";
053            public static final String MSN = "msnim";
054            public static final String SIP = "sip";
055            public static final String SKYPE = "skype";
056            public static final String XMPP = "xmpp";
057            public static final String YAHOO = "ymsgr";
058    
059            public ImppScribe() {
060                    super(Impp.class, "IMPP");
061            }
062    
063            @Override
064            protected VCardDataType _defaultDataType(VCardVersion version) {
065                    return VCardDataType.URI;
066            }
067    
068            @Override
069            protected String _writeText(Impp property, VCardVersion version) {
070                    return write(property);
071            }
072    
073            @Override
074            protected Impp _parseText(String value, VCardDataType dataType, VCardVersion version, VCardParameters parameters, List<String> warnings) {
075                    value = unescape(value);
076                    return parse(value);
077            }
078    
079            @Override
080            protected void _writeXml(Impp property, XCardElement parent) {
081                    parent.append(VCardDataType.URI, write(property));
082            }
083    
084            @Override
085            protected Impp _parseXml(XCardElement element, VCardParameters parameters, List<String> warnings) {
086                    String value = element.first(VCardDataType.URI);
087                    if (value != null) {
088                            return parse(value);
089                    }
090    
091                    throw missingXmlElements(VCardDataType.URI);
092            }
093    
094            @Override
095            protected Impp _parseHtml(HCardElement element, List<String> warnings) {
096                    String href = element.attr("href");
097                    if (href.length() == 0) {
098                            href = element.value();
099                    }
100    
101                    try {
102                            URI uri = parseHtmlLink(href);
103                            if (uri == null) {
104                                    throw new IllegalArgumentException();
105                            }
106                            return new Impp(uri);
107                    } catch (IllegalArgumentException e) {
108                            throw new CannotParseException(14, href);
109                    }
110            }
111    
112            @Override
113            protected JCardValue _writeJson(Impp property) {
114                    return JCardValue.single(write(property));
115            }
116    
117            @Override
118            protected Impp _parseJson(JCardValue value, VCardDataType dataType, VCardParameters parameters, List<String> warnings) {
119                    return parse(value.asSingle());
120            }
121    
122            private String write(Impp property) {
123                    URI uri = property.getUri();
124                    return (uri == null) ? "" : uri.toString();
125            }
126    
127            private Impp parse(String value) {
128                    if (value == null || value.length() == 0) {
129                            return new Impp((URI) null);
130                    }
131    
132                    try {
133                            return new Impp(value);
134                    } catch (IllegalArgumentException e) {
135                            throw new CannotParseException(15, value, e.getMessage());
136                    }
137            }
138    
139            /**
140             * List of recognized IM protocols that can be parsed from an HTML link
141             * (hCard).
142             */
143            private static final List<HtmlLinkFormat> htmlLinkFormats;
144            static {
145                    List<HtmlLinkFormat> list = new ArrayList<HtmlLinkFormat>();
146    
147                    //http://en.wikipedia.org/wiki/AOL_Instant_Messenger#URI_scheme
148                    list.add(new HtmlLinkFormat(AIM, "(goim|addbuddy)\\?.*?\\bscreenname=(.*?)(&|$)", 2, "goim?screenname=%s"));
149    
150                    //http://en.wikipedia.org/wiki/Yahoo!_Messenger#URI_scheme
151                    list.add(new HtmlLinkFormat(YAHOO, "(sendim|addfriend|sendfile|call)\\?(.*)", 2, "sendim?%s"));
152    
153                    //http://developer.skype.com/skype-uri/skype-uri-ref-api
154                    list.add(new HtmlLinkFormat(SKYPE, "(.*?)(\\?|$)", 1, "%s"));
155    
156                    //http://www.tech-recipes.com/rx/1157/msn-messenger-msnim-hyperlink-command-codes/
157                    list.add(new HtmlLinkFormat(MSN, "(chat|add|voice|video)\\?contact=(.*?)(&|$)", 2, "chat?contact=%s"));
158    
159                    //http://www.tech-recipes.com/rx/1157/msn-messenger-msnim-hyperlink-command-codes/
160                    list.add(new HtmlLinkFormat(XMPP, "(.*?)(\\?|$)", 1, "%s?message"));
161    
162                    //http://forums.miranda-im.org/showthread.php?26589-Add-support-to-quot-icq-message-uin-12345-quot-web-links
163                    list.add(new HtmlLinkFormat(ICQ, "message\\?uin=(\\d+)", 1, "message?uin=%s"));
164    
165                    //SIP: http://en.wikipedia.org/wiki/Session_Initiation_Protocol
166                    //leave as-is
167                    list.add(new HtmlLinkFormat(SIP));
168    
169                    //IRC: http://stackoverflow.com/questions/11970897/how-do-i-open-a-query-window-using-the-irc-uri-scheme
170                    //IRC handles are not globally unique, so leave as-is
171                    list.add(new HtmlLinkFormat(IRC));
172    
173                    htmlLinkFormats = Collections.unmodifiableList(list);
174            }
175    
176            /**
177             * Parses an IM URI from an HTML link.
178             * @param linkUri the HTML link (e.g. "aim:goim?screenname=theuser")
179             * @return the IM URI or null if not recognized
180             */
181            public URI parseHtmlLink(String linkUri) {
182                    for (HtmlLinkFormat format : htmlLinkFormats) {
183                            String handle = format.parseHandle(linkUri);
184                            if (handle == null) {
185                                    continue;
186                            }
187    
188                            try {
189                                    return new URI(format.getProtocol(), handle, null);
190                            } catch (URISyntaxException e) {
191                                    throw new IllegalArgumentException(e);
192                            }
193                    }
194                    return null;
195            }
196    
197            /**
198             * Builds a URI suitable for use as a link on a webpage.
199             * @param property the property
200             * @return the link URI (e.g. "aim:goim?screenname=theuser") or null if the
201             * property has no URI
202             */
203            public String writeHtmlLink(Impp property) {
204                    URI uri = property.getUri();
205                    if (uri == null) {
206                            return null;
207                    }
208    
209                    String protocol = uri.getScheme();
210                    String handle = uri.getSchemeSpecificPart();
211    
212                    for (HtmlLinkFormat format : htmlLinkFormats) {
213                            if (protocol.equals(format.getProtocol())) {
214                                    return format.buildLink(handle);
215                            }
216                    }
217                    return uri.toString();
218            }
219    
220            private static class HtmlLinkFormat {
221                    private final Pattern parseRegex;
222                    private final String protocol;
223                    private final int handleGroup;
224                    private final String linkFormat;
225    
226                    /**
227                     * @param protocol the IM protocol (e.g. "aim")
228                     */
229                    public HtmlLinkFormat(String protocol) {
230                            this(protocol, "(.*)", 1, "%s");
231                    }
232    
233                    /**
234                     * @param protocol the IM protocol (e.g. "aim")
235                     * @param linkRegex the regular expression used to parse a link
236                     * @param handleGroup the group number from the regular expression that
237                     * contains the IM handle
238                     * @param linkFormat the format string for building a link
239                     */
240                    public HtmlLinkFormat(String protocol, String linkRegex, int handleGroup, String linkFormat) {
241                            this.parseRegex = Pattern.compile('^' + protocol + ':' + linkRegex, Pattern.CASE_INSENSITIVE);
242                            this.protocol = protocol;
243                            this.handleGroup = handleGroup;
244                            this.linkFormat = protocol + ':' + linkFormat;
245                    }
246    
247                    /**
248                     * Parses the IM handle out of a link.
249                     * @param linkUri the link
250                     * @return the IM handle or null if it can't be found
251                     */
252                    public String parseHandle(String linkUri) {
253                            Matcher m = parseRegex.matcher(linkUri);
254                            return m.find() ? m.group(handleGroup) : null;
255                    }
256    
257                    /**
258                     * Builds a link for inclusion in a webpage.
259                     * @param handle the IM handle
260                     * @return the link
261                     */
262                    public String buildLink(String handle) {
263                            return String.format(linkFormat, handle);
264                    }
265    
266                    /**
267                     * Gets the protocol.
268                     * @return the protocol (e.g. "aim")
269                     */
270                    public String getProtocol() {
271                            return protocol;
272                    }
273            }
274    }