From 4060da5324359ccda220bb9335df2c674a50c1a1 Mon Sep 17 00:00:00 2001 From: mbax Date: Tue, 5 Jul 2016 21:49:30 -0400 Subject: [PATCH] Ban information and tracking --- .../irc/client/library/element/Channel.java | 24 +++++ .../client/library/element/mode/ModeInfo.java | 94 +++++++++++++++++++ .../channel/ChannelModeInfoListEvent.java | 78 +++++++++++++++ .../library/implementation/ActorProvider.java | 66 +++++++++++++ .../library/implementation/EventListener.java | 73 ++++++++++++-- .../library/implementation/ModeData.java | 65 +++++++++++++ .../kitteh/irc/client/library/util/Mask.java | 77 +++++++++++++++ 7 files changed, 469 insertions(+), 8 deletions(-) create mode 100644 src/main/java/org/kitteh/irc/client/library/element/mode/ModeInfo.java create mode 100644 src/main/java/org/kitteh/irc/client/library/event/channel/ChannelModeInfoListEvent.java create mode 100644 src/main/java/org/kitteh/irc/client/library/util/Mask.java diff --git a/src/main/java/org/kitteh/irc/client/library/element/Channel.java b/src/main/java/org/kitteh/irc/client/library/element/Channel.java index cb9b8ddc0..8d94e0034 100644 --- a/src/main/java/org/kitteh/irc/client/library/element/Channel.java +++ b/src/main/java/org/kitteh/irc/client/library/element/Channel.java @@ -29,7 +29,9 @@ import org.kitteh.irc.client.library.command.TopicCommand; import org.kitteh.irc.client.library.element.mode.ChannelMode; import org.kitteh.irc.client.library.element.mode.ChannelUserMode; +import org.kitteh.irc.client.library.element.mode.ModeInfo; import org.kitteh.irc.client.library.element.mode.ModeStatusList; +import org.kitteh.irc.client.library.event.channel.ChannelModeInfoListEvent; import org.kitteh.irc.client.library.event.channel.ChannelUsersUpdatedEvent; import org.kitteh.irc.client.library.util.Sanity; @@ -83,6 +85,16 @@ default Optional getLatest() { return this.getClient().getChannel(this.getName()); } + /** + * Gets the tracked mode info for the channel, if tracked. + * + * @param mode type A mode to acquire + * @return list of mode info if tracked, empty if not tracked + * @throws IllegalArgumentException for null or non-type-A mode + */ + @Nonnull + Optional> getModeInfoList(@Nonnull ChannelMode mode); + /** * Gets the channel's current known modes. * @@ -241,6 +253,18 @@ default void part(@Nonnull String reason) { this.getClient().removeChannel(this.getName(), reason); } + /** + * Sets whether a particular type A mode should be tracked for this + * channel, and sends a request for the full list. + * + * @param mode mode to track + * @param track true to track, false to stop tracking + * @throws IllegalArgumentException for null or non-type-A mode + * @throws IllegalStateException if not in channel + * @see ChannelModeInfoListEvent + */ + void setModeInfoTracking(@Nonnull ChannelMode mode, boolean track); + /** * Attempts to set the topic of the channel. * diff --git a/src/main/java/org/kitteh/irc/client/library/element/mode/ModeInfo.java b/src/main/java/org/kitteh/irc/client/library/element/mode/ModeInfo.java new file mode 100644 index 000000000..a50823132 --- /dev/null +++ b/src/main/java/org/kitteh/irc/client/library/element/mode/ModeInfo.java @@ -0,0 +1,94 @@ +/* + * * Copyright (C) 2013-2016 Matt Baxter http://kitteh.org + * + * 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 org.kitteh.irc.client.library.element.mode; + +import org.kitteh.irc.client.library.Client; +import org.kitteh.irc.client.library.command.ChannelModeCommand; +import org.kitteh.irc.client.library.element.Channel; +import org.kitteh.irc.client.library.util.Mask; + +import javax.annotation.Nonnull; +import java.time.Instant; +import java.util.Optional; + +/** + * Represents a type A mode information entry. + */ +public interface ModeInfo { + /** + * Gets the name of the party listed as creating the entry. This may be a + * nickname, a service name, a server name, etc. + * + * @return name, if known, of who created this entry + */ + @Nonnull + Optional getCreator(); + + /** + * Gets the channel for which this entry exists. + * + * @return the channel + */ + @Nonnull + Channel getChannel(); + + /** + * Gets the client that received this information. + * + * @return the client + */ + @Nonnull + Client getClient(); + + /** + * Gets the mask. + * + * @return the mask + */ + @Nonnull + Mask getMask(); + + /** + * Gets the mode for which this info exists. + * + * @return the mode + */ + @Nonnull + ChannelMode getMode(); + + /** + * Gets the time at which this entry was created. + * + * @return creation time, if known + */ + @Nonnull + Optional getCreationTime(); + + /** + * Attempts to remove this item from the channel. + */ + default void remove() { + new ChannelModeCommand(this.getClient(), this.getChannel().getName()).add(false, this.getMode(), this.getMask().asString()).execute(); + } +} diff --git a/src/main/java/org/kitteh/irc/client/library/event/channel/ChannelModeInfoListEvent.java b/src/main/java/org/kitteh/irc/client/library/event/channel/ChannelModeInfoListEvent.java new file mode 100644 index 000000000..5bff58a1d --- /dev/null +++ b/src/main/java/org/kitteh/irc/client/library/event/channel/ChannelModeInfoListEvent.java @@ -0,0 +1,78 @@ +/* + * * Copyright (C) 2013-2016 Matt Baxter http://kitteh.org + * + * 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 org.kitteh.irc.client.library.event.channel; + +import org.kitteh.irc.client.library.Client; +import org.kitteh.irc.client.library.element.Channel; +import org.kitteh.irc.client.library.element.ServerMessage; +import org.kitteh.irc.client.library.element.mode.ChannelMode; +import org.kitteh.irc.client.library.element.mode.ModeInfo; +import org.kitteh.irc.client.library.event.abstractbase.ChannelEventBase; + +import javax.annotation.Nonnull; +import java.util.Collections; +import java.util.List; + +/** + * A list of mode info is available! + */ +public class ChannelModeInfoListEvent extends ChannelEventBase { + private final ChannelMode mode; + private final List info; + + /** + * Constructs the event. + * + * @param client client for which this is occurring + * @param originalMessages original messages + * @param channel channel with this info + * @param mode mode for which the info exists + * @param info list of info + */ + public ChannelModeInfoListEvent(@Nonnull Client client, @Nonnull List originalMessages, @Nonnull Channel channel, @Nonnull ChannelMode mode, @Nonnull List info) { + super(client, originalMessages, channel); + this.mode = mode; + this.info = Collections.unmodifiableList(info); + } + + /** + * Gets the mode info's mode. + * + * @return mode + */ + @Nonnull + public ChannelMode getMode() { + return this.mode; + } + + /** + * Gets the channel's mode info. + * + * @return info + */ + @Nonnull + public List getModeInfo() { + return this.info; + } +} diff --git a/src/main/java/org/kitteh/irc/client/library/implementation/ActorProvider.java b/src/main/java/org/kitteh/irc/client/library/implementation/ActorProvider.java index 1263f4b1b..2823a56a4 100644 --- a/src/main/java/org/kitteh/irc/client/library/implementation/ActorProvider.java +++ b/src/main/java/org/kitteh/irc/client/library/implementation/ActorProvider.java @@ -25,6 +25,7 @@ import org.kitteh.irc.client.library.Client; +import org.kitteh.irc.client.library.command.ChannelModeCommand; import org.kitteh.irc.client.library.element.Actor; import org.kitteh.irc.client.library.element.Channel; import org.kitteh.irc.client.library.element.ISupportParameter; @@ -33,6 +34,7 @@ import org.kitteh.irc.client.library.element.User; import org.kitteh.irc.client.library.element.mode.ChannelMode; import org.kitteh.irc.client.library.element.mode.ChannelUserMode; +import org.kitteh.irc.client.library.element.mode.ModeInfo; 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; @@ -48,6 +50,7 @@ import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; @@ -156,6 +159,8 @@ synchronized T snapshot(@Nonnull Supplier supplier) { class IRCChannel extends IRCStaleable { private final Map> channelModes = new HashMap<>(); + private final Map> modeInfoLists = new HashMap<>(); + private final Set trackedModes = new HashSet<>(); private final Map> modes; private volatile boolean fullListReceived; private long lastWho = System.currentTimeMillis(); @@ -209,6 +214,35 @@ IRCChannelSnapshot snapshot() { return super.snapshot(() -> new IRCChannelSnapshot(IRCChannel.this, new IRCChannelTopicSnapshot(IRCChannel.this.topicTime, IRCChannel.this.topic, IRCChannel.this.topicSetter))); } + void trackMode(@Nonnull ChannelMode mode, boolean track) { + if (track && this.trackedModes.add(mode.getChar())) { + new ChannelModeCommand(ActorProvider.this.client, this.getName()).add(true, mode).execute(); + } else if (!track) { + this.trackedModes.remove(mode.getChar()); + } + } + + void setModeInfoList(char character, @Nonnull List modeInfoList) { + if (this.trackedModes.contains(character)) { + this.modeInfoLists.put(character, modeInfoList); + } + this.markStale(); + } + + void trackModeInfo(boolean add, @Nonnull ModeInfo modeInfo) { + if (add) { + this.modeInfoLists.get(modeInfo.getMode().getChar()).add(modeInfo); + } else { + Iterator iterator = this.modeInfoLists.get(modeInfo.getMode().getChar()).iterator(); + while (iterator.hasNext()) { + if (modeInfo.getMask().equals(iterator.next().getMask())) { + iterator.remove(); + return; + } + } + } + } + void trackUser(@Nonnull IRCUser user, @Nonnull Set modes) { ActorProvider.this.trackUser(user); this.setModes(user.getNick(), modes); @@ -276,6 +310,13 @@ private void setModes(@Nonnull String nick, @Nonnull Set modes) } void updateChannelModes(ModeStatusList statusList) { + statusList.getStatuses().stream().filter(status -> (status.getMode() instanceof ChannelUserMode) && (status.getParameter().isPresent())).forEach(status -> { + if (status.isSetting()) { + this.trackUserModeAdd(status.getParameter().get(), (ChannelUserMode) status.getMode()); + } else { + this.trackUserModeRemove(status.getParameter().get(), (ChannelUserMode) status.getMode()); + } + }); statusList.getStatuses().stream().filter(status -> !(status.getMode() instanceof ChannelUserMode) && (status.getMode().getType() != ChannelMode.Type.A_MASK)).forEach(status -> { if (status.isSetting()) { this.channelModes.put(status.getMode().getChar(), status); @@ -331,6 +372,7 @@ public String toString() { class IRCChannelSnapshot extends IRCActorSnapshot implements Channel { private final ModeStatusList channelModes; + private final Map> modeInfoLists; private final Map> modes; private final List names; private final Map nickMap; @@ -343,6 +385,11 @@ private IRCChannelSnapshot(@Nonnull IRCChannel channel, @Nonnull Topic topic) { this.complete = channel.fullListReceived; this.channelModes = ModeStatusList.of(channel.channelModes.values()); this.topic = topic; + this.modeInfoLists = new HashMap<>(); + for (Map.Entry> entry : channel.modeInfoLists.entrySet()) { + this.modeInfoLists.put(entry.getKey(), Collections.unmodifiableList(new ArrayList<>(entry.getValue()))); + } + channel.trackedModes.stream().filter(character -> !this.modeInfoLists.containsKey(character)).forEach(character -> this.modeInfoLists.put(character, Collections.unmodifiableList(new ArrayList<>()))); Map> newModes = new CIKeyMap<>(ActorProvider.this.client); Optional prefix = ActorProvider.this.client.getServerInfo().getISupportParameter("PREFIX", ISupportParameter.Prefix.class); Comparator comparator = prefix.isPresent() ? Comparator.comparingInt(prefix.get().getModes()::indexOf) : null; @@ -369,6 +416,14 @@ public String getMessagingName() { return this.getName(); } + @Nonnull + @Override + public Optional> getModeInfoList(@Nonnull ChannelMode mode) { + Sanity.nullCheck(mode, "Mode cannot be null"); + Sanity.truthiness(mode.getType() == ChannelMode.Type.A_MASK, "Mode type must be A, found " + mode.getType()); + return Optional.ofNullable(this.modeInfoLists.get(mode.getChar())); + } + @Override @Nonnull public ModeStatusList getModes() { @@ -412,6 +467,17 @@ public boolean hasCompleteUserData() { return this.complete; } + @Override + public void setModeInfoTracking(@Nonnull ChannelMode mode, boolean track) { + Sanity.nullCheck(mode, "Mode cannot be null"); + Sanity.truthiness(mode.getType() == ChannelMode.Type.A_MASK, "Mode type must be A, found " + mode.getType()); + IRCChannel channel = ActorProvider.this.getTrackedChannel(this.getName()); + if (channel == null) { + throw new IllegalStateException("Not currently in channel " + this.getName()); + } + channel.trackMode(mode, track); + } + @Override public int hashCode() { // RFC 2812 section 1.3 'Channel names are case insensitive.' diff --git a/src/main/java/org/kitteh/irc/client/library/implementation/EventListener.java b/src/main/java/org/kitteh/irc/client/library/implementation/EventListener.java index 1faa1eb91..53e41dec9 100644 --- a/src/main/java/org/kitteh/irc/client/library/implementation/EventListener.java +++ b/src/main/java/org/kitteh/irc/client/library/implementation/EventListener.java @@ -27,12 +27,14 @@ import net.engio.mbassy.listener.References; import org.kitteh.irc.client.library.command.CapabilityRequestCommand; import org.kitteh.irc.client.library.element.CapabilityState; +import org.kitteh.irc.client.library.element.Channel; import org.kitteh.irc.client.library.element.Server; import org.kitteh.irc.client.library.element.ServerMessage; import org.kitteh.irc.client.library.element.User; import org.kitteh.irc.client.library.element.WhoisData; import org.kitteh.irc.client.library.element.mode.ChannelMode; import org.kitteh.irc.client.library.element.mode.ChannelUserMode; +import org.kitteh.irc.client.library.element.mode.ModeInfo; import org.kitteh.irc.client.library.element.mode.ModeStatusList; import org.kitteh.irc.client.library.element.mode.UserMode; import org.kitteh.irc.client.library.event.abstractbase.CapabilityNegotiationResponseEventBase; @@ -50,6 +52,7 @@ import org.kitteh.irc.client.library.event.channel.ChannelKnockEvent; import org.kitteh.irc.client.library.event.channel.ChannelMessageEvent; import org.kitteh.irc.client.library.event.channel.ChannelModeEvent; +import org.kitteh.irc.client.library.event.channel.ChannelModeInfoListEvent; import org.kitteh.irc.client.library.event.channel.ChannelNamesUpdatedEvent; import org.kitteh.irc.client.library.event.channel.ChannelNoticeEvent; import org.kitteh.irc.client.library.event.channel.ChannelPartEvent; @@ -94,6 +97,8 @@ import org.kitteh.irc.client.library.util.ToStringer; import javax.annotation.Nonnull; +import java.time.DateTimeException; +import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -488,6 +493,63 @@ public void namesComplete(ClientReceiveNumericEvent event) { } } + private final List banMessages = new ArrayList<>(); + private final List bans = new ArrayList<>(); + + @NumericFilter(367) // BANLIST + @Handler(priority = Integer.MAX_VALUE - 1) + public void banList(ClientReceiveNumericEvent event) { + if (event.getParameters().size() < 3) { + this.trackException(event, "BANLIST response of incorrect length"); + return; + } + ActorProvider.IRCChannel channel = this.client.getActorProvider().getChannel(event.getParameters().get(1)); + if (channel != null) { + this.banMessages.add(messageFromEvent(event)); + String creator = event.getParameters().size() > 3 ? event.getParameters().get(3) : null; + Instant creationTime = null; + if (event.getParameters().size() > 4) { + try { + creationTime = Instant.ofEpochSecond(Integer.parseInt(event.getParameters().get(4))); + } catch (NumberFormatException | DateTimeException ignored) { + } + } + Optional ban = this.client.getServerInfo().getChannelMode('b'); + if (ban.isPresent()) { + this.bans.add(new ModeData.IRCModeInfo(this.client, channel.snapshot(), ban.get(), event.getParameters().get(2), Optional.ofNullable(creator), Optional.ofNullable(creationTime))); + } else { + this.trackException(event, "BANLIST can't list bans if there's no 'b' mode"); + } + } else { + this.trackException(event, "BANLIST response sent for invalid channel name"); + } + } + + @NumericFilter(368) // End of ban list + @Handler(priority = Integer.MAX_VALUE - 1) + public void banListEnd(ClientReceiveNumericEvent event) { + if (event.getParameters().size() < 2) { + this.trackException(event, "BANLIST response of incorrect length"); + return; + } + ActorProvider.IRCChannel channel = this.client.getActorProvider().getChannel(event.getParameters().get(1)); + if (channel != null) { + this.banMessages.add(messageFromEvent(event)); + Optional ban = this.client.getServerInfo().getChannelMode('b'); + if (ban.isPresent()) { + List bans = new ArrayList<>(this.bans); + this.fire(new ChannelModeInfoListEvent(this.client, this.banMessages, channel.snapshot(), ban.get(), bans)); + channel.setModeInfoList('b', bans); + } else { + this.trackException(event, "BANLIST can't list bans if there's no 'b' mode"); + } + this.bans.clear(); + this.banMessages.clear(); + } else { + this.trackException(event, "BANLIST response sent for invalid channel name"); + } + } + private final List motd = new ArrayList<>(); private final List motdMessages = new ArrayList<>(); @@ -904,14 +966,9 @@ public void mode(ClientReceiveCommandEvent event) { this.trackException(event, e.getMessage()); return; } - this.fire(new ChannelModeEvent(this.client, event.getOriginalMessages(), event.getActor(), channel.snapshot(), statusList)); - statusList.getStatuses().stream().filter(status -> (status.getMode() instanceof ChannelUserMode) && (status.getParameter().isPresent())).forEach(status -> { - if (status.isSetting()) { - channel.trackUserModeAdd(status.getParameter().get(), (ChannelUserMode) status.getMode()); - } else { - channel.trackUserModeRemove(status.getParameter().get(), (ChannelUserMode) status.getMode()); - } - }); + Channel channelSnapshot = channel.snapshot(); + this.fire(new ChannelModeEvent(this.client, event.getOriginalMessages(), event.getActor(), channelSnapshot, statusList)); + statusList.getStatuses().stream().filter(status -> status.getMode().getType() == ChannelMode.Type.A_MASK).forEach(status -> channel.trackModeInfo(status.isSetting(), new ModeData.IRCModeInfo(this.client, channelSnapshot, status.getMode(), status.getParameter().get(), Optional.of(event.getActor().getName()), Optional.of(Instant.now())))); channel.updateChannelModes(statusList); } else { this.trackException(event, "MODE message sent for invalid target"); diff --git a/src/main/java/org/kitteh/irc/client/library/implementation/ModeData.java b/src/main/java/org/kitteh/irc/client/library/implementation/ModeData.java index bb27e9681..c270b144e 100644 --- a/src/main/java/org/kitteh/irc/client/library/implementation/ModeData.java +++ b/src/main/java/org/kitteh/irc/client/library/implementation/ModeData.java @@ -24,12 +24,17 @@ package org.kitteh.irc.client.library.implementation; import org.kitteh.irc.client.library.Client; +import org.kitteh.irc.client.library.element.Channel; import org.kitteh.irc.client.library.element.mode.ChannelMode; import org.kitteh.irc.client.library.element.mode.ChannelUserMode; +import org.kitteh.irc.client.library.element.mode.ModeInfo; import org.kitteh.irc.client.library.element.mode.UserMode; +import org.kitteh.irc.client.library.util.Mask; import org.kitteh.irc.client.library.util.ToStringer; import javax.annotation.Nonnull; +import java.time.Instant; +import java.util.Optional; final class ModeData { abstract static class IRCModeBase { @@ -103,4 +108,64 @@ public String toString() { return new ToStringer(this).add("client", this.getClient()).add("char", this.getChar()).toString(); } } + + static class IRCModeInfo implements ModeInfo { + private final Client client; + private final Optional creationTime; + private final Optional creator; + private final Channel channel; + private final Mask mask; + private final ChannelMode mode; + + IRCModeInfo(@Nonnull Client client, @Nonnull Channel channel, @Nonnull ChannelMode mode, @Nonnull String mask, @Nonnull Optional creator, @Nonnull Optional creationTime) { + this.client = client; + this.creator = creator; + this.channel = channel; + this.mask = Mask.fromString(mask); + this.creationTime = creationTime; + this.mode = mode; + } + + @Nonnull + @Override + public Optional getCreator() { + return this.creator; + } + + @Nonnull + @Override + public Channel getChannel() { + return this.channel; + } + + @Nonnull + @Override + public Client getClient() { + return this.client; + } + + @Nonnull + @Override + public Mask getMask() { + return this.mask; + } + + @Nonnull + @Override + public ChannelMode getMode() { + return this.mode; + } + + @Nonnull + @Override + public Optional getCreationTime() { + return this.creationTime; + } + + @Nonnull + @Override + public String toString() { + return new ToStringer(this).add("client", this.client).add("channel", this.channel).add("mode", this.mode).add("mask", this.mask).add("creator", this.creator).add("creationTime", this.creationTime).toString(); + } + } } diff --git a/src/main/java/org/kitteh/irc/client/library/util/Mask.java b/src/main/java/org/kitteh/irc/client/library/util/Mask.java new file mode 100644 index 000000000..e1567c3f9 --- /dev/null +++ b/src/main/java/org/kitteh/irc/client/library/util/Mask.java @@ -0,0 +1,77 @@ +/* + * * Copyright (C) 2013-2016 Matt Baxter http://kitteh.org + * + * 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 org.kitteh.irc.client.library.util; + +import org.kitteh.irc.client.library.element.User; + +import javax.annotation.Nonnull; + +/** + * Represents a mask that can match a {@link User}. + */ +public class Mask { + /** + * Creates a Mask from a given String. + * + * @param string string + * @return mask from string + */ + @Nonnull + public static Mask fromString(@Nonnull String string) { + Sanity.nullCheck(string, "String cannot be null"); + return new Mask(string); + } + + private final String string; + + private Mask(@Nonnull String string) { + this.string = string; + } + + /** + * Gets the String representation of this mask. + * + * @return string + */ + @Nonnull + public String asString() { + return this.string; + } + + @Override + public int hashCode() { + return (2 * this.string.hashCode()) + 5; + } + + @Override + public boolean equals(Object o) { + return (o instanceof Mask) && ((Mask) o).string.equals(this.string); + } + + @Nonnull + @Override + public String toString() { + return new ToStringer(this).add("string", this.string).toString(); + } +}