/*
 * URLName.java
 * Copyright (C) 2002 The Free Software Foundation
 * 
 * This file is part of GNU JavaMail, a library.
 * 
 * GNU JavaMail is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * GNU JavaMail 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * As a special exception, if you link this library with other files to
 * produce an executable, this library does not by itself cause the
 * resulting executable to be covered by the GNU General Public License.
 * This exception does not however invalidate any other reasons why the
 * executable file might be covered by the GNU General Public License.
 */

package javax.mail;

import java.io.*;
import java.net.*;

/**
 * The name of a URL.
 * This class represents a URL name and also provides the basic
 * parsing functionality to parse most internet standard URL schemes.
 * <p>
 * Note that this class differs from java.net.URL in that this class just
 * represents the name of a URL, it does not model the connection to a URL.
 *
 * @author <a href="mailto:dog@gnu.org">Chris Burdess</a>
 * @version 1.3
 */
public class URLName
{

  /**
   * The full version of the URL
   */
  protected String fullURL;

  private String protocol;
  private String host;
  private int port = -1;
  private String file;
  private String ref;
  private String username;
  private String password;

  private InetAddress hostAddress;
  private boolean gotHostAddress = false;
  
  
  private int hashCode;

  /**
   * Creates a URLName object from the specified protocol, host,
   * port number, file, username, and password.
   * Specifying a port number of -1 indicates that the URL should 
   * use the default port for the protocol.
   */
  public URLName(String protocol, String host, int port,
      String file, String username, String password)
  {
    this.protocol = protocol;
    this.host = host;
    this.port = port;
    this.file = file;
    if (file!=null)
    {
      int hashIndex = file.indexOf('#');
      if (hashIndex!=-1)
      {
        this.file = file.substring(0, hashIndex);
        ref = file.substring(hashIndex+1);
      }
    }
    this.username = username;
    this.password = password;
  }

  /**
   * Construct a URLName from a java.net.URL object.
   */
  public URLName(URL url)
  {
    this(url.toString());
  }

  /**
   * Construct a URLName from the string. Parses out all the possible
   * information (protocol, host, port, file, username, password).
   */
  public URLName(String url)
  {
    parseString(url);
  }

  /**
   * Returns the port number of this URLName.
   * Returns -1 if the port is not set.
   */
  public int getPort()
  {
    return port;
  }

  /**
   * Returns the protocol of this URLName. 
   * Returns null if this URLName has no protocol.
   */
  public String getProtocol()
  {
    return protocol;
  }

  /**
   * Returns the file name of this URLName.
   * Returns null if this URLName has no file name.
   */
  public String getFile()
  {
    return file;
  }

  /**
   * Returns the reference of this URLName.
   * Returns null if this URLName has no reference.
   */
  public String getRef()
  {
    return ref;
  }

  /**
   * Returns the host of this URLName.
   * Returns null if this URLName has no host.
   */
  public String getHost()
  {
    return host;
  }

  /**
   * Returns the user name of this URLName.
   * Returns null if this URLName has no user name.
   */
  public String getUsername()
  {
    return username;
  }

  /**
   * Returns the password of this URLName.
   * Returns null if this URLName has no password.
   */
  public String getPassword()
  {
    return password;
  }

  /**
   * Constructs a URL from the URLName.
   */
  public URL getURL()
    throws MalformedURLException
  {
    return new URL(getProtocol(), getHost(), getPort(), getFile());
  }

  // -- Utility methods --

  /**
   * Constructs a string representation of this URLName.
   */
  public String toString()
  {
    if (fullURL==null)
    {
      StringBuffer buffer = new StringBuffer();
      if (protocol!=null)
      {
        buffer.append(protocol);
        buffer.append(":");
      }
      if (username!=null || host!=null)
      {
        buffer.append("//");
        if (username!=null)
        {
          buffer.append(username);
          if (password!=null)
          {
            buffer.append(":");
            buffer.append(password);
          }
          buffer.append("@");
        }
        if (host!=null)
          buffer.append(host);
        if (port!=-1)
        {
          buffer.append(":");
          buffer.append(Integer.toString(port));
        }
        if (file!=null)
          buffer.append("/");
      }
      if (file!=null)
        buffer.append(file);
      if (ref!=null)
      {
        buffer.append("#");
        buffer.append(ref);
      }
      fullURL = buffer.toString();
    }
    return fullURL;
  }

