CellAddress.java  
// $Id: CellAddress.java,v 1.1 2000/05/18 22:01:17 tucker Exp $
// Hive. Copyright (c) 1998-1999, The Massachusetts Institute of Technology.
// All rights reserved. Distributed with no warranty, without even the
// implied warranty of merchantability or fitness for a particular purpose.
// For more details see COPYING.GPL, the GNU General Public License.


package net.hivecell.hive.support;
import java.net.*;
import java.io.Serializable;
import java.rmi.Naming;

import net.hivecell.hive.Global;
import net.hivecell.hive.cell.RemoteCell;


/** This immutable class represents the URL of a Hive Cell. The
 *protocol portion of the url is defined to be "hive", and will not
 *allow the construction of urls with a different
 *protocol. CellAddress follows the CISS (Common Internet Scheme
 *Standard) Spec, although currently the file and ref portions of the
 *URL are not used by the Hive system.
 *
 * WARNING: Java deviates from it's spec (at least on windows) in the
 * DNS lookup behavior. Doing a
 * InetAdderss.getByName(host).getHostName() does NOT return the fully
 * qualified hostname as it should. Rather, some wierd caching
 * behavior is causing the original argument to be returned. To work
 * around this, we do a lookup to an IP Address and then do a lookup
 * from that. This requires one more DNS lookup and is hence lower
 * performance.

 * Also, hostnames are internally represented as they are returned by
 * the DNS Cell, but for the purposes of equal and hashCode, the 
 * hostname is converted to lowercase, because some DNS servers return
 * inconsistent case information.

 * ANOTHER WARNING: Because this class is specified in the RemoteCell
 * interface that is absolutely core to Hive, any changes in the
 * SerialVersionID of this class will cause an incompatibility with
 * any existing Hive cells. So think very carefully before performing
 * even 'minor' modifications to this class.

 *  */
public class CellAddress implements Serializable {
    
    /** This is the default port that is assumed if one is not
     * specified, or if -1 is used as an argument.
     *  */
    public static final int DEFAULT_PORT = Global.defaultPort;

    /** The fixed string representing the protocol used in Hive cells. 
     * 
     */
    public static final String PROTOCOL = "hive";
    public static final String RMI_NAME = "cell";

    /** The port of the URL.
     * 
     */
    private int port;

    /** The Host of the URL. Canonicalized at construction.
     * 
     */
    private String host;

    /** The file of the URL.
     * 
     */
    private String file; // Currently ignored.

    /** The Reference or Anchor of the URL.
     * 
     */
    private String ref; // Currently ignored.

    /** The Hashcode of the URL. Created at construction.
     * 
     */
    private int hashCode;

