# HostsManager.java

    /*
    ** Module   : HostsManager.java
    ** Abstract : Defines common methods to add host and to get port name.
    **
    ** Copyright (c) 2020-2024, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- ---------------------------------Description----------------------------------
    ** 001 SBI 20200317 Added common methods addHost and getPortName.
    ** 002 SBI 20230509 Added hosts resources map, fixed addHost.
    ** 003 SBI 20240501 Added loopback address entry to hosts map with its localhost entry, fixed
    **                  entry key to be the host address.
    ** 004 GBB 20240709 WebClientConfig renamed to WebAllocatedResources.
    */
    /*
    ** This program is free software: you can redistribute it and/or modify
    ** it under the terms of the GNU Affero General Public License as
    ** published by the Free Software Foundation, either version 3 of the
    ** License, or (at your option) any later version.
    **
    ** This program 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 Affero General Public License for more details.
    **
    ** You may find a copy of the GNU Affero GPL version 3 at the following
    ** location: https://www.gnu.org/licenses/agpl-3.0.en.html
    **
    ** Additional terms under GNU Affero GPL version 3 section 7:
    **
    **   Under Section 7 of the GNU Affero GPL version 3, the following additional
    **   terms apply to the works covered under the License.  These additional terms
    **   are non-permissive additional terms allowed under Section 7 of the GNU
    **   Affero GPL version 3 and may not be removed by you.
    **
    **   0. Attribution Requirement.
    **
    **     You must preserve all legal notices or author attributions in the covered
    **     work or Appropriate Legal Notices displayed by works containing the covered
    **     work.  You may not remove from the covered work any author or developer
    **     credit already included within the covered work.
    **
    **   1. No License To Use Trademarks.
    **
    **     This license does not grant any license or rights to use the trademarks
    **     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
    **     of Golden Code Development Corporation. You are not authorized to use the
    **     name Golden Code, FWD, or the names of any author or contributor, for
    **     publicity purposes without written authorization.
    **
    **   2. No Misrepresentation of Affiliation.
    **
    **     You may not represent yourself as Golden Code Development Corporation or FWD.
    **
    **     You may not represent yourself for publicity purposes as associated with
    **     Golden Code Development Corporation, FWD, or any author or contributor to
    **     the covered work, without written authorization.
    **
    **   3. No Misrepresentation of Source or Origin.
    **
    **     You may not represent the covered work as solely your work.  All modified
    **     versions of the covered work must be marked in a reasonable way to make it
    **     clear that the modified work is not originating from Golden Code Development
    **     Corporation or FWD.  All modified versions must contain the notices of
    **     attribution required in this license.
    */

    package com.goldencode.p2j.main;

    import java.io.*;
    import java.net.*;
    import java.util.*;
    import java.util.Map.*;
    import java.util.concurrent.*;
    import java.util.concurrent.atomic.*;

    /**
     * The host manager defines methods to add host and to get port name.
     */
    public class HostsManager
    {
       /** The path to the hosts file */
       private static final String HOSTS_FILE_PATH = "./hosts.txt";

       /** The host manager instance */
       private static HostsManager instance;

       /** Dynamic hosts map of registered brokers agents */
       private final Map<String, Integer> hosts;

       /** Dynamic hosts map of registered brokers agents */
       private final Map<String, InetAddress> resolvedHosts;

       /** Next registered broker agent */
       private final AtomicInteger nextRegisteredHost;

       /** Path to the hosts file */
       private final String hostsFilePath;

       /** Map a host name or its ip address to the web client resource queue */
       private final Map<String, BlockingQueue<WebAllocatedResources>> hostsResources;

       /**
        * The instance method to create the hosts manager instance.
        *
        * @return   The hosts manager
        */
       public static synchronized HostsManager getInstance()
       {
          if (instance == null)
          {
             instance = new HostsManager(HOSTS_FILE_PATH);
          }

          return instance;
       }

       /**
        * Creates the hosts manager.
        *
        * @param    file
        *           The hosts file path
        */
       private HostsManager(String file)
       {
          this.hostsFilePath = file;

          this.hosts = new ConcurrentHashMap<String, Integer>();

          this.resolvedHosts = new ConcurrentHashMap<String, InetAddress>();

          this.hostsResources = new ConcurrentHashMap<String, BlockingQueue<WebAllocatedResources>>();

          this.nextRegisteredHost = new AtomicInteger(0);

          // read hosts file
          Map<String, Integer> hostsFileMap;
          try
          {
             hostsFileMap = ClientsToPortsGenerator.readHostsFile(hostsFilePath);
          }
          catch (IOException e)
          {
             hostsFileMap = new LinkedHashMap<String, Integer>();
             hostsFileMap.put("localhost", 1);
             hostsFileMap.put("127.0.0.1", 1);
          }

          int lastHostNumber = 0;

          for(Entry<String, Integer> entry : hostsFileMap.entrySet())
          {
             hosts.put(entry.getKey(), entry.getValue());
             if (lastHostNumber < entry.getValue())
             {
                lastHostNumber = entry.getValue();
             }
          }

          nextRegisteredHost.set(lastHostNumber);
       }

       /**
        * Add new host.
        *
        * @param    hostNameOrIpAddress
        *           The host network address
        * @param    resourceQueue
        *           The queue of web resources to be allocated to the clients.
        *
        * @throws   UnknownHostException
        */
       public synchronized void addHost(String hostNameOrIpAddress, BlockingQueue<WebAllocatedResources> resourceQueue)
       throws UnknownHostException
       {
          if (resourceQueue == null)
          {
             return;
          }
          int id = nextRegisteredHost.incrementAndGet();
          if (hosts.putIfAbsent(hostNameOrIpAddress, id) == null)
          {
             ClientsToPortsGenerator.appendHost(hostsFilePath, hostNameOrIpAddress, id);
          }
          else
          {
             //host is already registered, so the next host number must be decreased by 1.
             nextRegisteredHost.compareAndSet(id, id - 1);
          }
          hostsResources.putIfAbsent(hostNameOrIpAddress, resourceQueue);

          InetAddress resolvedHost = resolvedHosts.get(hostNameOrIpAddress);

          if (resolvedHost == null)
          {
             resolvedHost = InetAddress.getByName(hostNameOrIpAddress);
             resolvedHosts.put(hostNameOrIpAddress, resolvedHost);
          }
       }

       /**
        * Getter for the queue of web resources to be allocated to the clients.
        *
        * @param    hostNameOrIpAddress
        *           The host network address
        *
        * @return   The queue of web resources to be allocated to the clients.
        */
       public BlockingQueue<WebAllocatedResources> getHostResource(String hostNameOrIpAddress)
       {
          return hostsResources.get(hostNameOrIpAddress);
       }

       /**
        * Get the resolved host name or IP address.
        *
        * @param    host
        *           The host name.
        *
        * @return   The resolved host name or IP address
        */
       public InetAddress getResolvedHost(String host)
       {
          return resolvedHosts.get(host);
       }

       /**
        * Represents a well-defined mapping from host ports numbers to their names.
        *
        * @param    prefix
        *           The client prefix
        * @param    from
        *           The beginning port number from the given port range
        * @param    host
        *           The host
        * @param    port
        *           The port number
        *
        * @return   The port name
        */
       public String getPortName(String prefix,
                                 int from,
                                 String host,
                                 int port)
       {
          try
          {
             InetAddress hostInetAddress = InetAddress.getByName(host);

             return getPortName(prefix, from, hostInetAddress, port);
          }
          catch (UnknownHostException e)
          {
             StringBuilder message = new StringBuilder("Unknown host: ");
             message.append(host);

             throw new IllegalStateException(message.toString());
          }
       }

       /**
        * Represents a well-defined mapping from host ports numbers to their names.
        *
        * @param    prefix
        *           The client prefix
        * @param    from
        *           The beginning port number from the given port range
        * @param    hostInetAddress
        *           The host IP4 address
        * @param    port
        *           The port number
        *
        * @return   The port name
        */
       private String getPortName(String prefix,
                                 int from,
                                 InetAddress hostInetAddress,
                                 int port)
       {
          Integer hostIndex = hosts.get(hostInetAddress.getHostAddress());

          if (hostIndex == null)
          {
             StringBuilder message = new StringBuilder("Add host: ");
             message.append(hostInetAddress.getHostAddress());

             throw new IllegalStateException(message.toString());
          }

          return ClientsToPortsGenerator.getPortName(prefix, from, hostIndex, port);
       }

    }