Package org.tsbs.manager

Source Code of org.tsbs.manager.BotManager

package org.tsbs.manager;

import com.github.theholywaffle.teamspeak3.TS3Api;
import com.github.theholywaffle.teamspeak3.TS3Config;
import com.github.theholywaffle.teamspeak3.TS3Query;
import com.github.theholywaffle.teamspeak3.api.wrapper.Channel;
import org.tsbs.core.CallableFunction;
import org.tsbs.core.CommandHandler;
import org.tsbs.core.TS3BotChannelListener;
import org.tsbs.manager.exceptions.DirectoryNotFoundException;
import org.tsbs.manager.exceptions.UnknownChannelException;
import org.tsbs.manager.exceptions.UnknownFunctionException;
import org.tsbs.util.ApiUtils;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* A BotManager is used to handle multiple TS3ChannelListener, CommandHandler and the functions they use.

*
* @author falx
* @version v0.1a
*/

public class BotManager
{

    // -------------------------------------------------------------------------------------------------------------- //

                                        // ------------------------- //
                                        // -- static declarations -- //
                                        // ------------------------- //

    // -------------------------------------------------------------------------------------------------------------- //




    // logger used by this class
    private static final Logger sfLogger = Logger.getLogger( BotManager.class.getName() );

    {
        sfLogger.setLevel( Level.ALL );
    }

    // URL to the internal bin - / - package where the internal implemented functions are saved - as class files
    private static final URL PACKAGE_DIRECTORY    = BotManager.class.getClassLoader().getResource( "org/tsbs/functions" );

    // qualifying prefix for the classes which are implemented internally. It is needed to load the classes using the ClassLoader!
    private static final String PACKAGE_QUALIFIER =  "org.tsbs.functions.";





    // -------------------------------------------------------------------------------------------------------------- //

                                        // ------------------------- //
                                        // -- member declarations -- //
                                        // ------------------------- //

    // -------------------------------------------------------------------------------------------------------------- //




    // URLClassLoader is used to load all functions, defined in class files, from the specified directories
    private URLClassLoader mUrlClassLoader;

    // contains all functions found in the specified directories
    private HashMap<Class<? extends CallableFunction>, FunctionWrapper> mFunctionMap;

    // contains all channels, where a TS§BotChannelListener is active
    private HashMap<Integer, ChannelWrapper> mChannelMap;

    // contains configuration data
    private BotConfiguration mBotConfiguration;

    // CommandHandler used by the TS3BotChannelListener
    private CommandHandler mCommandHandler;

    // objects to initially set up the TS3-Query API
    // this Query and Api is only used to connect to the server initially and
    // to get all needed informations from the server (e.g. channelList)
    private TS3Query mQuery;
    private TS3Api   mApi; // created for a more simple handling

    // FileNameFilter used to load the class files which contain the function classes
    private FilenameFilter mClassFileFilter = (dir, name) -> { return name.endsWith( ".class" ); };

    // indicates it the CommandHandler is running and if the channelListeners are running
    private volatile boolean mIsRunning;




    // -------------------------------------------------------------------------------------------------------------- //

                                            // ------------------ //
                                            // -- Constructors -- //
                                            // ------------------ //

    // -------------------------------------------------------------------------------------------------------------- //




    private BotManager( BotConfiguration botConfiguration )
    {
        this.mFunctionMap      = new HashMap<>();
        this.mChannelMap       = new HashMap<>();
        this.mBotConfiguration = botConfiguration;
        this.mIsRunning        = false;
    }




    // -------------------------------------------------------------------------------------------------------------- //

                                         // -------------------------------- //
                                         // -- Factory / Creation methods -- //
                                         // -------------------------------- //

    // -------------------------------------------------------------------------------------------------------------- //