    /** This rather grotesque method does all the parsing of the
     * string necessary to construct an URL. Anything that is
     * unparsable, including URLs with a protocol other than "hive"
     * will be rejected with a MalformedHiveURLException.  The
     * constructor does a lot of work up front, including
     * canonicalizing the hostname (which requires two DNS lookups,
     * and calculating the hashcode.) Hosts that are not in DNS will
     * throw a MalformedHiveURLException.
     * 
     * @param cellAddress String to be parsed into a CellAddress */
    public CellAddress(String cellAddress) throws MalformedHiveURLException {
    int hostIndex = 0; // First char of the host if present.
    boolean portPresent = true; // Is there a port specified.
    int portIndex; // The delimeter between host and port.
    boolean filePresent = true; // Is there a file present?
    int fileIndex; // First char of the file if present.
    boolean refPresent = true; // Is there an ref specified.
    int refIndex; // The delimeter between file and ref.

        if (cellAddress == null)
            throw new MalformedHiveURLException();
        
    if (cellAddress.indexOf("://") >= 0) {
        if (cellAddress.indexOf(PROTOCOL + "://") != 0) {
        throw new MalformedHiveURLException();
        }
        else {
        hostIndex = 7;
        }
    }
    else /* Require the hostname to be prepended with hive://.  This prevents strings like 
        "--nojoin" and "nowhere" from causing a DNS lookup, which locks hive if 
        the network is down.  such things as hive://ivy:9999 still work though.
         */
        throw new MalformedHiveURLException();

    try {
        fileIndex = cellAddress.indexOf('/', hostIndex);
        refIndex = cellAddress.indexOf('#', hostIndex) + 1;
    }
    catch (StringIndexOutOfBoundsException e) {
        throw new MalformedHiveURLException();
        // There is no URL after the protocol which is not valid.
    }
    if (refIndex == 0) {
        refIndex = cellAddress.length();
        refPresent = false;
    }
    if ((fileIndex == -1) || ((refPresent) && (refIndex < fileIndex))) {
        fileIndex = refIndex;
        filePresent = false;
    }
    if (refIndex == 0) {
        refPresent = false;
        refIndex = fileIndex + 1;
    }
    portIndex = cellAddress.indexOf(':', hostIndex) + 1;    
    if ((portIndex == 0) || (portIndex > fileIndex) || (portIndex > refIndex)) {
        portIndex = fileIndex;
        portPresent = false;
    }

    /*
      System.out.println("hostIndex: " + hostIndex);
      if (portPresent)
      System.out.println("portPresent");
      else 
      System.out.println("!portPresent");

      System.out.println("portIndex: " + portIndex);
      if (filePresent)
      System.out.println("filePresent");
      else 
      System.out.println("!filePresent");
      System.out.println("fileIndex: " + fileIndex);
      if (refPresent)
      System.out.println("refPresent");
      else 
      System.out.println("!refPresent");
      System.out.println("refIndex: " + refIndex);
    */

    try {
        if (portPresent) {
        host = cellAddress.substring(hostIndex, portIndex - 1);
        int endPort;
        if ((!filePresent) && (!refPresent)) {
            port = Integer.parseInt(cellAddress.substring(portIndex));
        }
        else if (fileIndex < refIndex)
            port = Integer.parseInt(cellAddress.substring(portIndex,
                                  fileIndex));
        else
            port = Integer.parseInt(cellAddress.
                        substring(portIndex, refIndex - 1));
        }
        else {
        host = cellAddress.substring(hostIndex, portIndex);
        port = DEFAULT_PORT;
        }
        if (filePresent) {
        if (refPresent) {
            file = cellAddress.substring(fileIndex, refIndex - 1);
            ref = cellAddress.substring(refIndex);
        }
        else {
            file = cellAddress.substring(fileIndex, refIndex);
            ref = "";
        }
        }
        else {
        file = "";
        if (refPresent) {
            ref = cellAddress.substring(refIndex);
        }
        else {
            ref = "";
        }
        }       
    }
    catch (NumberFormatException e) {
        throw new MalformedHiveURLException(); // Bad port specification.
    }
    catch(StringIndexOutOfBoundsException e) {
        throw new MalformedHiveURLException(); // Missing port specification
    }

    try {
        String address = InetAddress.getByName(host).getHostAddress();
        host = InetAddress.getByName(address).getHostName();
        // Canonicalize hostname. Requires two DNS lookups.
    }
    catch (UnknownHostException e) {
        throw new MalformedHiveURLException("Bad hostname");
    }
    if (port == -1) {
        port = DEFAULT_PORT; // This matches the URL spec.
    }
    else if (port < -1) {
        throw new MalformedHiveURLException(); // No negative ports other than -1 allowed.
    }
    calculateHashCode();
    }

    /** This Constructor allows the creation of a CellAddress with individual arguments rather than a parseable string.
     * 
     * @param port 
     * @param file 
     * @param ref 
     * @param host 
     */
    public CellAddress(String host, int port, String file, String ref) throws MalformedHiveURLException {
    if ((host == null) || (host.equals(""))) {
        throw new MalformedHiveURLException();
    }
    try {
        String address = InetAddress.getByName(host).getHostAddress();
        this.host = InetAddress.getByName(address).getHostName();
    }
    catch (UnknownHostException e) {
        throw new MalformedHiveURLException("Bad hostname");
    }
    ;
    if (port == -1) {
        port = DEFAULT_PORT; // This matches the URL spec.
    }
    else if (port < -1) {
        throw new MalformedHiveURLException();
    }
    this.port = port;   
    if (file == null) {
        this.file = "";
    }
    else {
        this.file = file;
    }
    if (ref == null) {
        this.ref = "";
    }
    else {
        this.ref = ref;
    }
    calculateHashCode();
    }

    /** A convenience method for the most common types of CellAddress
     * 
     * @param port 
     * @param host 
     */
    public CellAddress(String host, int port) throws MalformedHiveURLException {
    this(host, port, null, null);
    }

    /** Returns a cell address for the local machine and the specified port.
     */

    public static CellAddress createLocalCellAddress(int port) {
    String host;

    try {
        host = InetAddress.getByName(InetAddress.getLocalHost().getHostAddress()).getHostName();
    }
    catch (UnknownHostException e) {
        Debug.error("Unable to lookup own address.");
        return null;
    }
    try {
        return new CellAddress(host, port);
    }
    catch (MalformedHiveURLException e) {
        Debug.error("Somehow cannot create a CellAddress for the local machine.");
        return null;
    }
    }

