org.parakoopa.gmnetgate.punch.Mediator.java Source code

Java tutorial

Introduction

Here is the source code for org.parakoopa.gmnetgate.punch.Mediator.java

Source

/*
 * Copyright (c) 2015 Marco Kpcke <parakoopa at live.de>.
 * 
 * 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.parakoopa.gmnetgate.punch;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.DateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.cli.BasicParser;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;

/**
 * This class starts the UDP and TCP listening sockets.
 *
 * @author Parakoopa
 */
public class Mediator {

    /**
     * * (DEFAULT) SETTINGS **
     */
    /**
     * --port The port to listen on. Use the command line argument --port to
     * change it.
     */
    private static int port = 6510;

    /**
     * --version (read only) Version of this master server
     */
    private static final String version = "1.2.4-SNAPSHOT";

    /**
     * Minimum required GMnet PUNCH version.
     */
    private static final String udphpMin = "1.2.0";

    /**
     * --quiet Whether or not the console output should be hidden.
     */
    private static boolean quiet = false;

    /**
     * --verbose Log additional info?
     */
    private static boolean verbose = false;

    /**
     * --log Logfile (or null)
     */
    private static String log = null;

    /**
     * --disable-lobby Is the lobby enabled?
     */
    private static boolean lobby = true;

    /**
     * --testing Enable or disable debugging with HTMT.
     */
    private static boolean testing = false;

    /**
     * --name Optional name of this master server.
     */
    private static String name = "";

    /**
     * DEBUGGING: Start server with a bunch of test servers in the list
     */
    private static boolean dbg_servers = false;

    /**
     * Object that represents a server. Contains TCP socket, port, and the 5
     * data-strings
     */
    private HashMap<String, Server> serverMap;
    /**
     * Object that represents a client Contains only port right now.
     */
    private HashMap<String, Client> clientMap;
    /**
     * TCP Server.
     */
    private ServerSocket server;
    /**
     * UDP Server.
     */
    private DatagramSocket server_udp;
    /**
     * Recieving buffer for UDP.
     */
    byte[] receiveData = new byte[1024];

    /**
     * Command-line main-method. Currently no command line paramters are
     * supported.
     *
     * @param args Not used.
     */
    public static void main(String[] args) {

        /* Setup command line args */
        String header = "Start GMnet GATE.PUNCH. " + "The master sercer for GMnet PUNCH / GMnet ENGINE. "
                + "More information: http://gmnet.parakoopa.de"
                + "Use GMnet GATE.TESTER to debug your master server (http://gmnet.parakoopa.de/tester).";

        Options options = new Options();
        options.addOption(OptionBuilder.withLongOpt("port")
                .withDescription(
                        "Specify the TCP and UDP port this server will listen on. Default: " + Mediator.port)
                .hasArg().withArgName("PORT").create("p"));
        options.addOption("h", "help", false, "Print this help text.");
        options.addOption(OptionBuilder.withLongOpt("disable-lobby")
                .withDescription("Ignore all requests of listing the connected servers.").create());
        options.addOption(OptionBuilder.withLongOpt("version")
                .withDescription("Print version information and exit.").create());
        options.addOption(OptionBuilder.withLongOpt("name").hasArg()
                .withDescription("Name this master server (can be used with HTMT)").create());
        options.addOption(OptionBuilder.withLongOpt("testing")
                .withDescription("Enable testing mode to use debugging tools such as HTMT.").create());
        options.addOption("q", "quiet", false, "Don't output anything to the console.");
        options.addOption("v", "verbose", false, "Print and/or log all information.");
        options.addOption(OptionBuilder.withLongOpt("log")
                .withDescription("Log output to this file. Will still log" + " even if 'quiet' is set.").hasArg()
                .withArgName("FILE").create("l"));

        try {
            CommandLineParser parser = new BasicParser();
            CommandLine line = parser.parse(options, args);
            if (line.hasOption("help")) {
                HelpFormatter formatter = new HelpFormatter();
                formatter.printHelp("java -jar gmnet_gatepunch.jar", header, options, "", true);
                System.exit(0);
            }
            if (line.hasOption("quiet")) {
                Mediator.quiet = true;
            }
            if (line.hasOption("verbose")) {
                Mediator.verbose = true;
            }
            if (line.hasOption("testing")) {
                Mediator.testing = true;
            }
            if (line.hasOption("version")) {
                System.out.println("GMnet GATE.PUNCH");
                System.out.println("Version: " + Mediator.version);
                System.exit(0);
            }
            if (line.hasOption("name")) {
                Mediator.name = line.getOptionValue("name");
            }
            if (line.hasOption("log")) {
                Mediator.log = line.getOptionValue("log");
            }
            if (line.hasOption("port")) {
                Mediator.port = Integer.valueOf(line.getOptionValue("port"));
            }
            if (line.hasOption("disable-lobby")) {
                Mediator.lobby = false;
            }
        } catch (ParseException ex) {
            Logger.getLogger(Mediator.class.getName()).log(Level.SEVERE, null, ex);
        }
        /* END Setup command line args */

        new Mediator();
    }