    /**
     *
     * @param botConfiguration
     * @return
     * @throws DirectoryNotFoundException
     */
    public static BotManager createBotManager( BotConfiguration botConfiguration ) throws FileNotFoundException, MalformedURLException
    {
        BotManager manager     = new BotManager( botConfiguration );
        boolean retSuccessful;

        String logPath = manager.mBotConfiguration.getLoggingFilePath();
        if( logPath != null )
        {
            try
            {
                sfLogger.addHandler( new FileHandler( logPath ) );
                sfLogger.log( Level.CONFIG, "Started logging to " + logPath );
            }
            catch( IOException exc )
            {
                sfLogger.log( Level.WARNING, "Could not start logging to " + logPath, exc );
            }
        }
        else
            sfLogger.log( Level.INFO, "Not logging-file defined. Log will printed only on console!" );

        sfLogger.log( Level.INFO, "Starting to set up the BotManager..." );

        // if there is a path to external define functions: check if the path is valid and if it exist
        // and initiated the URLClassLoader with it, so the functions can be loaded.
        // if there is not path the class loader should be initiated without any external path
        URL externalFunctionURL;
        String functionDirPath = manager.mBotConfiguration.getFunctionDirectory();
        if( functionDirPath != null )
        {
            try
            {
                externalFunctionURL = new URL( functionDirPath );
            }
            catch ( MalformedURLException exc )
            {
                sfLogger.log( Level.SEVERE, "MalformedURLException caused by the " + functionDirPath, exc );
                throw exc;
            }

            File f = new File( externalFunctionURL.getFile() );
            if( !f.exists() || !f.isDirectory() )
            {
                FileNotFoundException exc = new FileNotFoundException( "The Directory " + externalFunctionURL.getFile() +
                                                                        " was not found!" );
                sfLogger.log( Level.SEVERE, exc.getMessage(), exc );
            }

            manager.initiateURLClassLoader( new URL[]{ externalFunctionURL } );
        }
        else
        {
            sfLogger.log( Level.INFO, "No directory for external functions found." );
            manager.initiateURLClassLoader( new URL[]{ } );
        }

        manager.loadInternalFunctions();
        manager.loadExternalFunctions();
        manager.initiateCommandHandler();
        manager.initialConnect();
        manager.initiateChannelMap();
        return manager;
    }




    // ------------------------------------------- private methods -------------------------------------------------- //

    // -------------------------------------------------------------------------------------------------------------- //

                                            // ------------------------ //
                                            // -- initiation methods -- //
                                            // ------------------------ //

    // -------------------------------------------------------------------------------------------------------------- //




    // initiates the URLClassLoader with the url given as arguments plus the package url where
    // all standartfunctions are saved
    private void initiateURLClassLoader( URL[] urls )
    {
        urls = Arrays.copyOf( urls, urls.length + 1 );
        urls[ urls.length - 1 ] = PACKAGE_DIRECTORY;
        mUrlClassLoader = URLClassLoader.newInstance( urls );

        sfLogger.log( Level.INFO, "ClassLoader initiated." );
    }


    // loading all internal implemented functions
    private void loadInternalFunctions()
    {
        File[] files = new File( PACKAGE_DIRECTORY.getFile() ).listFiles( mClassFileFilter );;
        String fileName;
        String className;

        for ( File f : files )
        {
            // differentiate between class- and fileName because the className needs to be
            // fully qualified, but the name for the function should be the not fully qualified name
            fileName  = f.getName().substring( 0, f.getName().indexOf( ".class" ) );
            className = PACKAGE_QUALIFIER + fileName;
            CallableFunction function = loadFunction( className );
            if( function != null )
            {
                mFunctionMap.put( function.getClass(), new FunctionWrapper( function.getClass(), f, fileName, false ) );
                sfLogger.log( Level.INFO, "Internal function \"" + fileName + "\" saved to the functionMap." );
            }
        }
        sfLogger.log( Level.INFO, "Internal functions loaded." );
    }


    // loading all external defined functions, if there are any defined
    private void loadExternalFunctions()
    {
        String externalFunctionPath = mBotConfiguration.getFunctionDirectory();

        // if there is not path to external functions exit this method, because there's nothing todo
        if( externalFunctionPath == null )
        {
            sfLogger.log( Level.WARNING, "No path to external define functions found!" );
            return;
        }

        File[] files = new File( externalFunctionPath ).listFiles( mClassFileFilter );
        String className;

        for( File f : files )
        {
            className = f.getName().substring( 0, f.getName().indexOf( ".class" ) );
            CallableFunction function = loadFunction( className );
            if( function != null )
            {
                mFunctionMap.put( function.getClass(), new FunctionWrapper( function.getClass(), f, className, false ) );
                sfLogger.log( Level.INFO, "External function \"" + className + "\" saved to the functionMap." );
            }
        }
        sfLogger.log( Level.INFO, "External functions loaded." );
    }


