Skip to content

Commit

Permalink
Add functionality to Mask
Browse files Browse the repository at this point in the history
Bippity boppity boop
  • Loading branch information
kashike committed Jan 25, 2017
1 parent 2f27732 commit 353b3f0
Show file tree
Hide file tree
Showing 6 changed files with 589 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import org.kitteh.irc.client.library.element.mode.ModeStatus;
import org.kitteh.irc.client.library.element.mode.ModeStatusList;
import org.kitteh.irc.client.library.util.CIKeyMap;
import org.kitteh.irc.client.library.util.Mask;
import org.kitteh.irc.client.library.util.Sanity;
import org.kitteh.irc.client.library.util.ToStringer;

Expand Down Expand Up @@ -746,13 +747,7 @@ public String toString() {
}
}

// Valid nick chars: \w\[]^`{}|-_
// Pattern unescaped: ([\w\\\[\]\^`\{\}\|\-_]+)!([~\w]+)@([\w\.\-:]+)
// You know what? Screw it.
// Let's just do it assuming no IRCD can handle following the rules.
// New pattern: ([^!@]+)!([^!@]+)@([^!@]+)
private static final Pattern NICK_PATTERN = Pattern.compile("([^!@]+)!([^!@]+)@([^!@]+)");
private static final Pattern SERVER_PATTERN = Pattern.compile("(?!-)(?:[a-zA-Z\\d\\-]{0,62}[a-zA-Z\\d]\\.){1,126}(?!\\d+)[a-zA-Z\\d]{1,63}");
private static final Pattern SERVER_PATTERN = Pattern.compile("(?!\\-)(?:[a-zA-Z\\d\\-]{0,62}[a-zA-Z\\d]\\.){1,126}(?!\\d+)[a-zA-Z\\d]{1,63}");

private final InternalClient client;