    public Mediator() {
        try {
            //Set up some local variables
            server = new ServerSocket(port);
            server_udp = new DatagramSocket(port);
            serverMap = new HashMap();
            clientMap = new HashMap();
            final Mediator me = this;

            Mediator.log("GMnet GATE.PUNCH STARTED", false);
            Mediator.log("Starting UDP and TCP servers on port " + port, false);

            //Start two new threads for the servers, just to be on the safe side.
            new Thread() {
                @Override
                //START UDP SERVER
                public void run() {
                    Mediator.log("Loaded UDP Listener", true);
                    //When packet is processed: Continue with next packet
                    while (true) {
                        try {
                            //Create incoming packet and wait for it.
                            DatagramPacket packet = new DatagramPacket(receiveData, receiveData.length);
                            server_udp.receive(packet);
                            //When a packet arrivied: Deal with it. 
                            UDPPacket packetHandler = new UDPPacket(me, packet, server_udp);
                            //This was once also a seperate thread, that's why the method is called run.
                            //annother thread is not needed though.
                            //it is propably even more efficient to just swap the packet out instead of
                            //creating a new class above. Do what you want :)
                            packetHandler.run();
                        } catch (IOException ex) {
                            //Print all exceptions.
                            ex.printStackTrace();
                        }
                    }
                }
            }.start();
            new Thread() {
                @Override
                //START TCP SERVER
                public void run() {
                    Mediator.log("Loaded TCP Listener", true);
                    //When connection thread is created: Wait for next connection
                    while (true) {
                        try {
                            //Wait for connection
                            Socket client = server.accept();
                            //When connection is opened: Start thread that handles it.
                            TCPConnection connectionHandler = new TCPConnection(me, client, server);
                            new Thread(connectionHandler).start();
                        } catch (IOException ex) {
                            //Print all exceptions.
                            ex.printStackTrace();
                        }
                    }
                }
            }.start();
            if (Mediator.dbg_servers) {
                for (int i = 0; i < 50; i++) {
                    Server serverObj = this.getServer(UUID.randomUUID().toString());
                    serverObj.setData1(UUID.randomUUID().toString());
                    serverObj.setData2(UUID.randomUUID().toString());
                    serverObj.setData3(UUID.randomUUID().toString());
                    serverObj.setData4(UUID.randomUUID().toString());
                    serverObj.setData5(UUID.randomUUID().toString());
                    serverObj.setData6(UUID.randomUUID().toString());
                    serverObj.setData7(UUID.randomUUID().toString());
                    serverObj.setData8(UUID.randomUUID().toString());
                }
            }
        } catch (IOException ex) {
            //Print all exceptions.
            ex.printStackTrace();
        }
    }

    /**
     * Returns server HashMap. Each server object contains TCP socket, port, and
     * the 5 data-strings
     *
     * @return HashMap containing the server objects
     */
    public HashMap<String, Server> getServerMap() {
        return serverMap;
    }