    // loads a function using the ClassLoader. Which function should be loaded is specified by className
    // returns null and lo  gs a failure if there is no function/class like className
    private CallableFunction loadFunction( String className )
    {
        CallableFunction function = null;
        try
        {
            function = (CallableFunction)mUrlClassLoader.loadClass( className ).newInstance();
            sfLogger.log( Level.INFO, "Function \"" + className + "\" successful loaded." );
        }
        catch ( InstantiationException | IllegalAccessException | ClassNotFoundException exc )
        {
            sfLogger.log( Level.WARNING, "Could not load the function class " + className, exc );
        }

        return function;
    }


    // initiates the command handler and sets up the threadpool-size
    private void initiateCommandHandler()
    {
        int amountThreads;
        try {
            amountThreads = Integer.parseInt( mBotConfiguration.getCmdHandlerProcNo() );
        }
        catch( NumberFormatException exc ) {
            sfLogger.log( Level.WARNING, "Unable to read the cmdHandlerProcNo-property. Using standard amount of threads", exc );
            amountThreads = Integer.parseInt( System.getenv( "NUMBER_OF_PROCESSORS" ) );
        }

        mCommandHandler = new CommandHandler( amountThreads );
        sfLogger.log( Level.INFO, "CommandHandler successfully created using " + amountThreads + " Threads." );
    }


    // initially connects to the ts3-server so the ts3-objects which are needed to manage the ts3 server stuff
    private void initialConnect()
    {
        TS3Config config = getServerConfig();
        mQuery = new TS3Query( config );
        mQuery.connect();
        mApi   = mQuery.getApi();
        mApi.selectVirtualServerById( 1 );
        sfLogger.log( Level.INFO, "Connected to the TS3-server \"" + mBotConfiguration.getHostName() + "\" using " +
                        "the login name \"" + mBotConfiguration.getLoginName() + "\"" );
    }


    private void initiateChannelMap()
    {
        sfLogger.log( Level.INFO, "initiateChannelMap..." );
        List<Channel> channelsToJoin = ApiUtils.filterNotJoinableChannels( getAllServerChannels() );
        for( Channel c : channelsToJoin )
        {
            TS3BotChannelListener listener = new TS3BotChannelListener( getServerConfig(), false, mCommandHandler, c.getId()  );
            ChannelWrapper cw = new ChannelWrapper( c, listener, false );
            mChannelMap.put( c.getId(), cw );
            sfLogger.log( Level.INFO, String.format( "ChannelName: %s \tChannelId: %d", c.getName(), c.getId() ) );
        }
        sfLogger.log( Level.INFO, "...initiateChannelMap finished!" );
    }


    // returns a TS3Config, configurated to connect to the server given by the BotConfiguration
    private TS3Config getServerConfig()
    {
        TS3Config config = new TS3Config();
        config.setHost( mBotConfiguration.getHostName() );
        config.setLoginCredentials( mBotConfiguration.getLoginName(), mBotConfiguration.getLoginPassword() );

        return config;
    }


    // connects all active channelwrappers to their associated channels
    private void connectChannel()
    {
        for( ChannelWrapper cw : mChannelMap.values() )
            if( cw.isActive() )
            {
                cw.connect();
                sfLogger.log( Level.INFO, "connected to " + cw.mfChannel.getName() + " with the id " + cw.mfChannel.getId() );
            }
        sfLogger.log( Level.INFO, "Connected all listeners to their channel." );
    }


    // disconnects all active channelwrappers from their associated channels
    private void disconnectChannel()
    {
        for( ChannelWrapper cw : mChannelMap.values() )
            if( !cw.isActive() )
                cw.disconnect();
        sfLogger.log( Level.INFO, "Disconnected all listeners from theixr channel." );
    }


    // starting the CommandHandler. Before starting there must be the functions given to it,
    // important: use the functions override method!
    private void startCommandHandler()
    {
        sfLogger.log( Level.INFO, "Preparing and starting CommandHandler.." );
        int i = 0;

        Map<String, Class<? extends CallableFunction>> activeFunctions = getAllActiveFunctionNames();
        Map<String, CallableFunction> commandMap = new HashMap<>();

        for( String s : activeFunctions.keySet() )
        {
            Class<? extends CallableFunction> clazz = activeFunctions.get( s );
            try {
                commandMap.put( s, clazz.newInstance() );
                i++;
            } catch( IllegalAccessException | InstantiationException exc ) {
                sfLogger.log( Level.INFO, "Could not instantiate the function: " + clazz.getName(), exc );
            }
        }

        mCommandHandler.overrideFunctions( commandMap );
        mCommandHandler.run();
        sfLogger.log( Level.INFO, String.format( "..prepared and started CommandHandler using %d Functions.", i ) );
    }