Expand Down Expand Up @@ -783,7 +778,7 @@ void unTrackChannel(@Nonnull IRCChannel channel) {

@Nonnull
IRCActor getActor(@Nonnull String name) {
Matcher nickMatcher = NICK_PATTERN.matcher(name);
Matcher nickMatcher = Mask.NICK_PATTERN.matcher(name);
if (nickMatcher.matches()) {
String nick = nickMatcher.group(1);
IRCUser user = this.trackedUsers.get(nick);
Expand Down
187 changes: 178 additions & 9 deletions src/main/java/org/kitteh/irc/client/library/util/Mask.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,24 @@
*/
package org.kitteh.irc.client.library.util;

import org.kitteh.irc.client.library.element.Channel;
import org.kitteh.irc.client.library.element.User;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.HashSet;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
* Represents a mask that can match a {@link User}.
*/
public class Mask {
public class Mask implements Predicate<User> {
/**
* Creates a Mask from a given String.
*
Expand All @@ -39,13 +49,132 @@ public class Mask {
*/
@Nonnull
public static Mask fromString(@Nonnull String string) {
return new Mask(Sanity.nullCheck(string, "String cannot be null"));
Sanity.nullCheck(string, "String cannot be null");
return new Mask(string);
}

private final String string;
/**
* Creates a Mask from a given User.
*
* @param user user
* @return mask from user
*/
@Nonnull
public static Mask fromUser(@Nonnull User user) {
Sanity.nullCheck(user, "User cannot be null");
return new Mask(user.getHost(), user.getNick(), user.getUserString());
}

/**
* Creates a Mask from the given nick.
*
* @param nick nick
* @return mask from nick
*/
@Nonnull
public static Mask fromNick(@Nonnull String nick) {
Sanity.nullCheck(nick, "Nick cannot be null");
return new Mask(nick, null, null);
}

private Mask(@Nonnull String string) {
this.string = string;
/**
* Creates a Mask from the given user.
*
* @param user user
* @return mask from user
*/
@Nonnull
public static Mask fromUserString(@Nonnull String user) {
Sanity.nullCheck(user, "User cannot be null");
return new Mask(null, user, null);
}

/**
* Creates a Mask from the given host.
*
* @param host host
* @return mask from host
*/
@Nonnull
public static Mask fromHost(@Nonnull String host) {
Sanity.nullCheck(host, "Host cannot be null");
return new Mask(null, null, host);
}

// Valid nick chars: \w\[]^`{}|-_
// Pattern unescaped: ([\w\\\[\]\^`\{\}\|\-_]+)!([~\w]+)@([\w\.\-:]+)
// You know what? Screw it.
// Let's just do it assuming no IRCD can handle following the rules.
// New pattern: ([^!@]+)!([^!@]+)@([^!@]+)
public static final Pattern NICK_PATTERN = Pattern.compile("([^!@]+)!([^!@]+)@([^!@]+)");
public static final char WILDCARD_CHARACTER = '*';
public static final String WILDCARD = String.valueOf(WILDCARD_CHARACTER);
protected final Optional<String> nick;
protected final Optional<String> user;
protected final Optional<String> host;
protected final Pattern pattern;

protected Mask(@Nonnull String string) {
Sanity.nullCheck(string, "String cannot be null");

@Nullable String nick = null;
@Nullable String user = null;
@Nullable String host = null;

final Matcher matcher = NICK_PATTERN.matcher(string);
if (matcher.matches()) {
nick = matcher.group(1);
user = matcher.group(2);
host = matcher.group(3);
}

this.nick = Optional.ofNullable(nick);
this.user = Optional.ofNullable(user);
this.host = Optional.ofNullable(host);
this.pattern = StringUtil.wildcardToPattern(string);
}

protected Mask(@Nullable String nick, @Nullable String user, @Nullable String host) {
this.nick = Optional.ofNullable(nick);
this.user = Optional.ofNullable(user);
this.host = Optional.ofNullable(host);
this.pattern = this.resolvePattern();
}

@Nonnull
protected Pattern resolvePattern() {
String pattern = this.asString();
return StringUtil.wildcardToPattern(pattern);
}

/**
* Gets the nick component of this mask.
*
* @return nick component if known
*/
@Nonnull
public Optional<String> getNick() {
return this.nick;
}

/**
* Gets the user component of this mask.
*
* @return user component if known
*/
@Nonnull
public Optional<String> getUser() {
return this.user;
}

/**
* Gets the host component of this mask.
*
* @return host component if known
*/
@Nonnull
public Optional<String> getHost() {
return this.host;
}

/**
Expand All @@ -55,22 +184,62 @@ private Mask(@Nonnull String string) {
*/
@Nonnull
public String asString() {
return this.string;
return this.getNick().orElse(WILDCARD) + '!' + this.getUser().orElse(WILDCARD) + '@' + this.getHost().orElse(WILDCARD);
}

/**
* Gets a set of users that match this mask in the provided channel.
*
* @param channel channel
* @return set of users that match this mask
*/
@Nonnull
public Set<User> getMatches(@Nonnull Channel channel) {
Sanity.nullCheck(channel, "Channel cannot be null");
return channel.getUsers().stream().filter(this).collect(Collectors.toCollection(HashSet::new));
}

/**
* Gets if the user matches this mask.
*
* @param user user
* @return true if user matches this mask
*/
@Override
public boolean test(@Nonnull User user) {
Sanity.nullCheck(user, "User cannot be null");
return this.test(user.getName());
}

/**
* Gets if the string matches this mask.
*
* @param string string
* @return true if string matches this mask
*/
public boolean test(@Nonnull String string) {
Sanity.nullCheck(string, "String cannot be null");
return this.pattern.matcher(string).matches();
}

@Override
public int hashCode() {
return (2 * this.string.hashCode()) + 5;
return Objects.hash(this.getNick(), this.getUser(), this.getHost());
}

@Override
public boolean equals(Object o) {
return (o instanceof Mask) && ((Mask) o).string.equals(this.string);
if (!(o instanceof Mask)) {
return false;
}

final Mask that = (Mask) o;
return Objects.equals(this.getNick(), that.getNick()) && Objects.equals(this.getUser(), that.getUser()) && Objects.equals(this.getHost(), that.getHost());
}

@Nonnull
@Override
public String toString() {
return new ToStringer(this).add("string", this.string).toString();
return new ToStringer(this).add("nick", this.getNick()).add("user", this.getUser()).add("host", this.getHost()).add("pattern", this.pattern).toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,10 @@ public static Pattern wildcardToPattern(@Nonnull String wildcardExpression) {
for (char character : wildcardExpression.toCharArray()) {
switch (character) {
case '*':
builder.append('.').append(character); // * to .*
break;
case '?':
builder.append('.').append(character); // * to .* and ? to .?
builder.append('.'); // ? to .
break;
case '<':
case '(':
Expand Down Expand Up @@ -203,6 +205,6 @@ public static Pattern wildcardToPattern(@Nonnull String wildcardExpression) {
}
builder.insert(0, '^');
builder.append('$');
return Pattern.compile(builder.toString());
return Pattern.compile(builder.toString(), Pattern.CASE_INSENSITIVE);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package org.kitteh.irc.client.library.implementation;

import org.kitteh.irc.client.library.Client;
import org.kitteh.irc.client.library.element.User;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Collections;
import java.util.Optional;
import java.util.Set;

public class TestUser implements User {
private final String nick;
private final String user;
private final String host;
private final Optional<String> account;

public TestUser(@Nonnull String nick, @Nonnull String user, @Nonnull String host, @Nullable String account) {
this.nick = nick;
this.user = user;
this.host = host;
this.account = Optional.ofNullable(account);
}

@Nonnull
@Override
public Client getClient() {
return null;
}

@Override
public long getCreationTime() {
return 0L;
}

@Override
public boolean isStale() {
return false;
}

@Nonnull
@Override
public String getMessagingName() {
return this.nick;
}

@Nonnull
@Override
public Optional<String> getAccount() {
return this.account;
}

@Nonnull
@Override
public Set<String> getChannels() {
return Collections.emptySet();
}

@Nonnull
@Override
public String getName() {
return this.nick + '!' + this.user + '@' + this.host;
}

@Nonnull
@Override
public String getHost() {
return this.host;
}

@Nonnull
@Override
public String getNick() {
return this.nick;
}

@Nonnull
@Override
public Optional<String> getOperatorInformation() {
return Optional.empty();
}

@Nonnull
@Override
public Optional<String> getRealName() {
return Optional.empty();
}

@Nonnull
@Override
public Optional<String> getServer() {
return Optional.empty();
}

@Nonnull
@Override
public String getUserString() {
return this.user;
}

@Override
public boolean isAway() {
return false;
}

@Nonnull
@Override
public String toString() {
return this.getName() + (this.account != null ? " (account: " + this.account + '}' : "");
}
}
Loading

0 comments on commit 353b3f0

Please sign in to comment.