Merge branch 'api-level-18' of https://github.com/deuill/Conversations into deuill-api-level-18
This commit is contained in:
		
						commit
						c071a53595
					
				| @ -56,7 +56,7 @@ dependencies { | |||||||
|     implementation 'com.makeramen:roundedimageview:2.3.0' |     implementation 'com.makeramen:roundedimageview:2.3.0' | ||||||
|     implementation "com.wefika:flowlayout:0.4.1" |     implementation "com.wefika:flowlayout:0.4.1" | ||||||
|     implementation 'net.ypresto.androidtranscoder:android-transcoder:0.2.0' |     implementation 'net.ypresto.androidtranscoder:android-transcoder:0.2.0' | ||||||
|     implementation 'rocks.xmpp:xmpp-addr:0.8.0' |     implementation project(':libs:xmpp-addr') | ||||||
|     implementation 'org.osmdroid:osmdroid-android:6.0.1' |     implementation 'org.osmdroid:osmdroid-android:6.0.1' | ||||||
|     implementation 'org.hsluv:hsluv:0.2' |     implementation 'org.hsluv:hsluv:0.2' | ||||||
|     implementation 'org.conscrypt:conscrypt-android:1.3.0' |     implementation 'org.conscrypt:conscrypt-android:1.3.0' | ||||||
| @ -73,7 +73,7 @@ android { | |||||||
|     compileSdkVersion 28 |     compileSdkVersion 28 | ||||||
| 
 | 
 | ||||||
|     defaultConfig { |     defaultConfig { | ||||||
|         minSdkVersion 19 |         minSdkVersion 16 | ||||||
|         targetSdkVersion 28 |         targetSdkVersion 28 | ||||||
|         versionCode 307 |         versionCode 307 | ||||||
|         versionName "2.3.9" |         versionName "2.3.9" | ||||||
|  | |||||||
							
								
								
									
										1
									
								
								libs/xmpp-addr/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								libs/xmpp-addr/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | |||||||
|  | /build | ||||||
							
								
								
									
										14
									
								
								libs/xmpp-addr/build.gradle
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								libs/xmpp-addr/build.gradle
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | |||||||
|  | apply plugin: 'java-library' | ||||||
|  | 
 | ||||||
|  | repositories { | ||||||
|  |     google() | ||||||
|  |     jcenter() | ||||||
|  |     mavenCentral() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | dependencies { | ||||||
|  |     implementation 'rocks.xmpp:precis:1.0.0' | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | sourceCompatibility = "8" | ||||||
|  | targetCompatibility = "8" | ||||||
							
								
								
									
										179
									
								
								libs/xmpp-addr/src/main/java/rocks/xmpp/addr/AbstractJid.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										179
									
								
								libs/xmpp-addr/src/main/java/rocks/xmpp/addr/AbstractJid.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,179 @@ | |||||||
|  | /* | ||||||
|  |  * The MIT License (MIT) | ||||||
|  |  * | ||||||
|  |  * Copyright (c) 2014-2017 Christian Schudt | ||||||
|  |  * | ||||||
|  |  * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||||
|  |  * of this software and associated documentation files (the "Software"), to deal | ||||||
|  |  * in the Software without restriction, including without limitation the rights | ||||||
|  |  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||||
|  |  * copies of the Software, and to permit persons to whom the Software is | ||||||
|  |  * furnished to do so, subject to the following conditions: | ||||||
|  |  * | ||||||
|  |  * The above copyright notice and this permission notice shall be included in | ||||||
|  |  * all copies or substantial portions of the Software. | ||||||
|  |  * | ||||||
|  |  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||||
|  |  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||||
|  |  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||||
|  |  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||||
|  |  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||||
|  |  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||||
|  |  * THE SOFTWARE. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | package rocks.xmpp.addr; | ||||||
|  | 
 | ||||||
|  | import java.text.Collator; | ||||||
|  | import java.util.Arrays; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Abstract Jid implementation for both full and bare JIDs. | ||||||
|  |  * | ||||||
|  |  * @author Christian Schudt | ||||||
|  |  */ | ||||||
|  | abstract class AbstractJid implements Jid { | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Checks if the JID is a full JID. | ||||||
|  |      * <blockquote> | ||||||
|  |      * <p>The term "full JID" refers to an XMPP address of the form <localpart@domainpart/resourcepart> (for a particular authorized client or device associated with an account) or of the form <domainpart/resourcepart> (for a particular resource or script associated with a server).</p> | ||||||
|  |      * </blockquote> | ||||||
|  |      * | ||||||
|  |      * @return True, if the JID is a full JID; otherwise false. | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public final boolean isFullJid() { | ||||||
|  |         return getResource() != null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Checks if the JID is a bare JID. | ||||||
|  |      * <blockquote> | ||||||
|  |      * <p>The term "bare JID" refers to an XMPP address of the form <localpart@domainpart> (for an account at a server) or of the form <domainpart> (for a server).</p> | ||||||
|  |      * </blockquote> | ||||||
|  |      * | ||||||
|  |      * @return True, if the JID is a bare JID; otherwise false. | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public final boolean isBareJid() { | ||||||
|  |         return getResource() == null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public final boolean isDomainJid() { | ||||||
|  |         return getLocal() == null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public final boolean equals(Object o) { | ||||||
|  |         if (o == this) { | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  |         if (!(o instanceof Jid)) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         Jid other = (Jid) o; | ||||||
|  | 
 | ||||||
|  |         return (getLocal() == other.getLocal() || getLocal() != null && getLocal().equals(other.getLocal())) | ||||||
|  |             && (getDomain() == other.getDomain() || getDomain() != null && getDomain().equals(other.getDomain())) | ||||||
|  |             && (getResource() == other.getResource() || getResource() != null && getResource().equals(other.getResource())); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public final int hashCode() { | ||||||
|  |         return Arrays.hashCode(new String[]{getLocal(), getDomain(), getResource()}); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Compares this JID with another JID. First domain parts are compared. If these are equal, local parts are compared | ||||||
|  |      * and if these are equal, too, resource parts are compared. | ||||||
|  |      * | ||||||
|  |      * @param o The other JID. | ||||||
|  |      * @return The comparison result. | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public final int compareTo(Jid o) { | ||||||
|  | 
 | ||||||
|  |         if (this == o) { | ||||||
|  |             return 0; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (o != null) { | ||||||
|  |             final Collator collator = Collator.getInstance(); | ||||||
|  |             int result; | ||||||
|  |             // First compare domain parts. | ||||||
|  |             if (getDomain() != null) { | ||||||
|  |                 result = o.getDomain() != null ? collator.compare(getDomain(), o.getDomain()) : -1; | ||||||
|  |             } else { | ||||||
|  |                 result = o.getDomain() != null ? 1 : 0; | ||||||
|  |             } | ||||||
|  |             // If the domains are equal, compare local parts. | ||||||
|  |             if (result == 0) { | ||||||
|  |                 if (getLocal() != null) { | ||||||
|  |                     // If this local part is not null, but the other is null, move this down (1). | ||||||
|  |                     result = o.getLocal() != null ? collator.compare(getLocal(), o.getLocal()) : 1; | ||||||
|  |                 } else { | ||||||
|  |                     // If this local part is null, but the other is not, move this up (-1). | ||||||
|  |                     result = o.getLocal() != null ? -1 : 0; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             // If the local parts are equal, compare resource parts. | ||||||
|  |             if (result == 0) { | ||||||
|  |                 if (getResource() != null) { | ||||||
|  |                     // If this resource part is not null, but the other is null, move this down (1). | ||||||
|  |                     return o.getResource() != null ? collator.compare(getResource(), o.getResource()) : 1; | ||||||
|  |                 } else { | ||||||
|  |                     // If this resource part is null, but the other is not, move this up (-1). | ||||||
|  |                     return o.getResource() != null ? -1 : 0; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             return result; | ||||||
|  |         } else { | ||||||
|  |             return -1; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public final int length() { | ||||||
|  |         return toString().length(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public final char charAt(int index) { | ||||||
|  |         return toString().charAt(index); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public final CharSequence subSequence(int start, int end) { | ||||||
|  |         return toString().subSequence(start, end); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Returns the JID in its string representation, i.e. [ localpart "@" ] domainpart [ "/" resourcepart ]. | ||||||
|  |      * | ||||||
|  |      * @return The JID. | ||||||
|  |      * @see #toEscapedString() | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public final String toString() { | ||||||
|  |         return toString(getLocal(), getDomain(), getResource()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public final String toEscapedString() { | ||||||
|  |         return toString(getEscapedLocal(), getDomain(), getResource()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     static String toString(String local, String domain, String resource) { | ||||||
|  |         StringBuilder sb = new StringBuilder(); | ||||||
|  |         if (local != null) { | ||||||
|  |             sb.append(local).append('@'); | ||||||
|  |         } | ||||||
|  |         sb.append(domain); | ||||||
|  |         if (resource != null) { | ||||||
|  |             sb.append('/').append(resource); | ||||||
|  |         } | ||||||
|  |         return sb.toString(); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										498
									
								
								libs/xmpp-addr/src/main/java/rocks/xmpp/addr/FullJid.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										498
									
								
								libs/xmpp-addr/src/main/java/rocks/xmpp/addr/FullJid.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,498 @@ | |||||||
|  | /* | ||||||
|  |  * The MIT License (MIT) | ||||||
|  |  * | ||||||
|  |  * Copyright (c) 2014-2017 Christian Schudt | ||||||
|  |  * | ||||||
|  |  * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||||
|  |  * of this software and associated documentation files (the "Software"), to deal | ||||||
|  |  * in the Software without restriction, including without limitation the rights | ||||||
|  |  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||||
|  |  * copies of the Software, and to permit persons to whom the Software is | ||||||
|  |  * furnished to do so, subject to the following conditions: | ||||||
|  |  * | ||||||
|  |  * The above copyright notice and this permission notice shall be included in | ||||||
|  |  * all copies or substantial portions of the Software. | ||||||
|  |  * | ||||||
|  |  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||||
|  |  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||||
|  |  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||||
|  |  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||||
|  |  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||||
|  |  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||||
|  |  * THE SOFTWARE. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | package rocks.xmpp.addr; | ||||||
|  | 
 | ||||||
|  | import rocks.xmpp.precis.PrecisProfile; | ||||||
|  | import rocks.xmpp.precis.PrecisProfiles; | ||||||
|  | import rocks.xmpp.util.cache.LruCache; | ||||||
|  | 
 | ||||||
|  | import java.net.IDN; | ||||||
|  | import java.nio.charset.Charset; | ||||||
|  | import java.text.Normalizer; | ||||||
|  | import java.util.Map; | ||||||
|  | import java.util.regex.Matcher; | ||||||
|  | import java.util.regex.Pattern; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * The implementation of the JID as described in <a href="https://tools.ietf.org/html/rfc7622">Extensible Messaging and Presence Protocol (XMPP): Address Format</a>. | ||||||
|  |  * <p> | ||||||
|  |  * This class is thread-safe and immutable. | ||||||
|  |  * | ||||||
|  |  * @author Christian Schudt | ||||||
|  |  * @see <a href="https://tools.ietf.org/html/rfc7622">RFC 7622 - Extensible Messaging and Presence Protocol (XMPP): Address Format</a> | ||||||
|  |  */ | ||||||
|  | final class FullJid extends AbstractJid { | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Escapes all disallowed characters and also backslash, when followed by a defined hex code for escaping. See 4. Business Rules. | ||||||
|  |      */ | ||||||
|  |     private static final Pattern ESCAPE_PATTERN = Pattern.compile("[ \"&'/:<>@]|\\\\(?=20|22|26|27|2f|3a|3c|3e|40|5c)"); | ||||||
|  | 
 | ||||||
|  |     private static final Pattern UNESCAPE_PATTERN = Pattern.compile("\\\\(20|22|26|27|2f|3a|3c|3e|40|5c)"); | ||||||
|  | 
 | ||||||
|  |     private static final Pattern JID = Pattern.compile("^((.*?)@)?([^/@]+)(/(.*))?$"); | ||||||
|  | 
 | ||||||
|  |     private static final IDNProfile IDN_PROFILE = new IDNProfile(); | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Whenever dots are used as label separators, the following characters MUST be recognized as dots: U+002E (full stop), U+3002 (ideographic full stop), U+FF0E (fullwidth full stop), U+FF61 (halfwidth ideographic full stop). | ||||||
|  |      */ | ||||||
|  |     private static final String DOTS = "[.\u3002\uFF0E\uFF61]"; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Label separators for domain labels, which should be mapped to "." (dot): IDEOGRAPHIC FULL STOP character (U+3002) | ||||||
|  |      */ | ||||||
|  |     private static final Pattern LABEL_SEPARATOR = Pattern.compile(DOTS); | ||||||
|  | 
 | ||||||
|  |     private static final Pattern LABEL_SEPARATOR_FINAL = Pattern.compile(DOTS + "$"); | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Caches the escaped JIDs. | ||||||
|  |      */ | ||||||
|  |     private static final Map<CharSequence, Jid> ESCAPED_CACHE = new LruCache<>(5000); | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Caches the unescaped JIDs. | ||||||
|  |      */ | ||||||
|  |     private static final Map<CharSequence, Jid> UNESCAPED_CACHE = new LruCache<>(5000); | ||||||
|  | 
 | ||||||
|  |     private static final long serialVersionUID = -3824234106101731424L; | ||||||
|  | 
 | ||||||
|  |     private final String escapedLocal; | ||||||
|  | 
 | ||||||
|  |     private final String local; | ||||||
|  | 
 | ||||||
|  |     private final String domain; | ||||||
|  | 
 | ||||||
|  |     private final String resource; | ||||||
|  | 
 | ||||||
|  |     private final Jid bareJid; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Creates a full JID with local, domain and resource part. | ||||||
|  |      * | ||||||
|  |      * @param local    The local part. | ||||||
|  |      * @param domain   The domain part. | ||||||
|  |      * @param resource The resource part. | ||||||
|  |      */ | ||||||
|  |     FullJid(CharSequence local, CharSequence domain, CharSequence resource) { | ||||||
|  |         this(local, domain, resource, false, null); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private FullJid(final CharSequence local, final CharSequence domain, final CharSequence resource, final boolean doUnescape, Jid bareJid) { | ||||||
|  |         final String enforcedLocalPart; | ||||||
|  |         final String enforcedDomainPart; | ||||||
|  |         final String enforcedResource; | ||||||
|  | 
 | ||||||
|  |         final String unescapedLocalPart; | ||||||
|  | 
 | ||||||
|  |         if (domain == null) { | ||||||
|  |             throw new NullPointerException(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (doUnescape) { | ||||||
|  |             unescapedLocalPart = unescape(local); | ||||||
|  |         } else { | ||||||
|  |             unescapedLocalPart = local != null ? local.toString() : null; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Escape the local part, so that disallowed characters like the space characters pass the UsernameCaseMapped profile. | ||||||
|  |         final String escapedLocalPart = escape(unescapedLocalPart); | ||||||
|  | 
 | ||||||
|  |         // If the domainpart includes a final character considered to be a label | ||||||
|  |         // separator (dot) by [RFC1034], this character MUST be stripped from | ||||||
|  |         // the domainpart before the JID of which it is a part is used for the | ||||||
|  |         // purpose of routing an XML stanza, comparing against another JID, or | ||||||
|  |         // constructing an XMPP URI or IRI [RFC5122].  In particular, such a | ||||||
|  |         // character MUST be stripped before any other canonicalization steps | ||||||
|  |         // are taken. | ||||||
|  |         // Also validate, that the domain name can be converted to ASCII, i.e. validate the domain name (e.g. must not start with "_"). | ||||||
|  |         final String strDomain = IDN.toASCII(LABEL_SEPARATOR_FINAL.matcher(domain).replaceAll(""), IDN.USE_STD3_ASCII_RULES); | ||||||
|  |         enforcedLocalPart = escapedLocalPart != null ? PrecisProfiles.USERNAME_CASE_MAPPED.enforce(escapedLocalPart) : null; | ||||||
|  |         enforcedResource = resource != null ? PrecisProfiles.OPAQUE_STRING.enforce(resource) : null; | ||||||
|  |         // See https://tools.ietf.org/html/rfc5895#section-2 | ||||||
|  |         enforcedDomainPart = IDN_PROFILE.enforce(strDomain); | ||||||
|  | 
 | ||||||
|  |         validateLength(enforcedLocalPart, "local"); | ||||||
|  |         validateLength(enforcedResource, "resource"); | ||||||
|  |         validateDomain(strDomain); | ||||||
|  | 
 | ||||||
|  |         this.local = unescape(enforcedLocalPart); | ||||||
|  |         this.escapedLocal = enforcedLocalPart; | ||||||
|  |         this.domain = enforcedDomainPart; | ||||||
|  |         this.resource = enforcedResource; | ||||||
|  |         if (bareJid != null) { | ||||||
|  |             this.bareJid = bareJid; | ||||||
|  |         } else { | ||||||
|  |             this.bareJid = isBareJid() ? this : new AbstractJid() { | ||||||
|  | 
 | ||||||
|  |                 @Override | ||||||
|  |                 public Jid asBareJid() { | ||||||
|  |                     return this; | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 @Override | ||||||
|  |                 public Jid withLocal(CharSequence local) { | ||||||
|  |                     if (local == this.getLocal() || local != null && local.equals(this.getLocal())) { | ||||||
|  |                         return this; | ||||||
|  |                     } | ||||||
|  |                     return new FullJid(local, getDomain(), getResource(), false, null); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 @Override | ||||||
|  |                 public Jid withResource(CharSequence resource) { | ||||||
|  |                     if (resource == this.getResource() || resource != null && resource.equals(this.getResource())) { | ||||||
|  |                         return this; | ||||||
|  |                     } | ||||||
|  |                     return new FullJid(getLocal(), getDomain(), resource, false, asBareJid()); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 @Override | ||||||
|  |                 public Jid atSubdomain(CharSequence subdomain) { | ||||||
|  |                     if (subdomain == null) { | ||||||
|  |                         throw new NullPointerException(); | ||||||
|  |                     } | ||||||
|  |                     return new FullJid(getLocal(), subdomain + "." + getDomain(), getResource(), false, null); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 @Override | ||||||
|  |                 public String getLocal() { | ||||||
|  |                     return FullJid.this.getLocal(); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 @Override | ||||||
|  |                 public String getEscapedLocal() { | ||||||
|  |                     return FullJid.this.getEscapedLocal(); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 @Override | ||||||
|  |                 public String getDomain() { | ||||||
|  |                     return FullJid.this.getDomain(); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 @Override | ||||||
|  |                 public String getResource() { | ||||||
|  |                     return null; | ||||||
|  |                 } | ||||||
|  |             }; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Creates a JID from a string. The format must be | ||||||
|  |      * <blockquote><p>[ localpart "@" ] domainpart [ "/" resourcepart ]</p></blockquote> | ||||||
|  |      * | ||||||
|  |      * @param jid        The JID. | ||||||
|  |      * @param doUnescape If the jid parameter will be unescaped. | ||||||
|  |      * @return The JID. | ||||||
|  |      * @throws NullPointerException     If the jid is null. | ||||||
|  |      * @throws IllegalArgumentException If the jid could not be parsed or is not valid. | ||||||
|  |      * @see <a href="https://xmpp.org/extensions/xep-0106.html">XEP-0106: JID Escaping</a> | ||||||
|  |      */ | ||||||
|  |     static Jid of(String jid, final boolean doUnescape) { | ||||||
|  |         if (jid == null) { | ||||||
|  |             throw new NullPointerException("jid must not be null."); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         jid = jid.trim(); | ||||||
|  | 
 | ||||||
|  |         if (jid.isEmpty()) { | ||||||
|  |             throw new IllegalArgumentException("jid must not be empty."); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         Jid result; | ||||||
|  |         if (doUnescape) { | ||||||
|  |             result = UNESCAPED_CACHE.get(jid); | ||||||
|  |         } else { | ||||||
|  |             result = ESCAPED_CACHE.get(jid); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (result != null) { | ||||||
|  |             return result; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         Matcher matcher = JID.matcher(jid); | ||||||
|  |         if (matcher.matches()) { | ||||||
|  |             Jid jidValue = new FullJid(matcher.group(2), matcher.group(3), matcher.group(5), doUnescape, null); | ||||||
|  |             if (doUnescape) { | ||||||
|  |                 UNESCAPED_CACHE.put(jid, jidValue); | ||||||
|  |             } else { | ||||||
|  |                 ESCAPED_CACHE.put(jid, jidValue); | ||||||
|  |             } | ||||||
|  |             return jidValue; | ||||||
|  |         } else { | ||||||
|  |             throw new IllegalArgumentException("Could not parse JID: " + jid); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Escapes a local part. The characters {@code "&'/:<>@} (+ whitespace) are replaced with their respective escape characters. | ||||||
|  |      * | ||||||
|  |      * @param localPart The local part. | ||||||
|  |      * @return The escaped local part or null. | ||||||
|  |      * @see <a href="https://xmpp.org/extensions/xep-0106.html">XEP-0106: JID Escaping</a> | ||||||
|  |      */ | ||||||
|  |     private static String escape(final CharSequence localPart) { | ||||||
|  |         if (localPart != null) { | ||||||
|  |             final Matcher matcher = ESCAPE_PATTERN.matcher(localPart); | ||||||
|  |             final StringBuffer sb = new StringBuffer(); | ||||||
|  |             while (matcher.find()) { | ||||||
|  |                 matcher.appendReplacement(sb, "\\\\" + Integer.toHexString(matcher.group().charAt(0))); | ||||||
|  |             } | ||||||
|  |             matcher.appendTail(sb); | ||||||
|  |             return sb.toString(); | ||||||
|  |         } | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private static String unescape(final CharSequence localPart) { | ||||||
|  |         if (localPart != null) { | ||||||
|  |             final Matcher matcher = UNESCAPE_PATTERN.matcher(localPart); | ||||||
|  |             final StringBuffer sb = new StringBuffer(); | ||||||
|  |             while (matcher.find()) { | ||||||
|  |                 final char c = (char) Integer.parseInt(matcher.group(1), 16); | ||||||
|  |                 if (c == '\\') { | ||||||
|  |                     matcher.appendReplacement(sb, "\\\\"); | ||||||
|  |                 } else { | ||||||
|  |                     matcher.appendReplacement(sb, String.valueOf(c)); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             matcher.appendTail(sb); | ||||||
|  |             return sb.toString(); | ||||||
|  |         } | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private static void validateDomain(String domain) { | ||||||
|  |         if (domain == null) { | ||||||
|  |             throw new NullPointerException("domain must not be null."); | ||||||
|  |         } | ||||||
|  |         if (domain.contains("@")) { | ||||||
|  |             // Prevent misuse of API. | ||||||
|  |             throw new IllegalArgumentException("domain must not contain a '@' sign"); | ||||||
|  |         } | ||||||
|  |         validateLength(domain, "domain"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Validates that the length of a local, domain or resource part is not longer than 1023 characters. | ||||||
|  |      * | ||||||
|  |      * @param value The value. | ||||||
|  |      * @param part  The part, only used to produce an exception message. | ||||||
|  |      */ | ||||||
|  |     private static void validateLength(CharSequence value, CharSequence part) { | ||||||
|  |         if (value != null) { | ||||||
|  |             if (value.length() == 0) { | ||||||
|  |                 throw new IllegalArgumentException(part + " must not be empty."); | ||||||
|  |             } | ||||||
|  |             if (value.toString().getBytes(Charset.forName("UTF-8")).length > 1023) { | ||||||
|  |                 throw new IllegalArgumentException(part + " must not be greater than 1023 bytes."); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Converts this JID into a bare JID, i.e. removes the resource part. | ||||||
|  |      * <blockquote> | ||||||
|  |      * <p>The term "bare JID" refers to an XMPP address of the form <localpart@domainpart> (for an account at a server) or of the form <domainpart> (for a server).</p> | ||||||
|  |      * </blockquote> | ||||||
|  |      * | ||||||
|  |      * @return The bare JID. | ||||||
|  |      * @see #withResource(CharSequence) | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public final Jid asBareJid() { | ||||||
|  |         return bareJid; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Gets the local part of the JID, also known as the name or node. | ||||||
|  |      * <blockquote> | ||||||
|  |      * <p><cite><a href="https://tools.ietf.org/html/rfc7622#section-3.3">3.3.  Localpart</a></cite></p> | ||||||
|  |      * <p>The localpart of a JID is an optional identifier placed before the | ||||||
|  |      * domainpart and separated from the latter by the '@' character. | ||||||
|  |      * Typically, a localpart uniquely identifies the entity requesting and | ||||||
|  |      * using network access provided by a server (i.e., a local account), | ||||||
|  |      * although it can also represent other kinds of entities (e.g., a | ||||||
|  |      * chatroom associated with a multi-user chat service [XEP-0045]).  The | ||||||
|  |      * entity represented by an XMPP localpart is addressed within the | ||||||
|  |      * context of a specific domain (i.e., <localpart@domainpart>).</p> | ||||||
|  |      * </blockquote> | ||||||
|  |      * | ||||||
|  |      * @return The local part or null. | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public final String getLocal() { | ||||||
|  |         return local; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public final String getEscapedLocal() { | ||||||
|  |         return escapedLocal; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Gets the domain part. | ||||||
|  |      * <blockquote> | ||||||
|  |      * <p><cite><a href="https://tools.ietf.org/html/rfc7622#section-3.2">3.2.  Domainpart</a></cite></p> | ||||||
|  |      * <p>The domainpart is the primary identifier and is the only REQUIRED | ||||||
|  |      * element of a JID (a mere domainpart is a valid JID).  Typically, | ||||||
|  |      * a domainpart identifies the "home" server to which clients connect | ||||||
|  |      * for XML routing and data management functionality.</p> | ||||||
|  |      * </blockquote> | ||||||
|  |      * | ||||||
|  |      * @return The domain part. | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public final String getDomain() { | ||||||
|  |         return domain; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Gets the resource part. | ||||||
|  |      * <blockquote> | ||||||
|  |      * <p><cite><a href="https://tools.ietf.org/html/rfc7622#section-3.4">3.4.  Resourcepart</a></cite></p> | ||||||
|  |      * <p>The resourcepart of a JID is an optional identifier placed after the | ||||||
|  |      * domainpart and separated from the latter by the '/' character.  A | ||||||
|  |      * resourcepart can modify either a <localpart@domainpart> address or a | ||||||
|  |      * mere <domainpart> address.  Typically, a resourcepart uniquely | ||||||
|  |      * identifies a specific connection (e.g., a device or location) or | ||||||
|  |      * object (e.g., an occupant in a multi-user chatroom [XEP-0045]) | ||||||
|  |      * belonging to the entity associated with an XMPP localpart at a domain | ||||||
|  |      * (i.e., <localpart@domainpart/resourcepart>).</p> | ||||||
|  |      * </blockquote> | ||||||
|  |      * | ||||||
|  |      * @return The resource part or null. | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public final String getResource() { | ||||||
|  |         return resource; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Creates a new JID with a new local part and the same domain and resource part of the current JID. | ||||||
|  |      * | ||||||
|  |      * @param local The local part. | ||||||
|  |      * @return The JID with a new local part. | ||||||
|  |      * @throws IllegalArgumentException If the local is not a valid local part. | ||||||
|  |      * @see #withResource(CharSequence) | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public final Jid withLocal(CharSequence local) { | ||||||
|  |         if (local == this.getLocal() || local != null && local.equals(this.getLocal())) { | ||||||
|  |             return this; | ||||||
|  |         } | ||||||
|  |         return new FullJid(local, getDomain(), getResource(), false, null); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Creates a new full JID with a resource and the same local and domain part of the current JID. | ||||||
|  |      * | ||||||
|  |      * @param resource The resource. | ||||||
|  |      * @return The full JID with a resource. | ||||||
|  |      * @throws IllegalArgumentException If the resource is not a valid resource part. | ||||||
|  |      * @see #asBareJid() | ||||||
|  |      * @see #withLocal(CharSequence) | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public final Jid withResource(CharSequence resource) { | ||||||
|  |         if (resource == this.getResource() || resource != null && resource.equals(this.getResource())) { | ||||||
|  |             return this; | ||||||
|  |         } | ||||||
|  |         return new FullJid(getLocal(), getDomain(), resource, false, asBareJid()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Creates a new JID at a subdomain and at the same domain as this JID. | ||||||
|  |      * | ||||||
|  |      * @param subdomain The subdomain. | ||||||
|  |      * @return The JID at a subdomain. | ||||||
|  |      * @throws NullPointerException     If subdomain is null. | ||||||
|  |      * @throws IllegalArgumentException If subdomain is not a valid subdomain name. | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public final Jid atSubdomain(CharSequence subdomain) { | ||||||
|  |         if (subdomain != null) { | ||||||
|  |             throw new NullPointerException(); | ||||||
|  |         } | ||||||
|  |         return new FullJid(getLocal(), subdomain + "." + getDomain(), getResource(), false, null); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * A profile for applying the rules for IDN as in RFC 5895. Although IDN doesn't use Precis, it's still very similar so that we can use the base class. | ||||||
|  |      * | ||||||
|  |      * @see <a href="https://tools.ietf.org/html/rfc5895#section-2">RFC 5895</a> | ||||||
|  |      */ | ||||||
|  |     private static final class IDNProfile extends PrecisProfile { | ||||||
|  | 
 | ||||||
|  |         private IDNProfile() { | ||||||
|  |             super(false); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         @Override | ||||||
|  |         public String prepare(CharSequence input) { | ||||||
|  |             return IDN.toUnicode(input.toString(), IDN.USE_STD3_ASCII_RULES); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         @Override | ||||||
|  |         public String enforce(CharSequence input) { | ||||||
|  |             // 4. Map IDEOGRAPHIC FULL STOP character (U+3002) to dot. | ||||||
|  |             return applyAdditionalMappingRule( | ||||||
|  |                     // 3.  All characters are mapped using Unicode Normalization Form C (NFC). | ||||||
|  |                     applyNormalizationRule( | ||||||
|  |                             // 2. Fullwidth and halfwidth characters (those defined with | ||||||
|  |                             // Decomposition Types <wide> and <narrow>) are mapped to their | ||||||
|  |                             // decomposition mappings | ||||||
|  |                             applyWidthMappingRule( | ||||||
|  |                                     // 1. Uppercase characters are mapped to their lowercase equivalents | ||||||
|  |                                     applyCaseMappingRule(prepare(input))))).toString(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         @Override | ||||||
|  |         protected CharSequence applyWidthMappingRule(CharSequence charSequence) { | ||||||
|  |             return widthMap(charSequence); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         @Override | ||||||
|  |         protected CharSequence applyAdditionalMappingRule(CharSequence charSequence) { | ||||||
|  |             return LABEL_SEPARATOR.matcher(charSequence).replaceAll("."); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         @Override | ||||||
|  |         protected CharSequence applyCaseMappingRule(CharSequence charSequence) { | ||||||
|  |             return charSequence.toString().toLowerCase(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         @Override | ||||||
|  |         protected CharSequence applyNormalizationRule(CharSequence charSequence) { | ||||||
|  |             return Normalizer.normalize(charSequence, Normalizer.Form.NFC); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         @Override | ||||||
|  |         protected CharSequence applyDirectionalityRule(CharSequence charSequence) { | ||||||
|  |             return charSequence; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										314
									
								
								libs/xmpp-addr/src/main/java/rocks/xmpp/addr/Jid.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										314
									
								
								libs/xmpp-addr/src/main/java/rocks/xmpp/addr/Jid.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,314 @@ | |||||||
|  | /* | ||||||
|  |  * The MIT License (MIT) | ||||||
|  |  * | ||||||
|  |  * Copyright (c) 2014-2017 Christian Schudt | ||||||
|  |  * | ||||||
|  |  * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||||
|  |  * of this software and associated documentation files (the "Software"), to deal | ||||||
|  |  * in the Software without restriction, including without limitation the rights | ||||||
|  |  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||||
|  |  * copies of the Software, and to permit persons to whom the Software is | ||||||
|  |  * furnished to do so, subject to the following conditions: | ||||||
|  |  * | ||||||
|  |  * The above copyright notice and this permission notice shall be included in | ||||||
|  |  * all copies or substantial portions of the Software. | ||||||
|  |  * | ||||||
|  |  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||||
|  |  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||||
|  |  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||||
|  |  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||||
|  |  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||||
|  |  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||||
|  |  * THE SOFTWARE. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | package rocks.xmpp.addr; | ||||||
|  | 
 | ||||||
|  | import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; | ||||||
|  | import java.io.Serializable; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Represents the JID as described in <a href="https://tools.ietf.org/html/rfc7622">Extensible Messaging and Presence Protocol (XMPP): Address Format</a>. | ||||||
|  |  * <p> | ||||||
|  |  * A JID consists of three parts: | ||||||
|  |  * <p> | ||||||
|  |  * [ localpart "@" ] domainpart [ "/" resourcepart ] | ||||||
|  |  * </p> | ||||||
|  |  * The easiest way to create a JID is to use the {@link #of(CharSequence)} method: | ||||||
|  |  * ```java | ||||||
|  |  * Jid jid = Jid.of("juliet@capulet.lit/balcony"); | ||||||
|  |  * ``` | ||||||
|  |  * You can then get the parts from it via the respective methods: | ||||||
|  |  * ```java | ||||||
|  |  * String local = jid.getLocal(); // juliet | ||||||
|  |  * String domain = jid.getDomain(); // capulet.lit | ||||||
|  |  * String resource = jid.getResource(); // balcony | ||||||
|  |  * ``` | ||||||
|  |  * Implementations of this interface should override <code>equals()</code> and <code>hashCode()</code>, so that different instances with the same value are equal: | ||||||
|  |  * ```java | ||||||
|  |  * Jid.of("romeo@capulet.lit/balcony").equals(Jid.of("romeo@capulet.lit/balcony")); // true | ||||||
|  |  * ``` | ||||||
|  |  * The default implementation of this class also supports <a href="https://xmpp.org/extensions/xep-0106.html">XEP-0106: JID Escaping</a>, i.e. | ||||||
|  |  * ```java | ||||||
|  |  * Jid.of("d'artagnan@musketeers.lit") | ||||||
|  |  * ``` | ||||||
|  |  * is escaped as <code>d\\27artagnan@musketeers.lit</code>. | ||||||
|  |  * <p> | ||||||
|  |  * Implementations of this interface should be thread-safe and immutable. | ||||||
|  |  * | ||||||
|  |  * @author Christian Schudt | ||||||
|  |  * @see <a href="https://tools.ietf.org/html/rfc7622">RFC 7622 - Extensible Messaging and Presence Protocol (XMPP): Address Format</a> | ||||||
|  |  */ | ||||||
|  | @XmlJavaTypeAdapter(JidAdapter.class) | ||||||
|  | public interface Jid extends Comparable<Jid>, Serializable, CharSequence { | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * The maximal length of a full JID, which is 3071. | ||||||
|  |      * <blockquote> | ||||||
|  |      * <p><cite><a href="https://tools.ietf.org/html/rfc7622#section-3.1">3.1.  Fundamentals</a></cite></p> | ||||||
|  |      * <p>Each allowable portion of a JID (localpart, domainpart, and | ||||||
|  |      * resourcepart) is 1 to 1023 octets in length, resulting in a maximum | ||||||
|  |      * total size (including the '@' and '/' separators) of 3071 octets. | ||||||
|  |      * </p> | ||||||
|  |      * </blockquote> | ||||||
|  |      * Note that the length is based on bytes, not characters. | ||||||
|  |      * | ||||||
|  |      * @see #MAX_BARE_JID_LENGTH | ||||||
|  |      */ | ||||||
|  |     int MAX_FULL_JID_LENGTH = 3071; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * The maximal length of a bare JID, which is 2047 (1023 + 1 + 1023). | ||||||
|  |      * Note that the length is based on bytes, not characters. | ||||||
|  |      * | ||||||
|  |      * @see #MAX_FULL_JID_LENGTH | ||||||
|  |      */ | ||||||
|  |     int MAX_BARE_JID_LENGTH = 2047; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * The service discovery feature used for determining support of JID escaping (<code>jid\20escaping</code>). | ||||||
|  |      */ | ||||||
|  |     String ESCAPING_FEATURE = "jid\\20escaping"; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Returns a full JID with a domain and resource part, e.g. <code>capulet.com/balcony</code> | ||||||
|  |      * | ||||||
|  |      * @param local    The local part. | ||||||
|  |      * @param domain   The domain. | ||||||
|  |      * @param resource The resource part. | ||||||
|  |      * @return The JID. | ||||||
|  |      * @throws NullPointerException     If the domain is null. | ||||||
|  |      * @throws IllegalArgumentException If the domain, local or resource part are not valid. | ||||||
|  |      */ | ||||||
|  |     static Jid of(CharSequence local, CharSequence domain, CharSequence resource) { | ||||||
|  |         return new FullJid(local, domain, resource); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Creates a bare JID with only the domain part, e.g. <code>capulet.com</code> | ||||||
|  |      * | ||||||
|  |      * @param domain The domain. | ||||||
|  |      * @return The JID. | ||||||
|  |      * @throws NullPointerException     If the domain is null. | ||||||
|  |      * @throws IllegalArgumentException If the domain or local part are not valid. | ||||||
|  |      */ | ||||||
|  |     static Jid ofDomain(CharSequence domain) { | ||||||
|  |         return new FullJid(null, domain, null); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Creates a bare JID with a local and domain part, e.g. <code>juliet@capulet.com</code> | ||||||
|  |      * | ||||||
|  |      * @param local  The local part. | ||||||
|  |      * @param domain The domain. | ||||||
|  |      * @return The JID. | ||||||
|  |      * @throws NullPointerException     If the domain is null. | ||||||
|  |      * @throws IllegalArgumentException If the domain or local part are not valid. | ||||||
|  |      */ | ||||||
|  |     static Jid ofLocalAndDomain(CharSequence local, CharSequence domain) { | ||||||
|  |         return new FullJid(local, domain, null); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Creates a full JID with a domain and resource part, e.g. <code>capulet.com/balcony</code> | ||||||
|  |      * | ||||||
|  |      * @param domain   The domain. | ||||||
|  |      * @param resource The resource part. | ||||||
|  |      * @return The JID. | ||||||
|  |      * @throws NullPointerException     If the domain is null. | ||||||
|  |      * @throws IllegalArgumentException If the domain or resource are not valid. | ||||||
|  |      */ | ||||||
|  |     static Jid ofDomainAndResource(CharSequence domain, CharSequence resource) { | ||||||
|  |         return new FullJid(null, domain, resource); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Creates a JID from an unescaped string. The format must be | ||||||
|  |      * <blockquote><p>[ localpart "@" ] domainpart [ "/" resourcepart ]</p></blockquote> | ||||||
|  |      * The input string will be escaped. | ||||||
|  |      * | ||||||
|  |      * @param jid The JID. | ||||||
|  |      * @return The JID. | ||||||
|  |      * @throws NullPointerException     If the jid is null. | ||||||
|  |      * @throws IllegalArgumentException If the jid could not be parsed or is not valid. | ||||||
|  |      * @see <a href="https://xmpp.org/extensions/xep-0106.html">XEP-0106: JID Escaping</a> | ||||||
|  |      */ | ||||||
|  |     static Jid of(CharSequence jid) { | ||||||
|  |         if (jid instanceof Jid) { | ||||||
|  |             return (Jid) jid; | ||||||
|  |         } | ||||||
|  |         return FullJid.of(jid.toString(), false); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Creates a JID from a escaped JID string. The format must be | ||||||
|  |      * <blockquote><p>[ localpart "@" ] domainpart [ "/" resourcepart ]</p></blockquote> | ||||||
|  |      * This method should be used, when parsing JIDs from the XMPP stream. | ||||||
|  |      * | ||||||
|  |      * @param jid The JID. | ||||||
|  |      * @return The JID. | ||||||
|  |      * @throws NullPointerException     If the jid is null. | ||||||
|  |      * @throws IllegalArgumentException If the jid could not be parsed or is not valid. | ||||||
|  |      * @see <a href="https://xmpp.org/extensions/xep-0106.html">XEP-0106: JID Escaping</a> | ||||||
|  |      */ | ||||||
|  |     static Jid ofEscaped(CharSequence jid) { | ||||||
|  |         return FullJid.of(jid.toString(), true); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Checks if the JID is a full JID. | ||||||
|  |      * <blockquote> | ||||||
|  |      * <p>The term "full JID" refers to an XMPP address of the form <localpart@domainpart/resourcepart> (for a particular authorized client or device associated with an account) or of the form <domainpart/resourcepart> (for a particular resource or script associated with a server).</p> | ||||||
|  |      * </blockquote> | ||||||
|  |      * | ||||||
|  |      * @return True, if the JID is a full JID; otherwise false. | ||||||
|  |      */ | ||||||
|  |     boolean isFullJid(); | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Checks if the JID is a bare JID. | ||||||
|  |      * <blockquote> | ||||||
|  |      * <p>The term "bare JID" refers to an XMPP address of the form <localpart@domainpart> (for an account at a server) or of the form <domainpart> (for a server).</p> | ||||||
|  |      * </blockquote> | ||||||
|  |      * | ||||||
|  |      * @return True, if the JID is a bare JID; otherwise false. | ||||||
|  |      */ | ||||||
|  |     boolean isBareJid(); | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Checks if the JID is a domain JID, i.e. if it has no local part. | ||||||
|  |      * | ||||||
|  |      * @return True, if the JID is a domain JID, i.e. if it has no local part. | ||||||
|  |      */ | ||||||
|  |     boolean isDomainJid(); | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Gets the bare JID representation of this JID, i.e. removes the resource part. | ||||||
|  |      * <blockquote> | ||||||
|  |      * <p>The term "bare JID" refers to an XMPP address of the form <localpart@domainpart> (for an account at a server) or of the form <domainpart> (for a server).</p> | ||||||
|  |      * </blockquote> | ||||||
|  |      * | ||||||
|  |      * @return The bare JID. | ||||||
|  |      * @see #withResource(CharSequence) | ||||||
|  |      */ | ||||||
|  |     Jid asBareJid(); | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Creates a new JID with a new local part and the same domain and resource part of the current JID. | ||||||
|  |      * | ||||||
|  |      * @param local The local part. | ||||||
|  |      * @return The JID with a new local part. | ||||||
|  |      * @throws IllegalArgumentException If the local is not a valid local part. | ||||||
|  |      * @see #withResource(CharSequence) | ||||||
|  |      */ | ||||||
|  |     Jid withLocal(CharSequence local); | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Creates a new full JID with a resource and the same local and domain part of the current JID. | ||||||
|  |      * | ||||||
|  |      * @param resource The resource. | ||||||
|  |      * @return The full JID with a resource. | ||||||
|  |      * @throws IllegalArgumentException If the resource is not a valid resource part. | ||||||
|  |      * @see #asBareJid() | ||||||
|  |      * @see #withLocal(CharSequence) | ||||||
|  |      */ | ||||||
|  |     Jid withResource(CharSequence resource); | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Creates a new JID at a subdomain and at the same domain as this JID. | ||||||
|  |      * | ||||||
|  |      * @param subdomain The subdomain. | ||||||
|  |      * @return The JID at a subdomain. | ||||||
|  |      * @throws NullPointerException     If subdomain is null. | ||||||
|  |      * @throws IllegalArgumentException If subdomain is not a valid subdomain name. | ||||||
|  |      */ | ||||||
|  |     Jid atSubdomain(CharSequence subdomain); | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Gets the local part of the JID, also known as the name or node. | ||||||
|  |      * <blockquote> | ||||||
|  |      * <p><cite><a href="https://tools.ietf.org/html/rfc7622#section-3.3">3.3.  Localpart</a></cite></p> | ||||||
|  |      * <p>The localpart of a JID is an optional identifier placed before the | ||||||
|  |      * domainpart and separated from the latter by the '@' character. | ||||||
|  |      * Typically, a localpart uniquely identifies the entity requesting and | ||||||
|  |      * using network access provided by a server (i.e., a local account), | ||||||
|  |      * although it can also represent other kinds of entities (e.g., a | ||||||
|  |      * chatroom associated with a multi-user chat service [XEP-0045]).  The | ||||||
|  |      * entity represented by an XMPP localpart is addressed within the | ||||||
|  |      * context of a specific domain (i.e., <localpart@domainpart>).</p> | ||||||
|  |      * </blockquote> | ||||||
|  |      * | ||||||
|  |      * @return The local part or null. | ||||||
|  |      * @see #getEscapedLocal() | ||||||
|  |      */ | ||||||
|  |     String getLocal(); | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Gets the escaped local part of the JID. | ||||||
|  |      * | ||||||
|  |      * @return The escaped local part or null. | ||||||
|  |      * @see #getLocal() | ||||||
|  |      * @since 0.8.0 | ||||||
|  |      */ | ||||||
|  |     String getEscapedLocal(); | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Gets the domain part. | ||||||
|  |      * <blockquote> | ||||||
|  |      * <p><cite><a href="https://tools.ietf.org/html/rfc7622#section-3.2">3.2.  Domainpart</a></cite></p> | ||||||
|  |      * <p>The domainpart is the primary identifier and is the only REQUIRED | ||||||
|  |      * element of a JID (a mere domainpart is a valid JID).  Typically, | ||||||
|  |      * a domainpart identifies the "home" server to which clients connect | ||||||
|  |      * for XML routing and data management functionality.</p> | ||||||
|  |      * </blockquote> | ||||||
|  |      * | ||||||
|  |      * @return The domain part. | ||||||
|  |      */ | ||||||
|  |     String getDomain(); | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Gets the resource part. | ||||||
|  |      * <blockquote> | ||||||
|  |      * <p><cite><a href="https://tools.ietf.org/html/rfc7622#section-3.4">3.4.  Resourcepart</a></cite></p> | ||||||
|  |      * <p>The resourcepart of a JID is an optional identifier placed after the | ||||||
|  |      * domainpart and separated from the latter by the '/' character.  A | ||||||
|  |      * resourcepart can modify either a <localpart@domainpart> address or a | ||||||
|  |      * mere <domainpart> address.  Typically, a resourcepart uniquely | ||||||
|  |      * identifies a specific connection (e.g., a device or location) or | ||||||
|  |      * object (e.g., an occupant in a multi-user chatroom [XEP-0045]) | ||||||
|  |      * belonging to the entity associated with an XMPP localpart at a domain | ||||||
|  |      * (i.e., <localpart@domainpart/resourcepart>).</p> | ||||||
|  |      * </blockquote> | ||||||
|  |      * | ||||||
|  |      * @return The resource part or null. | ||||||
|  |      */ | ||||||
|  |     String getResource(); | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Returns the JID in escaped form as described in <a href="https://xmpp.org/extensions/xep-0106.html">XEP-0106: JID Escaping</a>. | ||||||
|  |      * | ||||||
|  |      * @return The escaped JID. | ||||||
|  |      * @see #toString() | ||||||
|  |      */ | ||||||
|  |     String toEscapedString(); | ||||||
|  | } | ||||||
							
								
								
									
										53
									
								
								libs/xmpp-addr/src/main/java/rocks/xmpp/addr/JidAdapter.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								libs/xmpp-addr/src/main/java/rocks/xmpp/addr/JidAdapter.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,53 @@ | |||||||
|  | /* | ||||||
|  |  * The MIT License (MIT) | ||||||
|  |  * | ||||||
|  |  * Copyright (c) 2014-2016 Christian Schudt | ||||||
|  |  * | ||||||
|  |  * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||||
|  |  * of this software and associated documentation files (the "Software"), to deal | ||||||
|  |  * in the Software without restriction, including without limitation the rights | ||||||
|  |  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||||
|  |  * copies of the Software, and to permit persons to whom the Software is | ||||||
|  |  * furnished to do so, subject to the following conditions: | ||||||
|  |  * | ||||||
|  |  * The above copyright notice and this permission notice shall be included in | ||||||
|  |  * all copies or substantial portions of the Software. | ||||||
|  |  * | ||||||
|  |  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||||
|  |  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||||
|  |  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||||
|  |  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||||
|  |  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||||
|  |  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||||
|  |  * THE SOFTWARE. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | package rocks.xmpp.addr; | ||||||
|  | 
 | ||||||
|  | import javax.xml.bind.annotation.adapters.XmlAdapter; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Converts a String representation of a JID to JID object and vice a versa. | ||||||
|  |  */ | ||||||
|  | final class JidAdapter extends XmlAdapter<String, Jid> { | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public Jid unmarshal(String v) { | ||||||
|  |         if (v != null) { | ||||||
|  |             try { | ||||||
|  |                 return Jid.ofEscaped(v); | ||||||
|  |             } catch (Exception e) { | ||||||
|  |                 return MalformedJid.of(v, e); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public String marshal(Jid v) { | ||||||
|  |         if (v != null) { | ||||||
|  |             return v.toEscapedString(); | ||||||
|  |         } | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										131
									
								
								libs/xmpp-addr/src/main/java/rocks/xmpp/addr/MalformedJid.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								libs/xmpp-addr/src/main/java/rocks/xmpp/addr/MalformedJid.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,131 @@ | |||||||
|  | /* | ||||||
|  |  * The MIT License (MIT) | ||||||
|  |  * | ||||||
|  |  * Copyright (c) 2014-2017 Christian Schudt | ||||||
|  |  * | ||||||
|  |  * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||||
|  |  * of this software and associated documentation files (the "Software"), to deal | ||||||
|  |  * in the Software without restriction, including without limitation the rights | ||||||
|  |  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||||
|  |  * copies of the Software, and to permit persons to whom the Software is | ||||||
|  |  * furnished to do so, subject to the following conditions: | ||||||
|  |  * | ||||||
|  |  * The above copyright notice and this permission notice shall be included in | ||||||
|  |  * all copies or substantial portions of the Software. | ||||||
|  |  * | ||||||
|  |  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||||
|  |  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||||
|  |  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||||
|  |  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||||
|  |  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||||
|  |  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||||
|  |  * THE SOFTWARE. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | package rocks.xmpp.addr; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Represents a malformed JID in order to handle the <code>jid-malformed</code> error. | ||||||
|  |  * <p> | ||||||
|  |  * This class is not intended to be publicly instantiable, but is used for malformed JIDs during parsing automatically. | ||||||
|  |  * | ||||||
|  |  * @author Christian Schudt | ||||||
|  |  * @see <a href="https://xmpp.org/rfcs/rfc6120.html#stanzas-error-conditions-jid-malformed">RFC 6120, 8.3.3.8.  jid-malformed</a> | ||||||
|  |  */ | ||||||
|  | public final class MalformedJid extends AbstractJid { | ||||||
|  | 
 | ||||||
|  |     private static final long serialVersionUID = -2896737611021417985L; | ||||||
|  | 
 | ||||||
|  |     private final String localPart; | ||||||
|  | 
 | ||||||
|  |     private final String domainPart; | ||||||
|  | 
 | ||||||
|  |     private final String resourcePart; | ||||||
|  | 
 | ||||||
|  |     private final Throwable cause; | ||||||
|  | 
 | ||||||
|  |     static MalformedJid of(final String jid, final Throwable cause) { | ||||||
|  |         // Do some basic parsing without any further checks or validation. | ||||||
|  |         final StringBuilder sb = new StringBuilder(jid); | ||||||
|  |         // 1.  Remove any portion from the first '/' character to the end of the | ||||||
|  |         // string (if there is a '/' character present). | ||||||
|  |         final int indexOfResourceDelimiter = jid.indexOf('/'); | ||||||
|  |         final String resourcePart; | ||||||
|  |         if (indexOfResourceDelimiter > -1) { | ||||||
|  |             resourcePart = sb.substring(indexOfResourceDelimiter + 1); | ||||||
|  |             sb.delete(indexOfResourceDelimiter, sb.length()); | ||||||
|  |         } else { | ||||||
|  |             resourcePart = null; | ||||||
|  |         } | ||||||
|  |         // 2.  Remove any portion from the beginning of the string to the first | ||||||
|  |         // '@' character (if there is an '@' character present). | ||||||
|  |         final int indexOfAt = jid.indexOf('@'); | ||||||
|  |         final String localPart; | ||||||
|  |         if (indexOfAt > -1) { | ||||||
|  |             localPart = sb.substring(0, indexOfAt); | ||||||
|  |             sb.delete(0, indexOfAt + 1); | ||||||
|  |         } else { | ||||||
|  |             localPart = null; | ||||||
|  |         } | ||||||
|  |         return new MalformedJid(localPart, sb.toString(), resourcePart, cause); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private MalformedJid(final String localPart, final String domainPart, final String resourcePart, final Throwable cause) { | ||||||
|  |         this.localPart = localPart; | ||||||
|  |         this.domainPart = domainPart; | ||||||
|  |         this.resourcePart = resourcePart; | ||||||
|  |         this.cause = cause; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public final Jid asBareJid() { | ||||||
|  |         return new MalformedJid(localPart, domainPart, null, cause); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public Jid withLocal(CharSequence local) { | ||||||
|  |         return new MalformedJid(local.toString(), domainPart, resourcePart, cause); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public Jid withResource(CharSequence resource) { | ||||||
|  |         return new MalformedJid(localPart, domainPart, resource.toString(), cause); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public Jid atSubdomain(CharSequence subdomain) { | ||||||
|  |         if (subdomain == null) { | ||||||
|  |             throw new NullPointerException(); | ||||||
|  |         } | ||||||
|  |         return new MalformedJid(localPart, subdomain + "." + domainPart, resourcePart, cause); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public final String getLocal() { | ||||||
|  |         return localPart; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public final String getEscapedLocal() { | ||||||
|  |         return localPart; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public final String getDomain() { | ||||||
|  |         return domainPart; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public final String getResource() { | ||||||
|  |         return resourcePart; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Gets the cause why the JID is malformed. | ||||||
|  |      * | ||||||
|  |      * @return The cause. | ||||||
|  |      */ | ||||||
|  |     public final Throwable getCause() { | ||||||
|  |         return cause; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,31 @@ | |||||||
|  | /* | ||||||
|  |  * The MIT License (MIT) | ||||||
|  |  * | ||||||
|  |  * Copyright (c) 2014-2016 Christian Schudt | ||||||
|  |  * | ||||||
|  |  * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||||
|  |  * of this software and associated documentation files (the "Software"), to deal | ||||||
|  |  * in the Software without restriction, including without limitation the rights | ||||||
|  |  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||||
|  |  * copies of the Software, and to permit persons to whom the Software is | ||||||
|  |  * furnished to do so, subject to the following conditions: | ||||||
|  |  * | ||||||
|  |  * The above copyright notice and this permission notice shall be included in | ||||||
|  |  * all copies or substantial portions of the Software. | ||||||
|  |  * | ||||||
|  |  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||||
|  |  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||||
|  |  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||||
|  |  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||||
|  |  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||||
|  |  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||||
|  |  * THE SOFTWARE. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Provides classes for the <a href="https://tools.ietf.org/html/rfc7622">XMPP Address Format</a> (JID). | ||||||
|  |  * | ||||||
|  |  * @see <a href="https://tools.ietf.org/html/rfc7622">Extensible Messaging and Presence Protocol (XMPP): Address Format</a> | ||||||
|  |  */ | ||||||
|  | package rocks.xmpp.addr; | ||||||
|  | 
 | ||||||
							
								
								
									
										192
									
								
								libs/xmpp-addr/src/main/java/rocks/xmpp/util/cache/DirectoryCache.java
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										192
									
								
								libs/xmpp-addr/src/main/java/rocks/xmpp/util/cache/DirectoryCache.java
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,192 @@ | |||||||
|  | /* | ||||||
|  |  * The MIT License (MIT) | ||||||
|  |  * | ||||||
|  |  * Copyright (c) 2014-2016 Christian Schudt | ||||||
|  |  * | ||||||
|  |  * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||||
|  |  * of this software and associated documentation files (the "Software"), to deal | ||||||
|  |  * in the Software without restriction, including without limitation the rights | ||||||
|  |  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||||
|  |  * copies of the Software, and to permit persons to whom the Software is | ||||||
|  |  * furnished to do so, subject to the following conditions: | ||||||
|  |  * | ||||||
|  |  * The above copyright notice and this permission notice shall be included in | ||||||
|  |  * all copies or substantial portions of the Software. | ||||||
|  |  * | ||||||
|  |  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||||
|  |  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||||
|  |  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||||
|  |  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||||
|  |  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||||
|  |  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||||
|  |  * THE SOFTWARE. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | package rocks.xmpp.util.cache; | ||||||
|  | 
 | ||||||
|  | import java.io.IOException; | ||||||
|  | import java.io.UncheckedIOException; | ||||||
|  | import java.nio.file.FileVisitResult; | ||||||
|  | import java.nio.file.Files; | ||||||
|  | import java.nio.file.Path; | ||||||
|  | import java.nio.file.SimpleFileVisitor; | ||||||
|  | import java.nio.file.attribute.BasicFileAttributes; | ||||||
|  | import java.util.Arrays; | ||||||
|  | import java.util.Collection; | ||||||
|  | import java.util.Collections; | ||||||
|  | import java.util.Map; | ||||||
|  | import java.util.Optional; | ||||||
|  | import java.util.Set; | ||||||
|  | import java.util.function.BiConsumer; | ||||||
|  | import java.util.function.Predicate; | ||||||
|  | import java.util.stream.Collectors; | ||||||
|  | import java.util.stream.Stream; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * A simple directory based cache for caching of persistent items like avatars or entity capabilities. | ||||||
|  |  * | ||||||
|  |  * @author Christian Schudt | ||||||
|  |  */ | ||||||
|  | public final class DirectoryCache implements Map<String, byte[]> { | ||||||
|  | 
 | ||||||
|  |     private final Path cacheDirectory; | ||||||
|  | 
 | ||||||
|  |     public DirectoryCache(Path cacheDirectory) { | ||||||
|  |         this.cacheDirectory = cacheDirectory; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public final int size() { | ||||||
|  |         try (final Stream<Path> files = cacheContent()) { | ||||||
|  |             return (int) Math.min(files.count(), Integer.MAX_VALUE); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public final boolean isEmpty() { | ||||||
|  |         try (final Stream<Path> files = cacheContent()) { | ||||||
|  |             return files.findAny().map(file -> Boolean.FALSE).orElse(Boolean.TRUE); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public final boolean containsKey(Object key) { | ||||||
|  |         return Files.exists(cacheDirectory.resolve(key.toString())); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public final boolean containsValue(Object value) { | ||||||
|  |         throw new UnsupportedOperationException(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public final byte[] get(final Object key) { | ||||||
|  |         return Optional.ofNullable(key).map(Object::toString).filter(((Predicate<String>) String::isEmpty).negate()).map(cacheDirectory::resolve).filter(Files::isReadable).map(file -> { | ||||||
|  |             try { | ||||||
|  |                 return Files.readAllBytes(file); | ||||||
|  |             } catch (IOException e) { | ||||||
|  |                 throw new UncheckedIOException(e); | ||||||
|  |             } | ||||||
|  |         }).orElse(null); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public final byte[] put(String key, byte[] value) { | ||||||
|  |         // Make sure the directory exists. | ||||||
|  |         byte[] data = get(key); | ||||||
|  |         if (!Arrays.equals(data, value)) | ||||||
|  |             try { | ||||||
|  |                 if (Files.notExists(cacheDirectory)) { | ||||||
|  |                     Files.createDirectories(cacheDirectory); | ||||||
|  |                 } | ||||||
|  |                 Path file = cacheDirectory.resolve(key); | ||||||
|  |                 Files.write(file, value); | ||||||
|  |             } catch (IOException e) { | ||||||
|  |                 throw new UncheckedIOException(e); | ||||||
|  |             } | ||||||
|  |         return data; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public final byte[] remove(Object key) { | ||||||
|  |         byte[] data = get(key); | ||||||
|  |         try { | ||||||
|  |             Files.deleteIfExists(cacheDirectory.resolve(key.toString())); | ||||||
|  |         } catch (IOException e) { | ||||||
|  |             throw new UncheckedIOException(e); | ||||||
|  |         } | ||||||
|  |         return data; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public final void putAll(Map<? extends String, ? extends byte[]> m) { | ||||||
|  |         m.forEach(this::put); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public final void clear() { | ||||||
|  |         try { | ||||||
|  |             Files.walkFileTree(cacheDirectory, new SimpleFileVisitor<Path>() { | ||||||
|  |                 @Override | ||||||
|  |                 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { | ||||||
|  |                     Files.deleteIfExists(file); | ||||||
|  |                     return FileVisitResult.CONTINUE; | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 @Override | ||||||
|  |                 public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { | ||||||
|  |                     // Don't delete the cache directory itself. | ||||||
|  |                     if (!Files.isSameFile(dir, cacheDirectory)) { | ||||||
|  |                         Files.deleteIfExists(dir); | ||||||
|  |                     } | ||||||
|  |                     return FileVisitResult.CONTINUE; | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |         } catch (IOException e) { | ||||||
|  |             throw new UncheckedIOException(e); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public final Set<String> keySet() { | ||||||
|  |         try (final Stream<Path> files = Files.list(cacheDirectory)) { | ||||||
|  |             return Collections.unmodifiableSet(files.map(Path::getFileName).map(Path::toString).collect(Collectors.toSet())); | ||||||
|  |         } catch (IOException e) { | ||||||
|  |             throw new UncheckedIOException(e); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public final Collection<byte[]> values() { | ||||||
|  |         throw new UnsupportedOperationException(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public final Set<Entry<String, byte[]>> entrySet() { | ||||||
|  |         throw new UnsupportedOperationException(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public final void forEach(final BiConsumer<? super String, ? super byte[]> action) { | ||||||
|  |         if (Files.exists(cacheDirectory)) | ||||||
|  |             try (final Stream<Path> files = cacheContent().filter(Files::isReadable)) { | ||||||
|  |                 files.forEach(file -> { | ||||||
|  |                     try { | ||||||
|  |                         action.accept(file.getFileName().toString(), Files.readAllBytes(file)); | ||||||
|  |                     } catch (final IOException e) { | ||||||
|  |                         throw new UncheckedIOException(e); | ||||||
|  |                     } | ||||||
|  |                 }); | ||||||
|  |             } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @SuppressWarnings("StreamResourceLeak") | ||||||
|  |     private final Stream<Path> cacheContent() { | ||||||
|  |         try { | ||||||
|  |             return Files.walk(cacheDirectory).filter(Files::isRegularFile); | ||||||
|  |         } catch (final IOException e) { | ||||||
|  |             throw new UncheckedIOException(e); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
							
								
								
									
										228
									
								
								libs/xmpp-addr/src/main/java/rocks/xmpp/util/cache/LruCache.java
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										228
									
								
								libs/xmpp-addr/src/main/java/rocks/xmpp/util/cache/LruCache.java
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,228 @@ | |||||||
|  | /* | ||||||
|  |  * The MIT License (MIT) | ||||||
|  |  * | ||||||
|  |  * Copyright (c) 2014-2016 Christian Schudt | ||||||
|  |  * | ||||||
|  |  * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||||
|  |  * of this software and associated documentation files (the "Software"), to deal | ||||||
|  |  * in the Software without restriction, including without limitation the rights | ||||||
|  |  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||||
|  |  * copies of the Software, and to permit persons to whom the Software is | ||||||
|  |  * furnished to do so, subject to the following conditions: | ||||||
|  |  * | ||||||
|  |  * The above copyright notice and this permission notice shall be included in | ||||||
|  |  * all copies or substantial portions of the Software. | ||||||
|  |  * | ||||||
|  |  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||||
|  |  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||||
|  |  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||||
|  |  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||||
|  |  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||||
|  |  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||||
|  |  * THE SOFTWARE. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | package rocks.xmpp.util.cache; | ||||||
|  | 
 | ||||||
|  | import java.util.Collection; | ||||||
|  | import java.util.Map; | ||||||
|  | import java.util.Queue; | ||||||
|  | import java.util.Set; | ||||||
|  | import java.util.concurrent.ConcurrentHashMap; | ||||||
|  | import java.util.concurrent.ConcurrentLinkedDeque; | ||||||
|  | import java.util.function.BiFunction; | ||||||
|  | import java.util.function.Function; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * A simple concurrent implementation of a least-recently-used cache. | ||||||
|  |  * <p> | ||||||
|  |  * This cache is keeps a maximal number of items in memory and removes the least-recently-used item, when new items are added. | ||||||
|  |  * | ||||||
|  |  * @param <K> The key. | ||||||
|  |  * @param <V> The value. | ||||||
|  |  * @author Christian Schudt | ||||||
|  |  * @see <a href="http://javadecodedquestions.blogspot.de/2013/02/java-cache-static-data-loading.html">http://javadecodedquestions.blogspot.de/2013/02/java-cache-static-data-loading.html</a> | ||||||
|  |  * @see <a href="http://stackoverflow.com/a/22891780">http://stackoverflow.com/a/22891780</a> | ||||||
|  |  */ | ||||||
|  | public final class LruCache<K, V> implements Map<K, V> { | ||||||
|  |     private final int maxEntries; | ||||||
|  | 
 | ||||||
|  |     private final Map<K, V> map; | ||||||
|  | 
 | ||||||
|  |     final Queue<K> queue; | ||||||
|  | 
 | ||||||
|  |     public LruCache(final int maxEntries) { | ||||||
|  |         this.maxEntries = maxEntries; | ||||||
|  |         this.map = new ConcurrentHashMap<>(maxEntries); | ||||||
|  |         // Don't use a ConcurrentLinkedQueue here. | ||||||
|  |         // There's a JDK bug, leading to OutOfMemoryError and high CPU usage: | ||||||
|  |         // https://bugs.openjdk.java.net/browse/JDK-8054446 | ||||||
|  |         this.queue = new ConcurrentLinkedDeque<>(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public final int size() { | ||||||
|  |         return map.size(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public final boolean isEmpty() { | ||||||
|  |         return map.isEmpty(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public final boolean containsKey(final Object key) { | ||||||
|  |         return map.containsKey(key); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public final boolean containsValue(final Object value) { | ||||||
|  |         return map.containsValue(value); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @SuppressWarnings("unchecked") | ||||||
|  |     @Override | ||||||
|  |     public final V get(final Object key) { | ||||||
|  |         final V v = map.get(key); | ||||||
|  |         if (v != null) { | ||||||
|  |             // Remove the key from the queue and re-add it to the tail. It is now the most recently used key. | ||||||
|  |             keyUsed((K) key); | ||||||
|  |         } | ||||||
|  |         return v; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public final V put(final K key, final V value) { | ||||||
|  |         V v = map.put(key, value); | ||||||
|  |         keyUsed(key); | ||||||
|  |         limit(); | ||||||
|  |         return v; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public final V remove(final Object key) { | ||||||
|  |         queue.remove(key); | ||||||
|  |         return map.remove(key); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public final void putAll(final Map<? extends K, ? extends V> m) { | ||||||
|  |         for (Map.Entry<? extends K, ? extends V> entry : m.entrySet()) { | ||||||
|  |             put(entry.getKey(), entry.getValue()); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public final void clear() { | ||||||
|  |         queue.clear(); | ||||||
|  |         map.clear(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public final Set<K> keySet() { | ||||||
|  |         return map.keySet(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public final Collection<V> values() { | ||||||
|  |         return map.values(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public final Set<Entry<K, V>> entrySet() { | ||||||
|  |         return map.entrySet(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     // Default methods | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public final V putIfAbsent(final K key, final V value) { | ||||||
|  |         final V v = map.putIfAbsent(key, value); | ||||||
|  |         if (v == null) { | ||||||
|  |             keyUsed(key); | ||||||
|  |         } | ||||||
|  |         limit(); | ||||||
|  |         return v; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public final boolean remove(final Object key, final Object value) { | ||||||
|  |         final boolean removed = map.remove(key, value); | ||||||
|  |         if (removed) { | ||||||
|  |             queue.remove(key); | ||||||
|  |         } | ||||||
|  |         return removed; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public final boolean replace(final K key, final V oldValue, final V newValue) { | ||||||
|  |         final boolean replaced = map.replace(key, oldValue, newValue); | ||||||
|  |         if (replaced) { | ||||||
|  |             keyUsed(key); | ||||||
|  |         } | ||||||
|  |         return replaced; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public final V replace(final K key, final V value) { | ||||||
|  |         final V v = map.replace(key, value); | ||||||
|  |         if (v != null) { | ||||||
|  |             keyUsed(key); | ||||||
|  |         } | ||||||
|  |         return v; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public final V computeIfAbsent(final K key, final Function<? super K, ? extends V> mappingFunction) { | ||||||
|  |         return map.computeIfAbsent(key, mappingFunction.<V>andThen(v -> { | ||||||
|  |             keyUsed(key); | ||||||
|  |             limit(); | ||||||
|  |             return v; | ||||||
|  |         })); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public final V computeIfPresent(final K key, final BiFunction<? super K, ? super V, ? extends V> remappingFunction) { | ||||||
|  |         return map.computeIfPresent(key, remappingFunction.<V>andThen(v -> { | ||||||
|  |             keyUsed(key); | ||||||
|  |             limit(); | ||||||
|  |             return v; | ||||||
|  |         })); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public final V compute(final K key, final BiFunction<? super K, ? super V, ? extends V> remappingFunction) { | ||||||
|  |         return map.compute(key, remappingFunction.<V>andThen(v -> { | ||||||
|  |             keyUsed(key); | ||||||
|  |             limit(); | ||||||
|  |             return v; | ||||||
|  |         })); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public final V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) { | ||||||
|  |         return map.merge(key, value, remappingFunction.<V>andThen(v -> { | ||||||
|  |             keyUsed(key); | ||||||
|  |             limit(); | ||||||
|  |             return v; | ||||||
|  |         })); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void limit() { | ||||||
|  |         while (queue.size() > maxEntries) { | ||||||
|  |             final K oldestKey = queue.poll(); | ||||||
|  |             if (oldestKey != null) { | ||||||
|  |                 map.remove(oldestKey); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void keyUsed(final K key) { | ||||||
|  |         // remove it from the queue and re-add it, to make it the most recently used key. | ||||||
|  |         queue.remove(key); | ||||||
|  |         queue.offer(key); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										28
									
								
								libs/xmpp-addr/src/main/java/rocks/xmpp/util/cache/package-info.java
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								libs/xmpp-addr/src/main/java/rocks/xmpp/util/cache/package-info.java
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,28 @@ | |||||||
|  | /* | ||||||
|  |  * The MIT License (MIT) | ||||||
|  |  * | ||||||
|  |  * Copyright (c) 2014-2016 Christian Schudt | ||||||
|  |  * | ||||||
|  |  * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||||
|  |  * of this software and associated documentation files (the "Software"), to deal | ||||||
|  |  * in the Software without restriction, including without limitation the rights | ||||||
|  |  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||||
|  |  * copies of the Software, and to permit persons to whom the Software is | ||||||
|  |  * furnished to do so, subject to the following conditions: | ||||||
|  |  * | ||||||
|  |  * The above copyright notice and this permission notice shall be included in | ||||||
|  |  * all copies or substantial portions of the Software. | ||||||
|  |  * | ||||||
|  |  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||||
|  |  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||||
|  |  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||||
|  |  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||||
|  |  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||||
|  |  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||||
|  |  * THE SOFTWARE. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Provides simple cache implementations. | ||||||
|  |  */ | ||||||
|  | package rocks.xmpp.util.cache; | ||||||
| @ -1 +1,2 @@ | |||||||
|  | include ':libs:xmpp-addr' | ||||||
| rootProject.name = 'Conversations' | rootProject.name = 'Conversations' | ||||||
|  | |||||||
| @ -29,6 +29,7 @@ | |||||||
| 
 | 
 | ||||||
| package eu.siacs.conversations.ui.util; | package eu.siacs.conversations.ui.util; | ||||||
| 
 | 
 | ||||||
|  | import android.os.Build; | ||||||
| import android.text.Editable; | import android.text.Editable; | ||||||
| import android.text.util.Linkify; | import android.text.util.Linkify; | ||||||
| 
 | 
 | ||||||
| @ -80,7 +81,7 @@ public class MyLinkify { | |||||||
|         if (end < cs.length()) { |         if (end < cs.length()) { | ||||||
|             // Reject strings that were probably matched only because they contain a dot followed by |             // Reject strings that were probably matched only because they contain a dot followed by | ||||||
|             // by some known TLD (see also comment for WORD_BOUNDARY in Patterns.java) |             // by some known TLD (see also comment for WORD_BOUNDARY in Patterns.java) | ||||||
|             if (Character.isAlphabetic(cs.charAt(end-1)) && Character.isAlphabetic(cs.charAt(end))) { |             if (isAlphabetic(cs.charAt(end-1)) && isAlphabetic(cs.charAt(end))) { | ||||||
|                 return false; |                 return false; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @ -93,6 +94,24 @@ public class MyLinkify { | |||||||
|         return uri.isJidValid(); |         return uri.isJidValid(); | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|  |     private static boolean isAlphabetic(final int code) { | ||||||
|  |         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { | ||||||
|  |             return Character.isAlphabetic(code); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         switch (Character.getType(code)) { | ||||||
|  |             case Character.UPPERCASE_LETTER: | ||||||
|  |             case Character.LOWERCASE_LETTER: | ||||||
|  |             case Character.TITLECASE_LETTER: | ||||||
|  |             case Character.MODIFIER_LETTER: | ||||||
|  |             case Character.OTHER_LETTER: | ||||||
|  |             case Character.LETTER_NUMBER: | ||||||
|  |                 return true; | ||||||
|  |             default: | ||||||
|  |                 return false; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public static void addLinks(Editable body, boolean includeGeo) { |     public static void addLinks(Editable body, boolean includeGeo) { | ||||||
|         Linkify.addLinks(body, Patterns.XMPP_PATTERN, "xmpp", XMPPURI_MATCH_FILTER, null); |         Linkify.addLinks(body, Patterns.XMPP_PATTERN, "xmpp", XMPPURI_MATCH_FILTER, null); | ||||||
|         Linkify.addLinks(body, Patterns.AUTOLINK_WEB_URL, "http", WEBURL_MATCH_FILTER, WEBURL_TRANSFORM_FILTER); |         Linkify.addLinks(body, Patterns.AUTOLINK_WEB_URL, "http", WEBURL_MATCH_FILTER, WEBURL_TRANSFORM_FILTER); | ||||||
|  | |||||||
| @ -65,7 +65,9 @@ public class Resolver { | |||||||
|             final Field useHardcodedDnsServers = DNSClient.class.getDeclaredField("useHardcodedDnsServers"); |             final Field useHardcodedDnsServers = DNSClient.class.getDeclaredField("useHardcodedDnsServers"); | ||||||
|             useHardcodedDnsServers.setAccessible(true); |             useHardcodedDnsServers.setAccessible(true); | ||||||
|             useHardcodedDnsServers.setBoolean(dnsClient, false); |             useHardcodedDnsServers.setBoolean(dnsClient, false); | ||||||
|         } catch (NoSuchFieldException | IllegalAccessException e) { |         } catch (NoSuchFieldException e) { | ||||||
|  |             Log.e(Config.LOGTAG, "Unable to disable hardcoded DNS servers", e); | ||||||
|  |         } catch (IllegalAccessException e) { | ||||||
|             Log.e(Config.LOGTAG, "Unable to disable hardcoded DNS servers", e); |             Log.e(Config.LOGTAG, "Unable to disable hardcoded DNS servers", e); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -48,14 +48,17 @@ | |||||||
|                                 android:layout_height="@dimen/avatar_on_details_screen_size" |                                 android:layout_height="@dimen/avatar_on_details_screen_size" | ||||||
|                                 android:layout_alignParentStart="true" |                                 android:layout_alignParentStart="true" | ||||||
|                                 app:riv_corner_radius="2dp" |                                 app:riv_corner_radius="2dp" | ||||||
|                                 android:layout_marginEnd="@dimen/avatar_item_distance"/> |                                 android:layout_marginEnd="@dimen/avatar_item_distance" | ||||||
|  |                                 android:layout_alignParentLeft="true" | ||||||
|  |                                 android:layout_marginRight="@dimen/avatar_item_distance" /> | ||||||
| 
 | 
 | ||||||
|                             <LinearLayout |                             <LinearLayout | ||||||
|                                 android:layout_width="fill_parent" |                                 android:layout_width="fill_parent" | ||||||
|                                 android:layout_height="wrap_content" |                                 android:layout_height="wrap_content" | ||||||
|                                 android:layout_alignParentTop="true" |                                 android:layout_alignParentTop="true" | ||||||
|                                 android:layout_toEndOf="@+id/your_photo" |                                 android:layout_toEndOf="@+id/your_photo" | ||||||
|                                 android:orientation="vertical"> |                                 android:orientation="vertical" | ||||||
|  |                                 android:layout_toRightOf="@+id/your_photo"> | ||||||
| 
 | 
 | ||||||
|                                 <RelativeLayout |                                 <RelativeLayout | ||||||
|                                     android:layout_width="match_parent" |                                     android:layout_width="match_parent" | ||||||
| @ -67,7 +70,9 @@ | |||||||
|                                         android:layout_height="wrap_content" |                                         android:layout_height="wrap_content" | ||||||
|                                         android:layout_alignParentStart="true" |                                         android:layout_alignParentStart="true" | ||||||
|                                         android:layout_toStartOf="@+id/edit_muc_name_button" |                                         android:layout_toStartOf="@+id/edit_muc_name_button" | ||||||
|                                         android:orientation="vertical"> |                                         android:orientation="vertical" | ||||||
|  |                                         android:layout_alignParentLeft="true" | ||||||
|  |                                         android:layout_toLeftOf="@+id/edit_muc_name_button"> | ||||||
| 
 | 
 | ||||||
|                                         <TextView |                                         <TextView | ||||||
|                                             android:id="@+id/muc_title" |                                             android:id="@+id/muc_title" | ||||||
| @ -91,7 +96,9 @@ | |||||||
|                                         android:layout_alignParentStart="true" |                                         android:layout_alignParentStart="true" | ||||||
|                                         android:layout_toStartOf="@+id/edit_muc_name_button" |                                         android:layout_toStartOf="@+id/edit_muc_name_button" | ||||||
|                                         android:orientation="vertical" |                                         android:orientation="vertical" | ||||||
|                                         android:visibility="gone"> |                                         android:visibility="gone" | ||||||
|  |                                         android:layout_alignParentLeft="true" | ||||||
|  |                                         android:layout_toLeftOf="@+id/edit_muc_name_button"> | ||||||
| 
 | 
 | ||||||
|                                         <android.support.design.widget.TextInputLayout |                                         <android.support.design.widget.TextInputLayout | ||||||
|                                             android:layout_width="match_parent" |                                             android:layout_width="match_parent" | ||||||
| @ -132,7 +139,8 @@ | |||||||
|                                         android:alpha="?attr/icon_alpha" |                                         android:alpha="?attr/icon_alpha" | ||||||
|                                         android:background="?attr/selectableItemBackgroundBorderless" |                                         android:background="?attr/selectableItemBackgroundBorderless" | ||||||
|                                         android:padding="@dimen/image_button_padding" |                                         android:padding="@dimen/image_button_padding" | ||||||
|                                         android:src="?attr/icon_edit_body"/> |                                         android:src="?attr/icon_edit_body" | ||||||
|  |                                         android:layout_alignParentRight="true" /> | ||||||
|                                 </RelativeLayout> |                                 </RelativeLayout> | ||||||
|                             </LinearLayout> |                             </LinearLayout> | ||||||
|                         </RelativeLayout> |                         </RelativeLayout> | ||||||
| @ -151,7 +159,8 @@ | |||||||
|                                 android:layout_toStartOf="@+id/change_conference_button" |                                 android:layout_toStartOf="@+id/change_conference_button" | ||||||
|                                 android:text="@string/private_conference" |                                 android:text="@string/private_conference" | ||||||
|                                 android:textAppearance="@style/TextAppearance.Conversations.Body1" |                                 android:textAppearance="@style/TextAppearance.Conversations.Body1" | ||||||
|                                 /> |                                 android:layout_alignParentLeft="true" | ||||||
|  |                                 android:layout_toLeftOf="@+id/change_conference_button" /> | ||||||
| 
 | 
 | ||||||
|                             <ImageButton |                             <ImageButton | ||||||
|                                 android:id="@+id/change_conference_button" |                                 android:id="@+id/change_conference_button" | ||||||
| @ -164,7 +173,8 @@ | |||||||
|                                 android:alpha="?attr/icon_alpha" |                                 android:alpha="?attr/icon_alpha" | ||||||
|                                 android:background="?attr/selectableItemBackgroundBorderless" |                                 android:background="?attr/selectableItemBackgroundBorderless" | ||||||
|                                 android:padding="@dimen/image_button_padding" |                                 android:padding="@dimen/image_button_padding" | ||||||
|                                 android:src="?attr/icon_settings"/> |                                 android:src="?attr/icon_settings" | ||||||
|  |                                 android:layout_alignParentRight="true" /> | ||||||
|                         </RelativeLayout> |                         </RelativeLayout> | ||||||
| 
 | 
 | ||||||
|                         <TableLayout |                         <TableLayout | ||||||
| @ -193,7 +203,8 @@ | |||||||
|                                     android:layout_height="wrap_content" |                                     android:layout_height="wrap_content" | ||||||
|                                     android:layout_gravity="end" |                                     android:layout_gravity="end" | ||||||
|                                     android:paddingStart="4dp" |                                     android:paddingStart="4dp" | ||||||
|                                     android:textAppearance="@style/TextAppearance.Conversations.Body1"/> |                                     android:textAppearance="@style/TextAppearance.Conversations.Body1" | ||||||
|  |                                     android:paddingLeft="4dp" /> | ||||||
|                             </TableRow> |                             </TableRow> | ||||||
| 
 | 
 | ||||||
|                         </TableLayout> |                         </TableLayout> | ||||||
| @ -302,7 +313,8 @@ | |||||||
|                                 android:alpha="?attr/icon_alpha" |                                 android:alpha="?attr/icon_alpha" | ||||||
|                                 android:background="?attr/selectableItemBackgroundBorderless" |                                 android:background="?attr/selectableItemBackgroundBorderless" | ||||||
|                                 android:padding="@dimen/image_button_padding" |                                 android:padding="@dimen/image_button_padding" | ||||||
|                                 android:src="?attr/icon_edit_body"/> |                                 android:src="?attr/icon_edit_body" | ||||||
|  |                                 android:layout_alignParentRight="true" /> | ||||||
|                         </RelativeLayout> |                         </RelativeLayout> | ||||||
| 
 | 
 | ||||||
|                         <RelativeLayout |                         <RelativeLayout | ||||||
| @ -318,7 +330,8 @@ | |||||||
|                                 android:layout_toStartOf="@+id/notification_status_button" |                                 android:layout_toStartOf="@+id/notification_status_button" | ||||||
|                                 android:text="@string/notify_on_all_messages" |                                 android:text="@string/notify_on_all_messages" | ||||||
|                                 android:textAppearance="@style/TextAppearance.Conversations.Body1" |                                 android:textAppearance="@style/TextAppearance.Conversations.Body1" | ||||||
|                                 /> |                                 android:layout_alignParentLeft="true" | ||||||
|  |                                 android:layout_toLeftOf="@+id/notification_status_button" /> | ||||||
| 
 | 
 | ||||||
|                             <ImageButton |                             <ImageButton | ||||||
|                                 android:id="@+id/notification_status_button" |                                 android:id="@+id/notification_status_button" | ||||||
| @ -331,7 +344,8 @@ | |||||||
|                                 android:alpha="?attr/icon_alpha" |                                 android:alpha="?attr/icon_alpha" | ||||||
|                                 android:background="?attr/selectableItemBackgroundBorderless" |                                 android:background="?attr/selectableItemBackgroundBorderless" | ||||||
|                                 android:padding="@dimen/image_button_padding" |                                 android:padding="@dimen/image_button_padding" | ||||||
|                                 android:src="?attr/icon_notifications"/> |                                 android:src="?attr/icon_notifications" | ||||||
|  |                                 android:layout_alignParentRight="true" /> | ||||||
|                         </RelativeLayout> |                         </RelativeLayout> | ||||||
| 
 | 
 | ||||||
|                         <TextView |                         <TextView | ||||||
|  | |||||||
| @ -29,7 +29,8 @@ | |||||||
|                     android:layout_margin="16dp" |                     android:layout_margin="16dp" | ||||||
|                     app:backgroundTint="?colorPrimary" |                     app:backgroundTint="?colorPrimary" | ||||||
|                     android:contentDescription="@string/action_unfix_from_location" |                     android:contentDescription="@string/action_unfix_from_location" | ||||||
|                     android:src="?attr/icon_gps_fixed"/> |                     android:src="?attr/icon_gps_fixed" | ||||||
|  |                     android:layout_alignParentRight="true" /> | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|             <org.osmdroid.views.MapView |             <org.osmdroid.views.MapView | ||||||
|  | |||||||
| @ -28,6 +28,7 @@ | |||||||
|             android:contentDescription="@string/action_unfix_from_location" |             android:contentDescription="@string/action_unfix_from_location" | ||||||
|             android:src="?attr/icon_directions" |             android:src="?attr/icon_directions" | ||||||
|             app:backgroundTint="?colorPrimary" |             app:backgroundTint="?colorPrimary" | ||||||
|             app:tint="@color/white"/> |             app:tint="@color/white" | ||||||
|  |             android:layout_alignParentRight="true" /> | ||||||
|     </RelativeLayout> |     </RelativeLayout> | ||||||
| </layout> | </layout> | ||||||
| @ -21,7 +21,8 @@ | |||||||
|             android:listSelector="@android:color/transparent" |             android:listSelector="@android:color/transparent" | ||||||
|             android:stackFromBottom="true" |             android:stackFromBottom="true" | ||||||
|             android:transcriptMode="normal" |             android:transcriptMode="normal" | ||||||
|             tools:listitem="@layout/message_sent"> |             tools:listitem="@layout/message_sent" | ||||||
|  |             android:layout_alignParentLeft="true"> | ||||||
|         </ListView> |         </ListView> | ||||||
| 
 | 
 | ||||||
|         <android.support.design.widget.FloatingActionButton |         <android.support.design.widget.FloatingActionButton | ||||||
| @ -35,7 +36,8 @@ | |||||||
|             android:src="?attr/icon_scroll_down" |             android:src="?attr/icon_scroll_down" | ||||||
|             app:fabSize="mini" |             app:fabSize="mini" | ||||||
|             android:visibility="gone" |             android:visibility="gone" | ||||||
|             app:useCompatPadding="true"/> |             app:useCompatPadding="true" | ||||||
|  |             android:layout_alignParentRight="true" /> | ||||||
| 
 | 
 | ||||||
|         <eu.siacs.conversations.ui.widget.UnreadCountCustomView |         <eu.siacs.conversations.ui.widget.UnreadCountCustomView | ||||||
|             android:id="@+id/unread_count_custom_view" |             android:id="@+id/unread_count_custom_view" | ||||||
| @ -47,7 +49,10 @@ | |||||||
|             android:layout_marginEnd="8dp" |             android:layout_marginEnd="8dp" | ||||||
|             android:elevation="8dp" |             android:elevation="8dp" | ||||||
|             android:visibility="gone" |             android:visibility="gone" | ||||||
|             app:backgroundColor="?attr/unread_count" /> |             app:backgroundColor="?attr/unread_count" | ||||||
|  |             android:layout_alignRight="@+id/scroll_to_bottom_button" | ||||||
|  |             tools:ignore="RtlCompat" | ||||||
|  |             android:layout_marginRight="8dp" /> | ||||||
| 
 | 
 | ||||||
|         <RelativeLayout |         <RelativeLayout | ||||||
|             android:id="@+id/textsend" |             android:id="@+id/textsend" | ||||||
| @ -55,7 +60,8 @@ | |||||||
|             android:layout_height="wrap_content" |             android:layout_height="wrap_content" | ||||||
|             android:layout_alignParentBottom="true" |             android:layout_alignParentBottom="true" | ||||||
|             android:layout_alignParentStart="true" |             android:layout_alignParentStart="true" | ||||||
|             android:background="?attr/color_background_primary"> |             android:background="?attr/color_background_primary" | ||||||
|  |             android:layout_alignParentLeft="true"> | ||||||
| 
 | 
 | ||||||
|             <android.support.v7.widget.RecyclerView |             <android.support.v7.widget.RecyclerView | ||||||
|                 android:visibility="gone" |                 android:visibility="gone" | ||||||
| @ -68,7 +74,9 @@ | |||||||
|                 android:orientation="horizontal" |                 android:orientation="horizontal" | ||||||
|                 app:layoutManager="android.support.v7.widget.LinearLayoutManager" |                 app:layoutManager="android.support.v7.widget.LinearLayoutManager" | ||||||
|                 android:paddingTop="8dp" |                 android:paddingTop="8dp" | ||||||
|                 android:requiresFadingEdge="horizontal"> |                 android:requiresFadingEdge="horizontal" | ||||||
|  |                 android:layout_alignParentLeft="true" | ||||||
|  |                 android:layout_toLeftOf="@+id/textSendButton"> | ||||||
| 
 | 
 | ||||||
|             </android.support.v7.widget.RecyclerView> |             </android.support.v7.widget.RecyclerView> | ||||||
| 
 | 
 | ||||||
| @ -89,7 +97,9 @@ | |||||||
|                 android:paddingBottom="12dp" |                 android:paddingBottom="12dp" | ||||||
|                 android:paddingLeft="8dp" |                 android:paddingLeft="8dp" | ||||||
|                 android:paddingRight="8dp" |                 android:paddingRight="8dp" | ||||||
|                 android:paddingTop="12dp"> |                 android:paddingTop="12dp" | ||||||
|  |                 android:layout_alignParentLeft="true" | ||||||
|  |                 android:layout_toLeftOf="@+id/textSendButton"> | ||||||
| 
 | 
 | ||||||
|                 <requestFocus/> |                 <requestFocus/> | ||||||
|             </eu.siacs.conversations.ui.widget.EditMessage> |             </eu.siacs.conversations.ui.widget.EditMessage> | ||||||
| @ -102,7 +112,8 @@ | |||||||
|                 android:layout_centerVertical="true" |                 android:layout_centerVertical="true" | ||||||
|                 android:background="?attr/color_background_primary" |                 android:background="?attr/color_background_primary" | ||||||
|                 android:contentDescription="@string/send_message" |                 android:contentDescription="@string/send_message" | ||||||
|                 android:src="?attr/ic_send_text_offline"/> |                 android:src="?attr/ic_send_text_offline" | ||||||
|  |                 android:layout_alignParentRight="true" /> | ||||||
|         </RelativeLayout> |         </RelativeLayout> | ||||||
| 
 | 
 | ||||||
|         <RelativeLayout |         <RelativeLayout | ||||||
| @ -125,7 +136,10 @@ | |||||||
|                 android:layout_centerVertical="true" |                 android:layout_centerVertical="true" | ||||||
|                 android:layout_toStartOf="@+id/snackbar_action" |                 android:layout_toStartOf="@+id/snackbar_action" | ||||||
|                 android:paddingStart="24dp" |                 android:paddingStart="24dp" | ||||||
|                 android:textAppearance="@style/TextAppearance.Conversations.Body1.OnDark"/> |                 android:textAppearance="@style/TextAppearance.Conversations.Body1.OnDark" | ||||||
|  |                 android:layout_alignParentLeft="true" | ||||||
|  |                 android:paddingLeft="24dp" | ||||||
|  |                 android:layout_toLeftOf="@+id/snackbar_action" /> | ||||||
| 
 | 
 | ||||||
|             <TextView |             <TextView | ||||||
|                 android:id="@+id/snackbar_action" |                 android:id="@+id/snackbar_action" | ||||||
| @ -139,7 +153,8 @@ | |||||||
|                 android:paddingTop="16dp" |                 android:paddingTop="16dp" | ||||||
|                 android:textAllCaps="true" |                 android:textAllCaps="true" | ||||||
|                 android:textAppearance="@style/TextAppearance.Conversations.Body1.OnDark" |                 android:textAppearance="@style/TextAppearance.Conversations.Body1.OnDark" | ||||||
|                 android:textStyle="bold"/> |                 android:textStyle="bold" | ||||||
|  |                 android:layout_alignParentRight="true" /> | ||||||
|         </RelativeLayout> |         </RelativeLayout> | ||||||
| 
 | 
 | ||||||
|     </RelativeLayout> |     </RelativeLayout> | ||||||
|  | |||||||
| @ -19,6 +19,7 @@ | |||||||
|             android:layout_alignParentTop="true" |             android:layout_alignParentTop="true" | ||||||
|             android:alpha="?attr/delete_icon_alpha" |             android:alpha="?attr/delete_icon_alpha" | ||||||
|             android:background="?attr/selectableItemBackgroundBorderless" |             android:background="?attr/selectableItemBackgroundBorderless" | ||||||
|             android:src="?attr/icon_cancel"/> |             android:src="?attr/icon_cancel" | ||||||
|  |             android:layout_alignParentRight="true" /> | ||||||
|     </RelativeLayout> |     </RelativeLayout> | ||||||
| </layout> | </layout> | ||||||
|  | |||||||
| @ -17,7 +17,8 @@ | |||||||
|         android:layout_alignParentEnd="true" |         android:layout_alignParentEnd="true" | ||||||
|         android:layout_alignParentBottom="true" |         android:layout_alignParentBottom="true" | ||||||
|         android:layout_width="wrap_content" |         android:layout_width="wrap_content" | ||||||
|         android:layout_height="wrap_content"> |         android:layout_height="wrap_content" | ||||||
|  |         android:layout_alignParentRight="true"> | ||||||
|         <com.makeramen.roundedimageview.RoundedImageView |         <com.makeramen.roundedimageview.RoundedImageView | ||||||
|             android:id="@+id/message_photo" |             android:id="@+id/message_photo" | ||||||
|             android:layout_width="48dp" |             android:layout_width="48dp" | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Daniel Gultsch
						Daniel Gultsch