    // stops the CommandHandler. Nothing extras have to be done here
    private void stopCommandHandler()
    {
        sfLogger.log( Level.INFO, "Stopping CommandHandler.." );
        mCommandHandler.stop();
        sfLogger.log( Level.INFO, "..finished." ) ;
    }




    // ------------------------------------------ public methods ---------------------------------------------------- //


    // -------------------------------------------------------------------------------------------------------------- //

                                    // ---------------------------------- //
                                    // -- methods for channel handling -- //
                                    // ---------------------------------- //

    // -------------------------------------------------------------------------------------------------------------- //




    /**
     * @return a list with all available channels on the TS3-server
     */
    public List<Channel> getAllServerChannels()
    {
        return mApi.getChannels();
    }


    /**
     * @return a list containing all channels which are currently the bot is listening at - if it is running
     */
    public List<Channel> getActiveChannels()
    {
        ArrayList<Channel> list = new ArrayList<>();
        for( ChannelWrapper cw : mChannelMap.values() )
            if( cw.isActive() )
                list.add( cw.mfChannel );

        return list;
    }


    /**
     * @param c the channel to be checked if the bot is currently active in or not
     * @return true if the channel is active, else false
     */
    public boolean isChannelActive( Channel c )
    {
        ChannelWrapper cw = mChannelMap.get( c.getId() );
        return cw == null ? false : cw.isActive();
    }


    /**
     *
     * @param channelId
     * @throws org.tsbs.manager.exceptions.UnknownChannelException if there's no channel with the id ChannelId
     */
    public void activateChannel( int channelId ) throws UnknownChannelException
    {
        Channel c = ApiUtils.getChannelById( getAllServerChannels(), channelId );
        if( c == null )
            throw new UnknownChannelException( "Channel with the id( " + channelId + " ) not found!" );

        activateChannel( c );
    }


    /**
     *
     * @param channel
     * @throws org.tsbs.manager.exceptions.UnknownChannelException
     */
    public void activateChannel( Channel channel ) throws UnknownChannelException
    {
        ChannelWrapper cw = mChannelMap.get( channel.getId() );
        if( cw == null )
            throw new UnknownChannelException( "Channel with the id " + channel.getId() + " not as active found!" );

        cw.setActive();
        sfLogger.log( Level.INFO, String.format( "Activated channel with the id: %d, name: %s", cw.mfChannel.getId(),
                                                    channel.getId(), channel.getName() ), channel );
    }


    /**
     *
     * @param channelId id of the channel to be removed
     * @return true when successfully removed/deactivated, else false - e.g. if the channel was not active
     */
    public boolean deactivateChannel( int channelId )
    {
        ChannelWrapper cw = mChannelMap.remove( channelId );

        // if the channel was not in the active map, return false,
        if( cw == null )
        {
            sfLogger.log( Level.INFO, "No active channel found with the channeld-id: " + channelId );
            return false;
        }

        cw.setInactive();
        sfLogger.log( Level.INFO, String.format( "Deactivated channel id: %d, name: %s.", channelId, cw.mfChannel.getName() ),
                        cw.mfChannel );
        return true;
    }


    /**
     *
     * @param channel the channel to be removed/deactivated
     * @return true when successfully removed/deactivated, else false - e.g. if the channel was not active
     */
    public boolean deactivateChannel( Channel channel )
    {
        return deactivateChannel( channel.getId() );
    }




    // -------------------------------------------------------------------------------------------------------------- //

                                        // ------------------------------- //
                                        // -- Command/Function settings -- //
                                        // ------------------------------- //

    // -------------------------------------------------------------------------------------------------------------- //




    /**
     * @return a list containing the classes of all loaded functions
     */
    public List<Class<? extends CallableFunction>> getAllFunctionClasses()
    {
        return new ArrayList<>( mFunctionMap.keySet() );
    }