    /**
     * Returns client HashMap. Each client object contains port
     *
     * @return HashMap containing the client objects
     */
    public HashMap<String, Client> getClientMap() {
        return clientMap;
    }

    /**
     * Get the server object that represents the ip (and create it if it doesn't
     * exist).
     *
     * @param ip IP of the server
     */

    public Server getServer(String ip) {
        Server serverObj;
        if (serverMap.containsKey(ip)) {
            serverObj = serverMap.get(ip);
        } else {
            serverObj = new Server(ip);
            serverMap.put(ip, serverObj);
        }
        return serverObj;
    }

    /**
     * Get the client object that represents the ip (and create it if it doesn't
     * exist)
     *
     * @param ip IP of the client
     * @return client object with that ip
     */
    public Client getClient(String ip) {
        Client clientObj;
        if (clientMap.containsKey(ip)) {
            clientObj = clientMap.get(ip);
        } else {
            clientObj = new Client();
            clientMap.put(ip, clientObj);
        }
        return clientObj;
    }

    /**
     * Remove this IP from the server list.
     *
     * @param hostAddress
     */
    void destroyServer(String ip) {
        serverMap.remove(ip);
    }

    /**
     * Remove this IP from the client list.
     *
     * @param hostAddress
     */
    void destroyClient(String ip) {
        clientMap.remove(ip);
    }

    /**
     * Recieving buffer for UDP.
     *
     * @return A buffer.
     */
    public byte[] getReceiveData() {
        return receiveData;
    }

    public static boolean isLobby() {
        return lobby;
    }

    public static boolean isTesting() {
        return testing;
    }

    public static String getName() {
        return name;
    }

    public static String getVersion() {
        return version;
    }

    public static String getUdphpMin() {
        return udphpMin;
    }

    /**
     * Logs to the console (if quiet was not set) and to the logfile if
     * specified The logfile will also get timestamps for each event.
     *
     * @param str String to log
     * @param verbose boolean Should this be logged only with --verbose?
     */
    public static void log(String str, boolean verbose) {
        //Don't print verbose lines if not requested
        if (verbose && !Mediator.verbose) {
            return;
        }
        DateFormat date = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, Locale.getDefault());
        /* CONSOLE OUTPUT */
        if (!Mediator.quiet) {
            System.out.println(date.format(new Date()) + " : " + str);
        }
        /* FILE LOG */
        if (Mediator.log != null) {
            try (PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(Mediator.log, true)))) {
                out.println(date.format(new Date()) + " : " + str);
            } catch (IOException ex) {
                Logger.getLogger(Mediator.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    /**
     * Compares two version strings.
     *
     * Use this instead of String.compareTo() for a non-lexicographical
     * comparison that works for version strings. e.g. "1.10".compareTo("1.6").
     * By http://stackoverflow.com/questions/6701948/efficient-way-to-compare-version-strings-in-java
     *
     * @note It does not work if "1.10" is supposed to be equal to "1.10.0".
     *
     * @param str1 a string of ordinal numbers separated by decimal points.
     * @param str2 a string of ordinal numbers separated by decimal points.
     * @return The result is a negative integer if str1 is _numerically_ less
     * than str2. The result is a positive integer if str1 is _numerically_
     * greater than str2. The result is zero if the strings are _numerically_
     * equal.
     */
    public static Integer versionCompare(String str1, String str2) {
        String[] vals1 = str1.split("\\.");
        String[] vals2 = str2.split("\\.");
        int i = 0;
        // set index to first non-equal ordinal or length of shortest version string
        while (i < vals1.length && i < vals2.length && vals1[i].equals(vals2[i])) {
            i++;
        }
        // compare first non-equal ordinal number
        if (i < vals1.length && i < vals2.length) {
            int diff = Integer.valueOf(vals1[i]).compareTo(Integer.valueOf(vals2[i]));
            return Integer.signum(diff);
        } // the strings are equal or one string is a substring of the other
          // e.g. "1.2.3" = "1.2.3" or "1.2.3" < "1.2.3.4"
        else {
            return Integer.signum(vals1.length - vals2.length);
        }
    }

}