    /** Returns a cell address for the local machine and the default port.
     */
    public static CellAddress createLocalCellAddress() {
    return createLocalCellAddress(DEFAULT_PORT);
    }

    /** This method calculates the hashcode. All the fields must be valid before this
     *  method is called.
     */
    private void calculateHashCode() {  
    hashCode = getProtocol().hashCode() ^ getHost().toLowerCase().hashCode() ^ getPort() ^ file.hashCode() ^ ref.hashCode();
    }

    /** Return the host of the CellAddress
     * 
     */
    public String getHost() {
    return host;
    }

    /** return the port number of the CellAdress
     * 
     */
    public int getPort() {
    return port;
    }

    /** Return the file of the cell address. May return the empty string.
     * 
     */
    public String getFile() {
    return file;
    }

    /** Return the reference of the CellAdress. May return the empty string.
     * 
     */
    public String getRef() {
    return ref;
    }

    /** Returns the protocol of the URL. (Always "hive")
     * 
     */
    public String getProtocol() {
    return PROTOCOL;
    }

    /** Returns true if the argument refers to the same cell as the
     * one defined.
     * 
     * @param obj */
    public boolean equals(Object obj) {
    if (obj instanceof CellAddress) {
        CellAddress ca = (CellAddress) obj;
        return (host.equalsIgnoreCase(ca.getHost()) &&
            (port == ca.getPort()) &&
            (file.equals(ca.getFile())) &&
            (ref.equals(ca.getRef())));
    }
    return false;
    }

    /** Returns a hashcode for the CellAddress.
     *  */
    public int hashCode() {
    return hashCode;
    }

    /** Returns the string representation of the CellAddress.
     * 
     */
    public String toString() {
    StringBuffer sb = new StringBuffer();
    sb.append(PROTOCOL);
    sb.append("://");
    sb.append(host);
    if (port != DEFAULT_PORT) {
        sb.append(":");
        sb.append(port);
    }
    if (!file.equals("")) {
        sb.append(file);
    }
    if (!ref.equals("")) {
        sb.append("#");
        sb.append(ref);
    }
    return sb.toString();
    }

    /** Returns true if the address is determined to specify the
     * local machine.
     * 
     */
    public boolean isLocal() {
    try {
        return InetAddress.getByName(host).equals(InetAddress.getLocalHost());
    }
    catch (UnknownHostException e) {
        return false;
    }
    } 

    /** 
     * Find the Cell on the specified remote host.
     * @param address The host:port address
     * @return the RemoteCell, or else null if not found.
     */
    public RemoteCell getRemoteCell() throws CellConnectException {
    String name = "//" + host + ":" + port + "/" + RMI_NAME;
    try {
        RemoteCell rs = (RemoteCell)Naming.lookup( name );
        return rs;
    } 
    catch( Exception error ) {
        //      Debug.warning( "Couldn't find server at " + name );
        throw new CellConnectException( error );
    }
    }

    /** Private testing method for test cases.
     * 
     * @param str 
     */
    private static void test(String str) {
    System.out.println("Attempting to parse: " + str);
    try{
        CellAddress ca = new CellAddress(str);
        System.out.println("Protocol: " + ca.getProtocol());
        System.out.println("Host: " + ca.getHost());
        System.out.println("Port: " + ca.getPort());
        System.out.println("File: " + ca.getFile());
        System.out.println("Ref: " + ca.getRef());
        System.out.println(ca.toString());
        System.out.println();
    }
    catch(MalformedHiveURLException e) {
        System.out.println("Unparseable: " + str);
        e.printStackTrace();
    }
    }

    /** Test cases to check the complicated parsing method.
     * 
     * @param args 
     */
    public static final void main(String[] args) {
    try {
        System.out.println(InetAddress.getByName("18.244.1.37").getHostName());
    }
    catch(Throwable e) {
    }
    CellAddress.test("hive://oroup:23235/");
    CellAddress.test("hive://oroup:23237");
    CellAddress.test("oroup:23237/");
    CellAddress.test("oroup");
    CellAddress.test("oroup/");
    CellAddress.test("oroup/sds#dfsdf");
    CellAddress.test("hive://oroup.mit.edu:23235/34:53#21:24");
    CellAddress.test("hive://oroup:23/sdfdf#sdfdf");
    CellAddress.test("hi://oroup:23235/");
    CellAddress.test("hive://oroup:232435");
    CellAddress.test("hive://oroup:232435#sdffd");
    CellAddress.test("4334:232435#sd/ffd");
    }

}