  /**
   * Compares two URLNames. The result is true if and only if the argument is
   * not null and is a URLName object that represents the same URLName as this
   * object. Two URLName objects are equal if they have the same protocol and
   * reference the same host, the same port number on the host, the same
   * username and password, and the same file on the host. The fields (host,
   * username, password, file) are also considered the same if they are both
   * null.
   */
  public boolean equals(Object other)
  {
    if (!(other instanceof URLName))
      return false;
    URLName url = (URLName)other;
    if (url.protocol==null || !url.protocol.equals(protocol))
      return false;
    InetAddress address = getHostAddress();
    InetAddress otherAddress = url.getHostAddress();
    if (address!=null && otherAddress!=null)
    {
      if (!address.equals(otherAddress))
        return false;
    }
    else if (host!=null && url.host!=null)
    {
      if (!host.equalsIgnoreCase(url.host))
        return false;
    }
    else if (host!=url.host)
      return false;
    if (username!=url.username && 
        (username==null || !username.equals(url.username)))
      return false;
    String file = this.file!=null ? this.file : "";
    String otherFile = url.file!=null ? url.file : "";
    if (!file.equals(otherFile))
      return false;
    return port==url.port;
  }

  /**
   * Compute the hash code for this URLName.
   */
  public int hashCode()
  {
    if (hashCode!=0)
      return hashCode;
    if (protocol!=null)
      hashCode += protocol.hashCode();
    InetAddress address = getHostAddress();
    if (address!=null)
      hashCode += address.hashCode();
    else
    if (host!=null)
      hashCode += host.toLowerCase().hashCode();
    if (username!=null)
      hashCode += username.hashCode();
    if (file!=null)
      hashCode += file.hashCode();
    hashCode += port;
    return hashCode;
  }

  private synchronized InetAddress getHostAddress()
  {
    if (gotHostAddress)
      return hostAddress;
    if (host==null)
      return null;
    try
    {
      hostAddress = InetAddress.getByName(host);
    }
    catch (UnknownHostException e)
    {
      hostAddress = null;
    }
    gotHostAddress = true;
    return hostAddress;
  }

  /**
   * Method which does all of the work of parsing the string.
   */
  protected void parseString(String url)
  {
    protocol = file = ref = host = username = password = null;
    port = -1;
    int len = url.length();
    int colonIndex = url.indexOf(':');
    if (colonIndex!=-1)
      protocol = url.substring(0, colonIndex);
    if (url.regionMatches(colonIndex+1, "//", 0, 2))
    {
      String hostPart;
      int slashIndex = url.indexOf('/', colonIndex+3);
      if (slashIndex!=-1)
      {
        hostPart = url.substring(colonIndex+3, slashIndex);
        if ((slashIndex+1)<len)
          file = url.substring(slashIndex+1);
        else
          file = "";
      }
      else
        hostPart = url.substring(colonIndex+3);

      // user:password@host?
      int atIndex = hostPart.indexOf('@');
      if (atIndex!=-1)
      {
        String userPart = hostPart.substring(0, atIndex);
        hostPart = hostPart.substring(atIndex+1);
        colonIndex = userPart.indexOf(':');
        if (colonIndex!=-1)
        {
          username = userPart.substring(0, colonIndex);
          password = userPart.substring(colonIndex+1);
        }
        else
          username = userPart;
      }

      // host:port?
      if (hostPart.length()>0 && hostPart.charAt(0)=='[')
        colonIndex = hostPart.indexOf(':', hostPart.indexOf(']'));
      else
        colonIndex = hostPart.indexOf(':');
      if (colonIndex!=-1)
      {
        String portPart = hostPart.substring(colonIndex + 1);
        if (portPart.length()>0)
        {
          try
          {
            port = Integer.parseInt(portPart);
          }
          catch (NumberFormatException e)
          {
            port = -1;
          }
        }
        host = hostPart.substring(0, colonIndex);
      }
      else
        host = hostPart;
    }
    else if ((colonIndex+1)<len)
      file = url.substring(colonIndex+1);
    
    int hashIndex = (file!=null) ? file.indexOf('#') : -1;
    if (hashIndex!=-1)
    {
      ref = file.substring(hashIndex+1);
      file = file.substring(0, hashIndex);
    }
  }

}
