package chatty;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Gets an {@code InetSocketAddress} from resolving a host and a port, selecting
* those first that may be more likely to connect based on previous error
* reports (if more than one IP/port is available).
*
* @author tduva
*/
public class AddressManager {
private static final Logger LOGGER = Logger.getLogger(AddressManager.class.getName());
/**
* Socket Addressses that couldn't be connected to. This may be cleared
* (possibly only partly), so this doesn't mean all failed connection
* attempts are in here. It is only used to determine the next hopefully
* best address to connect to.
*/
private final Set<InetSocketAddress> errors = new HashSet<>();
/**
* Gets an {@code InetSocketAddress} based on the given host (a single host)
* and the list of ports (one or several comma-seperated ports). It uses the
* first host/port that hasn't been added to the errors list via
* {@code addError()}, or start from the beginning again if all possible
* addresses are in the errors list.
*
* @param host The host to connect to.
* @param portsString The port(s) to connect to.
* @return The {@code InetSocketAddress} or {@code null} if either the host
* or ports are invalid
* @throws java.net.UnknownHostException If the host could not be resolved
*/
public InetSocketAddress getAddress(String host, String portsString) throws UnknownHostException {
List<Integer> ports = parsePorts(portsString);
if (ports.isEmpty()) {
LOGGER.warning("No port to connect to found: "+portsString);
return null;
}
if (host == null || host.isEmpty()) {
LOGGER.warning("No host to connect to provided.");
return null;
}
InetAddress[] ips = InetAddress.getAllByName(host);
// For testing
//ips = InetAddress.getAllByName("199.9.250.239");
// Try to find a socket address that isn't in the error list
Set<InetSocketAddress> tried = new HashSet<>();
for (InetAddress ip : ips) {
for (int port : ports) {
InetSocketAddress address = new InetSocketAddress(ip, port);
if (!errors.contains(address)) {
return address;
}
tried.add(address);
}
}
LOGGER.info("Tried all available sockets.. trying from the start.");
clearErrorsForAddresses(tried);
return tried.iterator().next();
}
/**
* Tells the manager that connecting to this address failed, which means
* other addresses (if available) are tried first for the next attempt.
*
* @param address
*/
public void addError(InetSocketAddress address) {
errors.add(address);
}
/**
* Removes all given {@code InetSocketAddress} objects from the errors list,
* so they can be tried again.
*
* @param addresses The list of {@code InetSocketAddress} objects to remove
*/
private void clearErrorsForAddresses(Set<InetSocketAddress> addresses) {
errors.removeAll(addresses);
}
/**
* Parses a String containing one or more ports and returns a {@code List}
* of them. Port numbers can be seperated by any non-numeric character
* (although it is recommended to seperate by comma).
*
* @param ports
* @return A {@code List} or {@code Integer} objects representing the ports,
* which may be empty if no port could be found
*/
public static List<Integer> parsePorts(String ports) {
String[] split = ports.split("[^0-9]");
List<Integer> parsedPorts = new ArrayList<>();
for (String portString : split) {
if (!portString.isEmpty()) {
try {
parsedPorts.add(Integer.parseInt(portString));
} catch (NumberFormatException ex) {
// Do nothing if the port is invalid (e.g. larger than int)
}
}
}
return parsedPorts;
}
// Testing stuff
public static void main(String[] args) {
AddressManager m = new AddressManager();
try {
for (int i=0;i<60;i++) {
InetSocketAddress addr = m.getAddress("irc.chat.twitch.tv", "6697,6667,443,80");
System.out.println(addr);
m.addError(addr);
}
} catch (UnknownHostException ex) {
Logger.getLogger(AddressManager.class.getName()).log(Level.SEVERE, null, ex);
}
}
}