mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-11-19 02:42:05 +01:00
Kotlin conversion: UserId
This commit is contained in:
parent
ec8ae3eff0
commit
68ac5af255
3 changed files with 209 additions and 360 deletions
|
@ -1,356 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2020 Paul Schaub <vanitasvitae@fsfe.org>, 2021 Flowcrypt a.s.
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package org.pgpainless.key.util;
|
|
||||||
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
public final class UserId implements CharSequence {
|
|
||||||
|
|
||||||
// Email regex: https://emailregex.com/
|
|
||||||
// switched "a-z0-9" to "\p{L}\u0900-\u097F0-9" for better support for international characters
|
|
||||||
// \\p{L} = Unicode Letters
|
|
||||||
// \u0900-\u097F = Hindi Letters
|
|
||||||
private static final Pattern emailPattern = Pattern.compile("(?:[\\p{L}\\u0900-\\u097F0-9!#\\$%&'*+/=?^_`{|}~-]+(?:\\.[\\p{L}\\u0900-\\u097F0-9!#\\$%&'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-" +
|
|
||||||
"\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[\\p{L}\\u0900-\\u097F0-9](?:[\\p{L}\\u0900-\\u097F0-9" +
|
|
||||||
"-]*[\\p{L}\\u0900-\\u097F0-9])?\\.)+[\\p{L}\\u0900-\\u097F0-9](?:[\\p{L}\\u0900-\\u097F0-9-]*[\\p{L}\\u0900-\\u097F0-9])?|\\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)" +
|
|
||||||
"\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[$\\p{L}\\u0900-\\u097F0-9-]*[\\p{L}\\u0900-\\u097F0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f" +
|
|
||||||
"\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)])");
|
|
||||||
|
|
||||||
// User-ID Regex
|
|
||||||
// "Firstname Lastname (Comment) <email@example.com>"
|
|
||||||
// All groups are optional
|
|
||||||
// https://www.rfc-editor.org/rfc/rfc5322#page-16
|
|
||||||
private static final Pattern nameAddrPattern = Pattern.compile("^((?<name>.+?)\\s)?(\\((?<comment>.+?)\\)\\s)?(<(?<email>.+?)>)?$");
|
|
||||||
|
|
||||||
public static final class Builder {
|
|
||||||
private String name;
|
|
||||||
private String comment;
|
|
||||||
private String email;
|
|
||||||
|
|
||||||
private Builder() {
|
|
||||||
}
|
|
||||||
|
|
||||||
private Builder(String name, String comment, String email) {
|
|
||||||
this.name = name;
|
|
||||||
this.comment = comment;
|
|
||||||
this.email = email;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder withName(@Nonnull String name) {
|
|
||||||
this.name = name;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder withComment(@Nonnull String comment) {
|
|
||||||
this.comment = comment;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder withEmail(@Nonnull String email) {
|
|
||||||
this.email = email;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder noName() {
|
|
||||||
name = null;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder noComment() {
|
|
||||||
comment = null;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder noEmail() {
|
|
||||||
email = null;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UserId build() {
|
|
||||||
return new UserId(name, comment, email);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse a {@link UserId} from free-form text, <pre>name-addr</pre> or <pre>mailbox</pre> string and split it
|
|
||||||
* up into its components.
|
|
||||||
* Example inputs for this method:
|
|
||||||
* <ul>
|
|
||||||
* <li><pre>john@pgpainless.org</pre></li>
|
|
||||||
* <li><pre><john@pgpainless.org></pre></li>
|
|
||||||
* <li><pre>John Doe</pre></li>
|
|
||||||
* <li><pre>John Doe <john@pgpainless.org></pre></li>
|
|
||||||
* <li><pre>John Doe (work email) <john@pgpainless.org></pre></li>
|
|
||||||
* </ul>
|
|
||||||
* In these cases, this method will detect email addresses, names and comments and expose those
|
|
||||||
* via the respective getters.
|
|
||||||
* This method does not support parsing mail addresses of the following formats:
|
|
||||||
* <ul>
|
|
||||||
* <li>Local domains without TLDs (<pre>user@localdomain1</pre>)</li>
|
|
||||||
* <li><pre>" "@example.org</pre> (spaces between the quotes)</li>
|
|
||||||
* <li><pre>"very.(),:;<>[]\".VERY.\"very@\\ \"very\".unusual"@strange.example.com</pre></li>
|
|
||||||
* </ul>
|
|
||||||
* Note: This method does not guarantee that <pre>string.equals(UserId.parse(string).toString())</pre> is true.
|
|
||||||
* For example, <pre>UserId.parse("alice@pgpainless.org").toString()</pre> wraps the mail address in angled brackets.
|
|
||||||
*
|
|
||||||
* @see <a href="https://www.rfc-editor.org/rfc/rfc5322#page-16">RFC5322 §3.4. Address Specification</a>
|
|
||||||
* @param string user-id
|
|
||||||
* @return parsed {@link UserId} object
|
|
||||||
*/
|
|
||||||
public static UserId parse(@Nonnull String string) {
|
|
||||||
Builder builder = newBuilder();
|
|
||||||
string = string.trim();
|
|
||||||
Matcher matcher = nameAddrPattern.matcher(string);
|
|
||||||
if (matcher.find()) {
|
|
||||||
String name = matcher.group(2);
|
|
||||||
String comment = matcher.group(4);
|
|
||||||
String mail = matcher.group(6);
|
|
||||||
matcher = emailPattern.matcher(mail);
|
|
||||||
if (!matcher.matches()) {
|
|
||||||
throw new IllegalArgumentException("Malformed email address");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name != null) {
|
|
||||||
builder.withName(name);
|
|
||||||
}
|
|
||||||
if (comment != null) {
|
|
||||||
builder.withComment(comment);
|
|
||||||
}
|
|
||||||
builder.withEmail(mail);
|
|
||||||
} else {
|
|
||||||
matcher = emailPattern.matcher(string);
|
|
||||||
if (matcher.matches()) {
|
|
||||||
builder.withEmail(string);
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("Malformed email address");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private final String name;
|
|
||||||
private final String comment;
|
|
||||||
private final String email;
|
|
||||||
private long hash = Long.MAX_VALUE;
|
|
||||||
|
|
||||||
private UserId(@Nullable String name, @Nullable String comment, @Nullable String email) {
|
|
||||||
this.name = name == null ? null : name.trim();
|
|
||||||
this.comment = comment == null ? null : comment.trim();
|
|
||||||
this.email = email == null ? null : email.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static UserId onlyEmail(@Nonnull String email) {
|
|
||||||
return new UserId(null, null, email);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static UserId nameAndEmail(@Nonnull String name, @Nonnull String email) {
|
|
||||||
return new UserId(name, null, email);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Builder newBuilder() {
|
|
||||||
return new Builder();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder toBuilder() {
|
|
||||||
return new Builder(name, comment, email);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return getName(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName(boolean preserveQuotes) {
|
|
||||||
if (name == null || name.isEmpty()) {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name.startsWith("\"")) {
|
|
||||||
if (preserveQuotes) {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
String withoutQuotes = name.substring(1);
|
|
||||||
if (withoutQuotes.endsWith("\"")) {
|
|
||||||
withoutQuotes = withoutQuotes.substring(0, withoutQuotes.length() - 1);
|
|
||||||
}
|
|
||||||
return withoutQuotes;
|
|
||||||
}
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getComment() {
|
|
||||||
return comment;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getEmail() {
|
|
||||||
return email;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int length() {
|
|
||||||
return toString().length();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public char charAt(int i) {
|
|
||||||
return toString().charAt(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @Nonnull CharSequence subSequence(int i, int i1) {
|
|
||||||
return toString().subSequence(i, i1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @Nonnull String toString() {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
if (name != null && !name.isEmpty()) {
|
|
||||||
sb.append(getName(true));
|
|
||||||
}
|
|
||||||
if (comment != null && !comment.isEmpty()) {
|
|
||||||
if (sb.length() > 0) {
|
|
||||||
sb.append(' ');
|
|
||||||
}
|
|
||||||
sb.append('(').append(comment).append(')');
|
|
||||||
}
|
|
||||||
if (email != null && !email.isEmpty()) {
|
|
||||||
if (sb.length() > 0) {
|
|
||||||
sb.append(' ');
|
|
||||||
}
|
|
||||||
sb.append('<').append(email).append('>');
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a string representation of the object.
|
|
||||||
* @return a string representation of the object.
|
|
||||||
* @deprecated use {@link #toString()} instead.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public String asString() {
|
|
||||||
return toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (o == null) return false;
|
|
||||||
if (o == this) return true;
|
|
||||||
if (!(o instanceof UserId)) return false;
|
|
||||||
final UserId other = (UserId) o;
|
|
||||||
return isEqualComponent(name, other.name, false)
|
|
||||||
&& isEqualComponent(comment, other.comment, false)
|
|
||||||
&& isEqualComponent(email, other.email, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
if (hash != Long.MAX_VALUE) {
|
|
||||||
return (int) hash;
|
|
||||||
} else {
|
|
||||||
int hashCode = 7;
|
|
||||||
hashCode = 31 * hashCode + (name == null ? 0 : name.hashCode());
|
|
||||||
hashCode = 31 * hashCode + (comment == null ? 0 : comment.hashCode());
|
|
||||||
hashCode = 31 * hashCode + (email == null ? 0 : email.toLowerCase().hashCode());
|
|
||||||
this.hash = hashCode;
|
|
||||||
return hashCode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isEqualComponent(String value, String otherValue, boolean ignoreCase) {
|
|
||||||
final boolean valueIsNull = (value == null);
|
|
||||||
final boolean otherValueIsNull = (otherValue == null);
|
|
||||||
return (valueIsNull && otherValueIsNull)
|
|
||||||
|| (!valueIsNull && !otherValueIsNull
|
|
||||||
&& (ignoreCase ? value.equalsIgnoreCase(otherValue) : value.equals(otherValue)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int compare(@Nullable UserId o1, @Nullable UserId o2, @Nonnull Comparator<UserId> comparator) {
|
|
||||||
return comparator.compare(o1, o2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class DefaultComparator implements Comparator<UserId> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compare(UserId o1, UserId o2) {
|
|
||||||
if (o1 == o2) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if (o1 == null) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (o2 == null) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
NullSafeStringComparator c = new NullSafeStringComparator();
|
|
||||||
int cName = c.compare(o1.getName(), o2.getName());
|
|
||||||
if (cName != 0) {
|
|
||||||
return cName;
|
|
||||||
}
|
|
||||||
|
|
||||||
int cComment = c.compare(o1.getComment(), o2.getComment());
|
|
||||||
if (cComment != 0) {
|
|
||||||
return cComment;
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.compare(o1.getEmail(), o2.getEmail());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class DefaultIgnoreCaseComparator implements Comparator<UserId> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compare(UserId o1, UserId o2) {
|
|
||||||
if (o1 == o2) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if (o1 == null) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (o2 == null) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
NullSafeStringComparator c = new NullSafeStringComparator();
|
|
||||||
int cName = c.compare(lower(o1.getName()), lower(o2.getName()));
|
|
||||||
if (cName != 0) {
|
|
||||||
return cName;
|
|
||||||
}
|
|
||||||
|
|
||||||
int cComment = c.compare(lower(o1.getComment()), lower(o2.getComment()));
|
|
||||||
if (cComment != 0) {
|
|
||||||
return cComment;
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.compare(lower(o1.getEmail()), lower(o2.getEmail()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String lower(String string) {
|
|
||||||
return string == null ? null : string.toLowerCase();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class NullSafeStringComparator implements Comparator<String> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compare(String o1, String o2) {
|
|
||||||
// noinspection StringEquality
|
|
||||||
if (o1 == o2) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if (o1 == null) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (o2 == null) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return o1.compareTo(o2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,206 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package org.pgpainless.key.util
|
||||||
|
|
||||||
|
class UserId internal constructor(
|
||||||
|
name: String?,
|
||||||
|
comment: String?,
|
||||||
|
email: String?
|
||||||
|
) : CharSequence {
|
||||||
|
|
||||||
|
private val _name: String?
|
||||||
|
val comment: String?
|
||||||
|
val email: String?
|
||||||
|
|
||||||
|
init {
|
||||||
|
this._name = name?.trim()
|
||||||
|
this.comment = comment?.trim()
|
||||||
|
this.email = email?.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
val full: String = buildString {
|
||||||
|
if (name?.isNotBlank() == true) {
|
||||||
|
append(getName(true))
|
||||||
|
}
|
||||||
|
if (comment?.isNotBlank() == true) {
|
||||||
|
if (isNotEmpty()) {
|
||||||
|
append(' ')
|
||||||
|
}
|
||||||
|
append("($comment)")
|
||||||
|
}
|
||||||
|
if (email?.isNotBlank() == true) {
|
||||||
|
if (isNotEmpty()) {
|
||||||
|
append(' ')
|
||||||
|
}
|
||||||
|
append("<$email>")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val length: Int
|
||||||
|
get() = full.length
|
||||||
|
|
||||||
|
val name: String?
|
||||||
|
get() = getName(false)
|
||||||
|
|
||||||
|
fun getName(preserveQuotes: Boolean): String? {
|
||||||
|
return if (preserveQuotes || _name.isNullOrBlank()) {
|
||||||
|
_name
|
||||||
|
} else _name.removeSurrounding("\"")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (other === null) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (this === other) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (other !is UserId) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return isComponentEqual(_name, other._name, false)
|
||||||
|
&& isComponentEqual(comment, other.comment, false)
|
||||||
|
&& isComponentEqual(email, other.email, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun get(index: Int): Char {
|
||||||
|
return full[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return toString().hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun subSequence(startIndex: Int, endIndex: Int): CharSequence {
|
||||||
|
return full.subSequence(startIndex, endIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return full
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isComponentEqual(value: String?, otherValue: String?, ignoreCase: Boolean): Boolean = value.equals(otherValue, ignoreCase)
|
||||||
|
|
||||||
|
fun toBuilder() = builder().also { builder ->
|
||||||
|
if (this._name != null) builder.withName(_name)
|
||||||
|
if (this.comment != null) builder.withComment(comment)
|
||||||
|
if (this.email != null) builder.withEmail(email)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
// Email regex: https://emailregex.com/
|
||||||
|
// switched "a-z0-9" to "\p{L}\u0900-\u097F0-9" for better support for international characters
|
||||||
|
// \\p{L} = Unicode Letters
|
||||||
|
// \u0900-\u097F = Hindi Letters
|
||||||
|
@JvmStatic
|
||||||
|
private val emailPattern = ("(?:[\\p{L}\\u0900-\\u097F0-9!#\\$%&'*+/=?^_`{|}~-]+(?:\\.[\\p{L}\\u0900-\\u097F0-9!#\\$%&'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-" +
|
||||||
|
"\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[\\p{L}\\u0900-\\u097F0-9](?:[\\p{L}\\u0900-\\u097F0-9" +
|
||||||
|
"-]*[\\p{L}\\u0900-\\u097F0-9])?\\.)+[\\p{L}\\u0900-\\u097F0-9](?:[\\p{L}\\u0900-\\u097F0-9-]*[\\p{L}\\u0900-\\u097F0-9])?|\\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)" +
|
||||||
|
"\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[$\\p{L}\\u0900-\\u097F0-9-]*[\\p{L}\\u0900-\\u097F0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f" +
|
||||||
|
"\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)])").toPattern()
|
||||||
|
|
||||||
|
// User-ID Regex
|
||||||
|
// "Firstname Lastname (Comment) <email@example.com>"
|
||||||
|
// All groups are optional
|
||||||
|
// https://www.rfc-editor.org/rfc/rfc5322#page-16
|
||||||
|
@JvmStatic
|
||||||
|
private val nameAddrPattern = "^((?<name>.+?)\\s)?(\\((?<comment>.+?)\\)\\s)?(<(?<email>.+?)>)?$".toPattern()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a [UserId] from free-form text, <pre>name-addr</pre> or <pre>mailbox</pre> string and split it
|
||||||
|
* up into its components.
|
||||||
|
* Example inputs for this method:
|
||||||
|
* <ul>
|
||||||
|
* <li><pre>john@pgpainless.org</pre></li>
|
||||||
|
* <li><pre><john@pgpainless.org></pre></li>
|
||||||
|
* <li><pre>John Doe</pre></li>
|
||||||
|
* <li><pre>John Doe <john@pgpainless.org></pre></li>
|
||||||
|
* <li><pre>John Doe (work email) <john@pgpainless.org></pre></li>
|
||||||
|
* </ul>
|
||||||
|
* In these cases, this method will detect email addresses, names and comments and expose those
|
||||||
|
* via the respective getters.
|
||||||
|
* This method does not support parsing mail addresses of the following formats:
|
||||||
|
* <ul>
|
||||||
|
* <li>Local domains without TLDs (<pre>user@localdomain1</pre>)</li>
|
||||||
|
* <li><pre>" "@example.org</pre> (spaces between the quotes)</li>
|
||||||
|
* <li><pre>"very.(),:;<>[]\".VERY.\"very@\\ \"very\".unusual"@strange.example.com</pre></li>
|
||||||
|
* </ul>
|
||||||
|
* Note: This method does not guarantee that <pre>string.equals(UserId.parse(string).toString())</pre> is true.
|
||||||
|
* For example, <pre>UserId.parse("alice@pgpainless.org").toString()</pre> wraps the mail address in angled brackets.
|
||||||
|
*
|
||||||
|
* @see <a href="https://www.rfc-editor.org/rfc/rfc5322#page-16">RFC5322 §3.4. Address Specification</a>
|
||||||
|
* @param string user-id
|
||||||
|
* @return parsed UserId object
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
fun parse(string: String): UserId {
|
||||||
|
val trimmed = string.trim()
|
||||||
|
nameAddrPattern.matcher(trimmed).let { nameAddrMatcher ->
|
||||||
|
if (nameAddrMatcher.find()) {
|
||||||
|
val name = nameAddrMatcher.group(2)
|
||||||
|
val comment = nameAddrMatcher.group(4)
|
||||||
|
val mail = nameAddrMatcher.group(6)
|
||||||
|
require(emailPattern.matcher(mail).matches()) { "Malformed email address" }
|
||||||
|
return UserId(name, comment, mail)
|
||||||
|
} else {
|
||||||
|
require(emailPattern.matcher(trimmed).matches()) { "Malformed email address" }
|
||||||
|
return UserId(null, null, trimmed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun onlyEmail(email: String) = UserId(null, null, email)
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun nameAndEmail(name: String, email: String) = UserId(name, null, email)
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun compare(u1: UserId?, u2: UserId?, comparator: Comparator<UserId?>) = comparator.compare(u1, u2)
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@Deprecated("Deprecated in favor of builde() method.", ReplaceWith("builder()"))
|
||||||
|
fun newBuilder() = builder()
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun builder() = Builder()
|
||||||
|
}
|
||||||
|
|
||||||
|
class Builder internal constructor() {
|
||||||
|
var name: String? = null
|
||||||
|
var comment: String? = null
|
||||||
|
var email: String? = null
|
||||||
|
|
||||||
|
fun withName(name: String) = apply { this.name = name }
|
||||||
|
fun withComment(comment: String) = apply { this.comment = comment}
|
||||||
|
fun withEmail(email: String) = apply { this.email = email }
|
||||||
|
|
||||||
|
fun noName() = apply { this.name = null }
|
||||||
|
fun noComment() = apply { this.comment = null }
|
||||||
|
fun noEmail() = apply { this.email = null }
|
||||||
|
|
||||||
|
fun build() = UserId(name, comment, email)
|
||||||
|
}
|
||||||
|
|
||||||
|
class DefaultComparator : Comparator<UserId> {
|
||||||
|
override fun compare(o1: UserId?, o2: UserId?): Int {
|
||||||
|
return compareBy<UserId?> { it?._name }
|
||||||
|
.thenBy { it?.comment }
|
||||||
|
.thenBy { it?.email }
|
||||||
|
.compare(o1, o2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DefaultIgnoreCaseComparator : Comparator<UserId> {
|
||||||
|
override fun compare(p0: UserId?, p1: UserId?): Int {
|
||||||
|
return compareBy<UserId?> { it?._name?.lowercase() }
|
||||||
|
.thenBy { it?.comment?.lowercase() }
|
||||||
|
.thenBy { it?.email?.lowercase() }
|
||||||
|
.compare(p0, p1)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -197,15 +197,14 @@ public class UserIdTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void asStringTest() {
|
public void toStringTest() {
|
||||||
UserId id = UserId.newBuilder()
|
UserId id = UserId.builder()
|
||||||
.withName("Alice")
|
.withName("Alice")
|
||||||
.withComment("Work Email")
|
.withComment("Work Email")
|
||||||
.withEmail("alice@pgpainless.org")
|
.withEmail("alice@pgpainless.org")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// noinspection deprecation
|
assertEquals(id.toString(), id.toString());
|
||||||
assertEquals(id.toString(), id.asString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
Loading…
Reference in a new issue