org.pircbotx.Channel.java Source code

Java tutorial

Introduction

Here is the source code for org.pircbotx.Channel.java

Source

/**
 * Copyright (C) 2010-2014 Leon Blakey <lord.quackstar at gmail.com>
 *
 * This file is part of PircBotX.
 *
 * PircBotX is free software: you can redistribute it and/or modify it under the
 * terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 *
 * PircBotX is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * PircBotX. If not, see <http://www.gnu.org/licenses/>.
 */
package org.pircbotx;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterators;
import com.google.common.collect.PeekingIterator;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.concurrent.AtomicSafeInitializer;
import org.apache.commons.lang3.concurrent.ConcurrentException;
import org.pircbotx.hooks.managers.ThreadedListenerManager;
import org.pircbotx.output.OutputChannel;
import org.pircbotx.snapshot.ChannelSnapshot;

/**
 * Represents a Channel that we're joined to.
 *
 * @author Leon Blakey
 */
@ToString(doNotUseGetters = true)
@EqualsAndHashCode(of = { "name", "bot" })
@Slf4j
@Getter
@Setter(AccessLevel.PROTECTED)
public class Channel implements Comparable<Channel> {
    /**
     * The name of the channel. Will never change
     */
    protected final String name;
    /**
     * Unique UUID for this channel <i>instance</i>
     */
    protected final UUID channelId;
    /**
     * The bot that this channel came from
     */
    protected final PircBotX bot;
    //Output is lazily created since it might not ever be used
    @Getter(AccessLevel.NONE)
    protected final AtomicSafeInitializer<OutputChannel> output = new AtomicSafeInitializer<OutputChannel>() {
        @Override
        protected OutputChannel initialize() {
            return bot.getConfiguration().getBotFactory().createOutputChannel(bot, Channel.this);
        }
    };
    @Setter(AccessLevel.NONE)
    protected String mode = "";
    /**
     * The current channel topic
     */
    protected String topic = "";
    /**
     * Timestamp of when the topic was created. Defaults to 0
     */
    protected long topicTimestamp;
    /**
     * Timestamp of when channel was created. Defaults to 0
     */
    protected long createTimestamp;
    /**
     * The user who set the topic. Default is blank
     */
    protected UserHostmask topicSetter;
    /**
     * Moderated (+m) status
     */
    protected boolean moderated = false;
    /**
     * No external messages (+n) status
     */
    protected boolean noExternalMessages = false;
    /**
     * Invite only (+i) status
     */
    protected boolean inviteOnly = false;
    /**
     * Secret (+s) status
     */
    protected boolean secret = false;
    /**
     * Private (+p) status
     */
    protected boolean channelPrivate = false;
    @Getter(AccessLevel.NONE)
    protected boolean topicProtection = false;
    /**
     * Channel limit (+l #)
     */
    protected int channelLimit = -1;
    /**
     * Channel key (+k)
     */
    protected String channelKey = null;
    @Getter(AccessLevel.NONE)
    @Setter(AccessLevel.NONE)
    protected CountDownLatch modeChangeLatch = null;
    @Getter(AccessLevel.NONE)
    protected final Object modeChangeLock = new Object();

    protected Channel(PircBotX bot, String name) {
        this.bot = bot;
        this.name = name;
        this.channelId = UUID.randomUUID();
    }

    /**
     * Used by ChannelSnapshot
     * @param channel 
     */
    protected Channel(Channel channel) {
        this.bot = channel.bot;
        this.name = channel.name;
        this.channelId = channel.channelId;
    }

    protected UserChannelDao<User, Channel> getDao() {
        return bot.getUserChannelDao();
    }

    /**
     * Send a line to the channel.
     *
     * @return A {@link OutputChannel} for this channel
     */
    public OutputChannel send() {
        try {
            return output.get();
        } catch (ConcurrentException ex) {
            throw new RuntimeException("Could not generate OutputChannel for " + getName(), ex);
        }
    }