    /**
     * @return a map containing the function names in reference to the classes they name
     */
    public Map<String, Class<? extends CallableFunction>> getAllFunctionNames()
    {
        HashMap<String, Class<? extends CallableFunction>> map = new HashMap<>();
        for( FunctionWrapper fw : mFunctionMap.values() )
            map.put( fw.getFunctionName(), fw.getCallableAction() );

        return map;
    }


    /** @return a map containing the function names of all active functions in reference to the classes they name */
    public Map<String, Class<? extends CallableFunction>> getAllActiveFunctionNames()
    {
        HashMap<String, Class<? extends CallableFunction>> map = new HashMap<>();
        for( FunctionWrapper fw : mFunctionMap.values() )
            if( fw.isActive() )
                map.put( fw.getFunctionName(), fw.getCallableAction() );

        return map;
    }


    /**
     * This Operation will take effect after the Bot has been restarted!
     * @param clazz identifies the function which has to be renamed
     * @param newName the new Name for the function
     * @return true if rename process was successful, else false
     */
    public boolean renameFunction( Class<? extends CallableFunction> clazz, String newName ) throws UnknownFunctionException
    {
        FunctionWrapper fw = mFunctionMap.get( clazz );
        if( clazz == null )
        {
            UnknownFunctionException exc = new UnknownFunctionException( "Unknown function: " + clazz.getName() );
            sfLogger.log( Level.WARNING, "Unknown function tried to rename.", exc );
        }
        String old = fw.getFunctionName();
        fw.setFunctionName( newName );
        sfLogger.log( Level.INFO, "Renamed function from " + old + " to " + newName );
        return true;
    }


    /**
     * This operation will take effect after the bot has been restarted!
     * @param clazz function to activate/deactivate
     * @param setActive will be activated if true, else deactivate
     * @return true if the operation was successful, else false
     */
    public boolean setFunctionActive( Class<? extends CallableFunction> clazz, boolean setActive ) throws UnknownFunctionException
    {
        FunctionWrapper fw = mFunctionMap.get(clazz);
        if( fw == null )
        {
            UnknownFunctionException exc = new UnknownFunctionException( "UnknownFunction: " + clazz.getName() );
            sfLogger.log( Level.WARNING, "Unknown function tried to activate.", exc );
        }
        fw.setIsActive( setActive );
        sfLogger.log( Level.INFO, "Function " + clazz.getName() + " is now activated." );
        return true;
    }


    /**
     * This Operation will take effect after the bot has been restarted!
     * @param clazz function to activate
     * @return true if the operation was successful, else false
     */
    public boolean setFunctionActive( Class<? extends CallableFunction> clazz )
    {
       return setFunctionActive( clazz, true );
    }


    /**
     * This operation will take effect after the bot has been restarted!
     * @param clazz function to deactivate
     * @return true if the operation was successful, else false
     */
    public boolean setFunctionInactive( Class<? extends CallableFunction> clazz )
    {
        return setFunctionActive( clazz, false );
    }


    // -------------------------------------------------------------------------------------------------------------- //

                                // ---------------------------------------------- //
                                // -- methods to control the managers activity -- //
                                // ---------------------------------------------- //

    // -------------------------------------------------------------------------------------------------------------- //

    /**
     * Connects all ChannelListener to their TS3-Channels and starts up
     * the commandHandler using the functions marked as active.
     * @return true if the bot has been set active, else false;
     */
    public boolean setBotActive()
    {
        sfLogger.log( Level.INFO, "Activating Bot ..." );
        if( mIsRunning )
        {
            sfLogger.log( Level.INFO, ".. Bot is already active!" );
            return false;
        }

        connectChannel();
        startCommandHandler();
        mIsRunning = true;
        sfLogger.log( Level.INFO, "... activating bot finished." );
        return true;
    }


    /**
     * Disconnects all ChannelListener from their TS3-Channels and stops
     * the CommandHandler imidiatly
     * @return true if the bot has been set inactive, else false
     */
    public boolean setBotInactive()
    {
        sfLogger.log( Level.INFO, "Deactivating Bot ... " );
        if( !mIsRunning )
        {
            sfLogger.log( Level.INFO, "... Bot is already deactivated!" );
            return false;
        }

        disconnectChannel();
        stopCommandHandler();
        mIsRunning = false;
        sfLogger.log( Level.INFO, "... deactivating finished." );
        return true;
    }


}
TOP

Related Classes of org.tsbs.manager.BotManager

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.