/* gnu.java.net.protocol.jar.Handler - jar protocol handler for java.net
   Copyright (C) 1999, 2002, 2003, 2005, 2006 Free Software Foundation, Inc.

This file is part of GNU Classpath.

GNU Classpath 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, or (at your option)
any later version.

GNU Classpath 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 GNU Classpath; see the file COPYING.  If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA.

Linking this library statically or dynamically with other modules is
making a combined work based on this library.  Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.

As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module.  An independent module is a module which is not derived from
or based on this library.  If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so.  If you do not wish to do so, delete this
exception statement from your version. */


package gnu.java.net.protocol.jar;

import gnu.java.lang.CPStringBuilder;

import gnu.java.net.URLParseError;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.StringTokenizer;

/**
 * @author Kresten Krab Thorup (krab@gnu.org)
 */
public class Handler extends URLStreamHandler
{
  /**
   * A do nothing constructor
   */
  public Handler()
  {
  }

  /**
   * This method returs a new JarURLConnection for the specified URL
   *
   * @param url The URL to return a connection for
   *
   * @return The URLConnection
   *
   * @exception IOException If an error occurs
   */
  protected URLConnection openConnection(URL url) throws IOException
  {
    return new Connection(url);
  }

  /**
   * This method overrides URLStreamHandler's for parsing url of protocol "jar"
   *
   * @param url The URL object in which to store the results
   * @param url_string The String-ized URL to parse
   * @param start The position in the string to start scanning from
   * @param end The position in the string to stop scanning
   */
  protected void parseURL (URL url, String url_string, int start, int end)
  {
    // This method does not throw an exception or return a value.  Thus our
    // strategy when we encounter an error in parsing is to return without
    // doing anything.
    String file = url.getFile();

    if (!file.equals(""))
      { //has context url
        url_string = url_string.substring (start, end);
        if (url_string.startsWith("/"))
          { //url string is an absolute path
            int idx = file.lastIndexOf ("!/");

            if (idx < 0)
              throw new URLParseError("no !/ in spec");

            file = file.substring (0, idx + 1) + url_string;
          }
        else if (url_string.length() > 0)
          {
            int idx = file.lastIndexOf ("/");
            if (idx == -1) //context path is weird
              file = "/" + url_string;
            else if (idx == (file.length() - 1))
              //just concatenate two parts
              file = file + url_string;
            else
              // according to Java API Documentation, here is a little different
              // with URLStreamHandler.parseURL
              // but JDK seems doesn't handle it well
              file = file.substring(0, idx + 1) + url_string;
          }

        setURL (url, "jar", url.getHost(), url.getPort(), flat(file), null);
        return;
      }

    // Bunches of things should be true.  Make sure.
    if (end < start)
      return;
    if (end - start < 2)
      return;
    if (start > url_string.length())
      return;

    // Skip remains of protocol
    url_string = url_string.substring (start, end);

    int jar_stop;
    if ((jar_stop = url_string.indexOf("!/")) < 0)
      throw new URLParseError("no !/ in spec");

    try
      {
        new URL(url_string.substring (0, jar_stop));
      }
    catch (MalformedURLException e)
      {
        throw new URLParseError("invalid inner URL: " + e.getMessage());
      }

    if (!url.getProtocol().equals ("jar") )
      throw new URLParseError("unexpected protocol " + url.getProtocol());

    setURL (url, "jar", url.getHost(), url.getPort(), url_string, null);
  }

  /**
   * Makes the given jar url string 'flat' by removing any . and .. from
   * jar file path because ZipFile entries can only handle flat paths.
   * Inside jar files '/' is always the path separator.
   */
  private static String flat(String url_string)
  {
    int jar_stop = url_string.indexOf("!/");
    String jar_path = url_string.substring(jar_stop + 1, url_string.length());

    if (jar_path.indexOf("/.") < 0)
      return url_string;

    ArrayList<String> tokens = new ArrayList<String>();
    StringTokenizer st = new StringTokenizer(jar_path, "/");
    while (st.hasMoreTokens())
      {
        String token = st.nextToken();
        if (token.equals("."))
          continue;
        else if (token.equals(".."))
          {
            if (! tokens.isEmpty())
              tokens.remove(tokens.size() - 1);
          }
        else
          tokens.add(token);
      }

    CPStringBuilder path = new CPStringBuilder(url_string.length());
    path.append(url_string.substring(0, jar_stop + 1));

    Iterator<String> it = tokens.iterator();
    while (it.hasNext())
      path.append('/').append(it.next());

    return path.toString();
  }

  /**
   * This method converts a Jar URL object into a String.
   *
   * @param url The URL object to convert
   */
  protected String toExternalForm (URL url)
  {
    String file = url.getFile();
    String ref = url.getRef();

    // return "jar:" + file;
    // Performance!!:
    //  Do the concatenation manually to avoid resize StringBuffer's
    //  internal buffer.  The length of ref is not taken into consideration
    //  as it's a rare path.
    CPStringBuilder sb = new CPStringBuilder (file.length() + 5);
    sb.append ("jar:");
    sb.append (file);
    if (ref != null)
      sb.append('#').append(ref);
    return sb.toString();
  }
}