    protected void parseMode(String rawMode) {
        synchronized (modeChangeLock) {
            if (rawMode.contains(" ") || (mode != null && mode.contains(" "))) {
                //Mode contains arguments which are impossible to parse.
                //Could be a ban command (we shouldn't use this), channel key (should, but where), etc
                //Need to ask server
                log.trace("Unknown args in channel {} mode '{}', getting fresh mode", name, rawMode);
                mode = null;
                send().getMode();
            } else {
                //Parse mode by switching between removing and adding by the existance of a + or - sign
                boolean adding = true;
                for (char curChar : rawMode.toCharArray())
                    if (curChar == '-')
                        adding = false;
                    else if (curChar == '+')
                        adding = true;
                    else if (adding)
                        mode = mode + curChar;
                    else
                        mode = mode.replace(Character.toString(curChar), "");
            }
        }
    }

    /**
     * Gets the channel mode. If mode is simple (no arguments), this will return
     * immediately. If its not (mode with arguments, eg channel key), then asks
     * the server for the correct mode, waiting until it gets a response
     * <p>
     * <b>WARNING:</b> Because of the last checking, a threaded listener manager
     * like {@link ThreadedListenerManager} is required. Using a single threaded
     * listener manager like
     * {@link org.pircbotx.hooks.managers.GenericListenerManager} will mean this
     * method <i>never returns</i>!
     *
     * @return A known good mode, either immediately or soon.
     */
    public String getMode() {
        synchronized (modeChangeLock) {
            if (mode != null)
                return mode;
        }

        log.debug("Pausing channel {} getMode() until server responds with fresh mode", name);

        //While unlikely, mode could be reset. Continuously try
        int counter = 0;
        while (true) {
            log.trace("Attempt #{} to get mode for channel {}", counter++, name);
            try {
                CountDownLatch modeWait;
                synchronized (modeChangeLock) {
                    modeWait = modeChangeLatch;
                }
                modeWait.await();
            } catch (InterruptedException e) {
                throw new RuntimeException("Waiting for mode response for channel " + name + " interrupted", e);
            }

            synchronized (modeChangeLock) {
                if (mode != null)
                    return mode;
            }
        }
    }

    public boolean containsMode(char modeLetter) {
        String modeLetters = StringUtils.split(getMode(), ' ')[0];
        return StringUtils.contains(modeLetters, modeLetter);
    }

    /**
     * Check if the channel has topic protection (+t) set.
     *
     * @return True if +t
     */
    public boolean hasTopicProtection() {
        return topicProtection;
    }

    /**
     * Get all levels the user holds in this channel.
     *
     * @param user The user to get the levels of
     * @return An <b>immutable copy</b> of the levels the user holds
     */
    public ImmutableSortedSet<UserLevel> getUserLevels(User user) {
        return getDao().getLevels(this, user);
    }

    /**
     * Get all users that don't have any special status in this channel. This
     * means that they aren't ops, have voice, superops, halops, or owners in
     * this channel
     *
     * @return An <b>immutable copy</b> of normal users
     */
    public ImmutableSortedSet<User> getNormalUsers() {
        return getDao().getNormalUsers(this);
    }

    /**
     * Get all opped users in this channel.
     *
     * @return An <b>immutable copy</b> of opped users
     */
    public ImmutableSortedSet<User> getOps() {
        return getDao().getUsers(this, UserLevel.OP);
    }

    /**
     * Get all voiced users in this channel.
     *
     * @return An <b>immutable copy</b> of voiced users
     */
    public ImmutableSortedSet<User> getVoices() {
        return getDao().getUsers(this, UserLevel.VOICE);
    }

    /**
     * Get all users with Owner status in this channel.
     *
     * @return An <b>immutable copy</b> of users with Owner status
     */
    public ImmutableSortedSet<User> getOwners() {
        return getDao().getUsers(this, UserLevel.OWNER);
    }

    /**
     * Get all users with Half Operator status in this channel.
     *
     * @return An <b>immutable copy</b> of users with Half Operator status
     */
    public ImmutableSortedSet<User> getHalfOps() {
        return getDao().getUsers(this, UserLevel.HALFOP);
    }

    /**
     * Get all users with Super Operator status in this channel.
     *
     * @return An <b>immutable copy</b> of users with Super Operator status
     */
    public ImmutableSortedSet<User> getSuperOps() {
        return getDao().getUsers(this, UserLevel.SUPEROP);
    }

    /**
     * Sets the mode of the channel. If there is a getMode() waiting on this,
     * fire it.
     *
     * @param mode
     */
    protected void setMode(String mode, ImmutableList<String> modeParsed) {
        synchronized (modeChangeLock) {
            this.mode = mode;

            //Parse out mode
            PeekingIterator<String> params = Iterators.peekingIterator(modeParsed.iterator());

            //Process modes letter by letter, grabbing paramaters as needed
            boolean adding = true;
            String modeLetters = params.next();
            for (int i = 0; i < modeLetters.length(); i++) {
                char curModeChar = modeLetters.charAt(i);
                if (curModeChar == '+')
                    adding = true;
                else if (curModeChar == '-')
                    adding = false;
                else {
                    ChannelModeHandler modeHandler = bot.getConfiguration().getChannelModeHandlers()
                            .get(curModeChar);
                    if (modeHandler != null)
                        modeHandler.handleMode(bot, this, null, null, params, adding, false);
                }
            }

            if (modeChangeLatch != null)
                modeChangeLatch.countDown();
        }
    }

    /**
     * Get all users in this channel. }
     *
     * @return An <i>Unmodifiable</i> Set of users in this channel
     */
    public ImmutableSortedSet<User> getUsers() {
        return getDao().getUsers(this);
    }

    /**
     * Get all the user's nicks in this channel
     *
     * @return An <i>Unmodifiable</i> Set of user's nicks as String in this
     * channel
     */
    public ImmutableSortedSet<String> getUsersNicks() {
        ImmutableSortedSet.Builder<String> builder = ImmutableSortedSet.naturalOrder();
        for (User curUser : getDao().getUsers(this))
            builder.add(curUser.getNick());
        return builder.build();
    }

    /**
     * Get the user that set the topic. As the user may or may not be in the
     * channel return as a string
     *
     * @return The user that set the topic in String format
     */
    public UserHostmask getTopicSetter() {
        return topicSetter;
    }

    /**
     * Checks if the given user is an Operator in this channel
     *
     * @return True if the user is an Operator, false if not
     */
    public boolean isOp(User user) {
        return getDao().levelContainsUser(UserLevel.OP, this, user);
    }

    /**
     * Checks if the given user has Voice in this channel.
     *
     * @return True if the user has Voice, false if not
     */
    public boolean hasVoice(User user) {
        return getDao().levelContainsUser(UserLevel.VOICE, this, user);
    }

    /**
     * Checks if the given user is a Super Operator in this channel.
     *
     * @return True if the user is a Super Operator, false if not
     */
    public boolean isSuperOp(User user) {
        return getDao().levelContainsUser(UserLevel.SUPEROP, this, user);
    }

    /**
     * Checks if the given user is an Owner in this channel.
     *
     * @return True if the user is an Owner, false if not
     */
    public boolean isOwner(User user) {
        return getDao().levelContainsUser(UserLevel.OWNER, this, user);
    }

    /**
     * Checks if the given user is a Half Operator in this channel.
     *
     * @return True if the user is a Half Operator, false if not
     */
    public boolean isHalfOp(User user) {
        return getDao().levelContainsUser(UserLevel.HALFOP, this, user);
    }

    /**
     * Create an immutable snapshot of this channel.
     *
     * @return Immutable Channel copy minus the DAO
     */
    public ChannelSnapshot createSnapshot() {
        return new ChannelSnapshot(this, mode);
    }

    /**
     * Compare {@link #getName()} with {@link String#compareToIgnoreCase(java.lang.String)
     * }. This is useful for sorting lists of Channel objects.
     *
     * @param other Other channel to compare to
     * @return the result of calling compareToIgnoreCase on channel names.
     */
    public int compareTo(Channel other) {
        return getName().compareToIgnoreCase(other.getName());
    }
}